/*
    QAD - Qt ADvanced

    Ivan Pelipenko peri4ko@yandex.ru, Andrey Bychkov work.a.b@yandex.ru

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU Lesser General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

#ifndef SQL_TABLE_WIDGET_H
#define SQL_TABLE_WIDGET_H

#include "qad_sql_table_export.h"
#include "qpiconfig.h"

#include <QAction>
#include <QActionGroup>
#include <QBoxLayout>
#include <QCheckBox>
#include <QComboBox>
#include <QDateTimeEdit>
#include <QDebug>
#include <QDoubleSpinBox>
#include <QElapsedTimer>
#include <QHeaderView>
#include <QLabel>
#include <QLineEdit>
#include <QMenu>
#include <QMouseEvent>
#include <QScrollBar>
#include <QSqlDatabase>
#include <QSqlError>
#include <QSqlField>
#include <QSqlIndex>
#include <QSqlQuery>
#include <QSqlQueryModel>
#include <QSqlRecord>
#include <QSqlTableModel>
#include <QStyledItemDelegate>
#include <QTableView>
#include <QToolButton>


struct QAD_SQL_TABLE_EXPORT ColumnProperties {
	enum DataType {
		Unknown,
		Int,
		Float,
		Chars,
		Text,
		Binary,
		Date,
		Time,
		DateTime
	};
	ColumnProperties(const QString & table_,
	                 const QString & type_,
	                 const QString & name_,
	                 const QString & def_,
	                 bool auto_,
	                 bool prim_,
	                 bool option_);
	bool isRelation() const { return (!relation_key.isEmpty() && !relation_column.isEmpty()); }
	QString shortName() const {
		if (!isRelation()) return name;
		return relation_column;
	}
	QString fullName() const {
		if (!isRelation()) return table + "." + name;
		return (relation_table.isEmpty() ? "" : relation_table + ".") + relation_column;
	}
	DataType getType() const {
		if (!isRelation()) return type;
		return relation_type;
	}
	static DataType typeFromString(const QString & n);

	DataType type;
	int size;
	bool auto_increment;
	bool primary;
	bool optional;
	bool visible;
	bool is_text;
	QString table;
	QString name;
	QString relation_table;
	QString relation_key;
	QString relation_column;
	QList<QPair<QString, QString>> relation_list; // <id, relation_column>
	DataType relation_type;
	int key_column;
	QString def;
};


struct QAD_SQL_TABLE_EXPORT TableColumns {
	int mapColumn(int abs_ind) const {
		if (abs_ind <= 0 || abs_ind >= column_indexes.size() + 1) return 0;
		return column_indexes[abs_ind - 1];
	}
	QVector<int> column_indexes;
	QList<ColumnProperties> columns;
};


class QAD_SQL_TABLE_EXPORT SQLQueryModel: public QSqlQueryModel {
	Q_OBJECT

public:
	explicit SQLQueryModel(QString & t, QString & conn, QList<ColumnProperties> & cp, QVector<int> & ci, QObject * parent = 0)
		: QSqlQueryModel(parent)
		, table_(t)
		, column_props(cp)
		, column_indexes(ci)
		, conn_name(conn) {
		;
	}
	virtual QVariant data(const QModelIndex & item, int role = Qt::DisplayRole) const;

protected:
	virtual Qt::ItemFlags flags(const QModelIndex &) const { return (Qt::ItemIsEnabled | Qt::ItemIsEditable | Qt::ItemIsSelectable); }
	virtual bool setData(const QModelIndex & index, const QVariant & value, int role = Qt::EditRole);
	int mapColumn(int abs_ind) const {
		if (abs_ind <= 0 || abs_ind >= column_indexes.size() + 1) return 0;
		return column_indexes[abs_ind - 1];
	}
	QString & table_;
	QList<ColumnProperties> & column_props;
	QVector<int> & column_indexes;
	QString & conn_name;
signals:
	void updateTable(bool save_selection);
	void tableChanged();
};


class QAD_SQL_TABLE_EXPORT SQLUniEdit: public QWidget {
	Q_OBJECT

public:
	explicit SQLUniEdit(const ColumnProperties & prop, const QString & conn_name, QWidget * parent = 0);
	~SQLUniEdit() {
		for (int i = 0; i < w_.size(); ++i)
			deleteW(*w_[i]);
	}
	void setProp(const ColumnProperties & p);
	QString value(bool for_insert = false);
	void clear() { setValue(QVariant()); }

private:
	void deleteW(QWidget * w) {
		if (w != 0) delete w;
	}
	void resizeW(QWidget * w) {
		if (w != 0) w->setGeometry(QRect(0, 0, width(), height()));
	}
	void resizeEvent(QResizeEvent *) {
		for (int i = 0; i < w_.size(); ++i)
			resizeW(*w_[i]);
	}
	bool eventFilter(QObject * o, QEvent * e);
	QLineEdit * wtext;
	QSpinBox * wint;
	QDoubleSpinBox * wfloat;
	QDateEdit * wdate;
	QTimeEdit * wtime;
	QDateTimeEdit * wdatetime;
	QComboBox * wrelation;
	QList<QWidget **> w_;
	ColumnProperties prop_;
	QString connection_name;
private slots:
	void value_text(QString value) { valueChanged(value); }
	void value_int(int value) { valueChanged(value); }
	void value_float(double value) { valueChanged(value); }
	void value_date(QDate value) { valueChanged(value); }
	void value_time(QTime value) { valueChanged(value); }
	void value_datetime(QDateTime value) { valueChanged(value); }
public slots:
	void setValue(const QVariant & value);
	void updateRelation();
signals:
	void valueChanged(const QVariant &);
};


class QAD_SQL_TABLE_EXPORT SQLNewEdit: public QWidget {
	Q_OBJECT
	friend class SQLTableWidget;

public:
	explicit SQLNewEdit(const ColumnProperties & prop, const QString & conn_name, QWidget * parent = 0);
	~SQLNewEdit() {
		delete line;
		delete check;
	}
	void setProp(const ColumnProperties & p);
	bool isEnabled() const { return check->isChecked(); }
	QString value() const { return line->value(true); }
	void clear() { line->clear(); }

private:
	ColumnProperties prop_;
	SQLUniEdit * line;
	QCheckBox * check;
};


class QAD_SQL_TABLE_EXPORT SQLFilterEdit: public QWidget {
	Q_OBJECT
	friend class SQLTableWidget;

public:
	explicit SQLFilterEdit(const ColumnProperties & prop, const QString & conn_name, QWidget * parent = 0);
	~SQLFilterEdit() {
		delete line;
		delete combo;
	}
	void setProp(const ColumnProperties & p) {
		prop_ = p;
		line->setProp(p);
	}
	QString filter() const;
	bool isEmpty() const { return line->value().isEmpty() || combo->currentIndex() == 0; }
	void clear();

private:
	ColumnProperties prop_;
	SQLUniEdit * line;
	QComboBox * combo;
private slots:
	void value_changed() {
		if (combo->currentIndex() == 0) combo->setCurrentIndex(prop_.is_text ? 2 : 1);
	}
public slots:
signals:
	void filterChanged();
};


class QAD_SQL_TABLE_EXPORT SQLItemDelegate: public QStyledItemDelegate {
	Q_OBJECT

public:
	explicit SQLItemDelegate(QList<ColumnProperties> & cp, QVector<int> & ci, bool & ro, const QString & conn_name, QObject * parent = 0);
	~SQLItemDelegate() { ; }

private:
	QWidget * createEditor(QWidget * parent, const QStyleOptionViewItem & option, const QModelIndex & index) const;
	QString displayText(const QVariant & value, const QLocale & locale) const;
	void setModelData(QWidget * editor, QAbstractItemModel * model, const QModelIndex & index) const;
	int mapColumn(int abs_ind) const {
		if (abs_ind <= 0 || abs_ind >= column_indexes.size() + 1) return 0;
		return column_indexes[abs_ind - 1];
	}
	void setConnectionName(const QString & conn_name) { connection_name = conn_name; }
	QList<ColumnProperties> & column_props;
	QVector<int> & column_indexes;
	bool & read_only;
	QString connection_name;
private slots:
public slots:
signals:
};


namespace Ui {
class SQLTableWidget;
}


class QAD_SQL_TABLE_EXPORT SQLTableWidget: public QWidget {
	Q_OBJECT
	Q_PROPERTY(QString tableName READ tableName WRITE setTableName)
	Q_PROPERTY(QString connectionName READ connectionName WRITE setConnectionName)
	Q_PROPERTY(bool lineNewVisible READ lineNewVisible WRITE setLineNewVisible)
	Q_PROPERTY(bool lineFilterVisible READ lineFilterVisible WRITE setLineFilterVisible)
	Q_PROPERTY(bool readOnly READ readOnly WRITE setReadOnly)
	Q_PROPERTY(bool debugEnabled READ isDebugEnabled WRITE setDebugEnabled)
	friend class SQLItemDelegate;
	friend class SQLUniEdit;

public:
	SQLTableWidget(QWidget * parent = 0);
	virtual ~SQLTableWidget();

	bool isTableExists() const { return table_opened; }
	const QString & tableName() const { return table_; }

	QTableView * tableView();
	bool readOnly() const { return read_only; }

	void setDebugEnabled(bool on) { debug = on; }
	bool isDebugEnabled() const { return debug; }

	const ColumnProperties * columnProperty(int index) const {
		if (index < 0 || index >= column_props.count()) return 0;
		return &(column_props[index]);
	}
	const ColumnProperties * columnProperty(const QString & name) const { return columnProperty(columnByName(name)); }
	const QList<ColumnProperties> & columnProperties() const { return column_props; }
	QStringList columnNames() const {
		QStringList sl;
		foreach(const ColumnProperties & i, column_props)
			sl << i.name;
		return sl;
	}
	int columnsCount() const { return column_props.size(); }
	bool isColumnVisible(int ind) {
		if (!columnExists(ind)) return false;
		return column_props[ind].visible;
	}
	bool isColumnVisible(const QString & name) {
		if (!columnExists(name)) return false;
		return column_props[columnByName(name)].visible;
	}
	bool isColumnHidden(int ind) {
		if (!columnExists(ind)) return true;
		return !column_props[ind].visible;
	}
	bool isColumnHidden(const QString & name) {
		if (!columnExists(name)) return true;
		return !column_props[columnByName(name)].visible;
	}
	void setColumnVisible(int ind, bool visible);
	void setColumnVisible(const QString & name, bool visible) {
		col_vis[name] = visible;
		setColumnVisible(columnByName(name), visible);
	}
	void setColumnHidden(int ind, bool hidden) { setColumnVisible(ind, !hidden); }
	void setColumnHidden(const QString & name, bool hidden) {
		col_vis[name] = !hidden;
		setColumnVisible(columnByName(name), !hidden);
	}

	bool lineNewVisible() const;
	bool lineFilterVisible() const;
	bool addRelation(const QString & this_column, const QString & other_table, const QString & other_key, const QString & other_column);
	void addTranslation(const QString & file);
	void addFixedColumnTranslation(const QString & col_name, const QString & col_tr, const QString & col_tt = QString());
	void fetchMore() {
		if (model) model->fetchMore();
	}
	void fetchAll() {
		if (model)
			while (model->canFetchMore())
				model->fetchMore();
	}
	void selectId(int id);

	void setAdditionalActions(QList<QAction *> a);

	QSqlRecord headerRecord() const {
		if (model) return model->record();
		return QSqlRecord();
	}
	QSqlRecord currentRecord() const;

	void setCustomQuery(const QString & q);
	void setCustomColumnNames(const QStringList & cn) {
		custom_col_names = cn;
		updateTable();
	}

	static bool isTableExists(const QString & table, const QString & conn_name = QLatin1String(QSqlDatabase::defaultConnection)) {
		return QSqlDatabase::database(conn_name).tables().contains(table, Qt::CaseInsensitive);
	}

	static bool
	executeScript(const QString & text, QSqlDatabase db = QSqlDatabase::database(), bool skip_errors = false, bool sqlite = false);
	static bool
	executeScriptFile(const QString & file, QSqlDatabase db = QSqlDatabase::database(), bool skip_errors = false, bool sqlite = false);

	static bool isConnectedToDatabase(const QString & conn_name = QLatin1String(QSqlDatabase::defaultConnection)) {
		return QSqlDatabase::database(conn_name, false).isOpen();
	}
	static bool connectToDatabase(const QString & config, const QString & conn_name = QLatin1String(QSqlDatabase::defaultConnection));

	QString connectionName() const { return connection_name; }

private:
	static QString preprocessScript(QString text);

	bool eventFilter(QObject * o, QEvent * e);
	void timerEvent(QTimerEvent *);
	void changeEvent(QEvent * e);
	void stopTimer() {
		if (timer != 0) killTimer(timer);
		timer = 0;
	}
	bool checkTable();
	QStringList getTableColumns(const QString & t);
	QString getColumnType(const QString & t, const QString & c);
	static QList<QPair<QString, QString>>
	getColumnValues(const QString & t, const QString & k, const QString & c, const QString & conn_name);
	void setTableVisible(bool on);
	int mapColumn(int abs_ind) const {
		if (abs_ind <= 0 || abs_ind >= column_indexes.size() + 1) return 0;
		return column_indexes[abs_ind - 1];
	}
	int columnByName(const QString & name) const {
		for (int i = 0; i < column_props.size(); ++i)
			if (column_props[i].name == name) return i;
		return -1;
	}
	bool columnExists(int index) const { return (index >= 0 && index < column_props.size()); }
	bool columnExists(const QString & name) const {
		for (int i = 0; i < column_props.size(); ++i)
			if (column_props[i].name == name) return true;
		return false;
	}
	QPair<QString, QString> trColumn(const QString & n);
	QString tableNames();
	QString columnNames();
	QString columnFilters();
	QString columnRelations();

	Ui::SQLTableWidget * ui;
	QString table_, cquery, custom_query;
	QStringList custom_col_names;
	SQLQueryModel * model;
	// QAction act_add, act_del;
	QList<ColumnProperties> column_props;
	QList<SQLNewEdit *> column_news;
	QList<SQLFilterEdit *> column_filters;
	QVector<int> column_indexes;
	QStringList wait_rels;
	QMenu popup_menu, popup_col, popup_filter;
	QElapsedTimer tm;
	QActionGroup filters_group;
	QAction * action_del;
	QList<QAction *> add_actions;
	QMap<QString, QPair<QString, QString>> translates, fixed_translates;
	QMap<QString, bool> col_vis;
	int timer;
	bool filters_active, table_opened, read_only, first_update, debug;
	static QString _dir;
	QPoint pp;
	QString connection_name;


public slots:
	void setTableName(const QString & t);
	void setLineNewVisible(bool on);
	void setLineFilterVisible(bool on);
	void setReadOnly(bool yes);
	void updateTable(bool save_selection = false);
	void setConnectionName(const QString & conn_name);

private slots:
	void on_buttonAdd_clicked();
	void on_actionFiltersClear_triggered();
	void on_view_clicked(const QModelIndex & index);
	void header_sectionResized(int logicalIndex, int oldSize, int newSize);
	void header_sortIndicatorChanged(int logicalIndex, Qt::SortOrder order);
	void scrolled(int value);
	void del_triggered();
	void column_triggered(bool on);

signals:
	void rowClicked(int row);
	void recordClicked(QSqlRecord rec);
	void selectionChanged();
	void tableChanged();
};


#endif // MYSQLTABLE_H
