/*
    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 QCODEEDIT_H
#define QCODEEDIT_H

#include "qad_widgets_export.h"

#include <QIcon>
#include <QTextEdit>
#include <QUrl>


namespace Ui {
class QCodeEdit;
}

class QPlainTextEdit;
class QCodeEditCompleter;
class IconedLabel;
class _QCE_Viewport;


class QAD_WIDGETS_EXPORT QCodeEdit: public QWidget {
	Q_OBJECT
	Q_PROPERTY(QString text READ text WRITE setText)
	Q_PROPERTY(bool showSpaces READ showSpaces WRITE setShowSpaces)
	Q_PROPERTY(bool showLineNumbers READ showLineNumbers WRITE setShowLineNumbers)
	Q_PROPERTY(bool wordCompletitionEnabled READ wordCompletitionEnabled WRITE setWordCompletitionEnabled)
	Q_PROPERTY(QFont editorFont READ editorFont WRITE setEditorFont)

	friend class QCodeEditCompleter;
	friend class _QCE_Viewport;

public:
	QCodeEdit(QWidget * parent = nullptr);
	~QCodeEdit() override;

	enum ACClassType {
		Keyword,
		Function,
		Namespace
	};
	struct QAD_WIDGETS_EXPORT ACEntry {
		ACEntry(const QString & t = QString(), const QString & n = QString(), const QString & h = QString()): type(t), name(n), hint(h) {
			declaration_pos = -1;
		}
		bool isNull() const { return type.isEmpty() && name.isEmpty(); }
		QString declaration() const;
		ACEntry & addHint(QString h) {
			hint = h;
			return *this;
		}
		ACEntry & addHelpHRef(QUrl h) {
			help_href = h;
			return *this;
		}
		ACEntry & addCustomData(QVariant d) {
			custom_data = d;
			return *this;
		}
		QString type;
		QString name;
		QString hint;
		int declaration_pos;
		QString declaration_file;
		QUrl help_href;
		QVariant custom_data;
	};

	QTextCursor textCursor() const;
	QTextDocument * document() const;
	void setDocument(QTextDocument * doc);
	void setTextCursor(const QTextCursor & c);
	void centerCursor();
	void insertText(const QString & text);
	void appendText(const QString & text);
	void setCustomExtraSelection(const QList<QTextEdit::ExtraSelection> & es);
	QRect cursorRect() const;
	QRect cursorRect(const QTextCursor & cursor) const;
	QString text() const;
	QStringList cursorScope() const;
	bool showSpaces() const { return spaces_; }
	bool showLineNumbers() const;
	void setHelpHintVisible(bool on);
	bool isHelpHintVisible() const;
	QString commentText() const;
	void setCommentText(QString t);

	void setEditorFont(QFont f);
	QFont editorFont() const;

	QPlainTextEdit * textEdit() const;

	void registerAutoCompletitionClass(int id, ACClassType ac_class, const QString & name, const QIcon & icon = QIcon());
	bool wordCompletitionEnabled() const { return word_complete; }
	bool isDarkTheme() const { return is_dark_theme; }

	static QBrush invertedBrush(const QBrush & b);
	static QTextCharFormat invertedFormat(const QTextCharFormat & f);

protected:
	typedef QPair<QString, QString> StringsPair;
	typedef QPair<int, QList<ACEntry>> ACSection; // section, ACClass.id -> list of entries
	typedef QList<ACSection> ACList;              // list of sections

	virtual ACList autoCompletitionList(const QStringList & scope, const QString & written) const { return ACList(); }
	virtual void parse() {}
	virtual void documentUnset() {}
	virtual void documentChanged(QTextDocument * d) {}
	virtual void linkClicked(ACEntry entry) {}
	virtual bool linkValidate(ACEntry e) { return true; }

	QString selectArg(QString s, int arg);
	void raiseHelp(QTextCursor tc, int arg = -1);
	QTextCursor functionStart(QTextCursor tc, int * arg);
	ACList wordsCompletitionList(const QString & written) const;
	QPair<QStringList, QString> getScope(QTextCursor tc, bool * ok = nullptr);
	static int skipRange(const QString & s, int pos, QChar oc, QChar cc, QChar sc = QChar());
	static int skipCWord(const QString & s, int pos);
	static bool matchWritten(QString s, QString w);
	static QChar pairChar(QChar c);

private slots:
	void _activateLink(QCodeEdit::ACEntry e) { linkClicked(e); }
	void gotoHelpHRef(QCodeEdit::ACEntry e);
	void resizeOverlay();
	void syncScrolls();
	void scrollUp();
	void scrollDown();
	void hideHelp();
	void deleteLine();
	void copyLineUp();
	void copyLineDown();
	void moveLineUp();
	void moveLineDown();
	void indent();
	void deindent();
	void autoIndent();
	void showLink();
	void hideLink();
	void gotoLink();
	void invokeAutoCompletition(bool force = false);
	void commitCompletition();
	void searchAll();
	void search_triggered();
	void textEdit_cursorPositionChanged();
	void textEdit_textChanged();
	void textEdit_selectionChanged();
	void textEdit_redoAvailable(bool available);
	void on_comboSearch_currentTextChanged(const QString & t);
	void on_buttonReplace_clicked();
	void on_buttonReplaceSearch_clicked();
	void on_buttonReplaceAll_clicked();

public slots:
	void updateLines();
	void scrollToTop();
	void newLine();
	void newLineBefore();
	void setFocus();
	void setText(const QString & t);
	void setShowSpaces(bool yes);
	void setShowLineNumbers(bool yes);
	void search(const QString & t);
	void searchNext(bool next);
	void searchNext() { searchNext(true); }
	void searchPrevious();
	void hideSearch();
	void setWordCompletitionEnabled(bool on) { word_complete = on; }

signals:
	void textChanged();
	void parseRequest();

private:
	struct QAD_WIDGETS_EXPORT ACClass {
		ACClass(int i = -2, ACClassType c = QCodeEdit::Keyword, const QString & n = QString(), const QIcon & ic = QIcon())
			: id(i)
			, class_(c)
			, name(n)
			, icon(ic) {}
		int id;
		ACClassType class_;
		QString name;
		QIcon icon;
	};
	enum LabelHelpType {
		lhMain,
		lhHint,
		lhF1,
		lh_last
	};

	bool eventFilter(QObject * o, QEvent * e) override;
	void showEvent(QShowEvent *) override;
	void timerEvent(QTimerEvent *) override;
	void leaveEvent(QEvent *) override;
	void changeEvent(QEvent * e) override;
	bool codeKeyEvent(QKeyEvent * ke);
	void toggleComment();
	void highlightBrackets();
	void applyExtraSelection();
	void clearSearch();
	void moveToSearch();
	int searchIndFromCursor();
	QRect cursorRect();
	QRect blockSelectionRect();
	QVector<QTextCursor> blockSelectionCursors(QRect bsr);
	void repaintCursor();
	void drawCursor();
	bool hasBlockSelection() const;
	void startBlockSelection();
	void cancelBlockSelection();
	void switchBlockSelection(QKeyEvent * ke = nullptr);
	bool removeBlockSelection(bool is_del);
	void insertBlockSelection(QString text);
	void createBlockSelection();
	void cancelDragCursor();
	ACEntry findEntryOnCursor(QTextCursor tc, int arg = -1, ACClass * acc = nullptr, QPair<QStringList, QString> * scope = nullptr);
	void detectTheme();

	Ui::QCodeEdit * ui;
	QCodeEditCompleter * completer;
	IconedLabel * lbl_help[3];
	QFrame * widget_help;
	_QCE_Viewport * overlay;
	QTextEdit::ExtraSelection es_line, es_cursor, es_bracket, es_range, es_search, es_link;
	QList<QTextEdit::ExtraSelection> es_selected, es_custom, es_brackets, es_search_list, es_blockselection;
	QMap<int, ACClass> ac_classes;
	QStringList cursor_scope;
	QString comment_text;
	ACEntry link_entry, help_entry;
	QTextCursor block_start_cursor, drag_cursor;
	int prev_lc, auto_comp_pl, timer_parse, timer_blink, cur_search_ind, pos_press, pos_el_press;
	int cursor_width;
	bool is_dark_theme = false;
	bool spaces_, _ignore_focus_out, _first, _destructor, _replacing;
	bool word_complete, help_visible, cursor_state, block_sel_state;
};


#endif // QCODEEDIT_H
