/*
 Program WinCaml: Graphical User Interface
 for interactive use of Caml-Light and Ocaml.
 Copyright (C) 2005-2017 Jean Mouric 35700 Rennes France
 email: jean.mouric@orange.fr

 This program is free software: you can redistribute it and/or modify
 it under the terms of the GNU 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 General Public License for more details.

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

// File Qt.h

#pragma once

#include <deque>
#include <string>
#ifdef QT5
#include <QtWidgets>
#else
#include <QtGui>
#endif
#include <QProcess>
#ifdef Q_OS_WIN
#include <windows.h>
#else
#include <signal.h>
#endif
#include "../All/resource.h"
#include "../All/Utils.h"

#define code_escape Qt::Key_Escape
#define code_return Qt::Key_Return
#define idCancel QMessageBox::Cancel
#define idNo QMessageBox::No
#define idYes QMessageBox::Yes
#define endofline L"\r"

#ifdef Q_OS_WIN
#define MONOSPACE L"Courier New"
#else
#ifdef Q_OS_MAC
#define MONOSPACE L"Menlo"
#else
#define MONOSPACE L"DejaVu Sans Mono"
#endif
#endif

#define ARROW Qt::ArrowCursor
#define EAST_WEST Qt::SplitHCursor
#define SOUTH_NORTH Qt::SplitVCursor

#define CSPLITTER_BASE

typedef struct Rect
{
	long    left;
	long    top;
	long    right;
	long    bottom;
} Rect;

class CMDIFrame;
class CMDIChildFrame;
class CFindDialog;
class CFont;
class CRichEdit;

using namespace std;

extern const wstring appName;
extern const wstring version, version1;
extern const bool liveSplit;

void aboutMessage(const wstring& msg);
void errorMessage(const wstring& msg);
bool yesnoMessage(const wstring& msg);
int yesnocancelMessage(const wstring& msg);

wstring applicationDirectoryPath();
void closeFindDialog(bool enableMenus = false);
void findDialog();
void findReplaceDialog();
unsigned long rgb(int r, int g, int b);
void setSearchEdit(CRichEdit* edit);

inline wstring redoShortCut()
{
#ifdef Q_OS_X11
	return L"Ctrl+Maj+Z";
#else
#if defined(Q_OS_MAC) && defined(QT5)
    return L"";
#else
    return QKeySequence(QKeySequence::Redo).toString().toStdWString();
#endif
#endif
}

inline wstring undoShortCut()
{
#ifdef Q_OS_X11
    return L"Ctrl+Z";
#else
#if defined(Q_OS_MAC) && defined(QT5)
    return L"";
#else
    return QKeySequence(QKeySequence::Undo).toString().toStdWString();
#endif
#endif
}

class CMDIApp: public QApplication
{
public:    
    bool alreadyExists;

    CMDIApp(int& argc, char* argv[]);
    ~CMDIApp();    
    int run(CMDIFrame& frame);
#ifdef Q_OS_MAC
    bool event(QEvent* event);
#endif
private:
#ifdef Q_OS_MAC
    CMDIFrame* mdiFrame;
#else
    int argC;
    char** argV;
#endif
};

class CMDIFrame : public QMainWindow
{
    Q_OBJECT
    friend class CMDIChildFrame;
    friend class CMDIApp;
	friend class CFont;
public:
	int LeftMargin;
	int TopMargin;
	int RightMargin;
	int BottomMargin;

    deque<CMDIChildFrame*> mdiChildren;
    
	void enableMenuItem(int cmd, bool on);
	void setStatus(int index, const wstring& text);
protected:
	int xDim;
	int yDim;
    
	bool cascade;
	long frameLeft;
	long frameTop;
	long frameWidth;
	long frameHeight;
	deque<wstring*>* droppedFiles;
	bool Maximized;
	bool StatusBarVisible;
	bool ToolbarVisible;

	CMDIFrame();
	~CMDIFrame();
	void checkMenuItem(int cmd, bool on);
	void colorDialog(unsigned long& color);
	void fontDlg(CFont*& font);
	bool isIconic();
	void moveWindow(int left, int top, int width, int height);
	void onCascadeWindow();
	void onFileClose();
	void onFileExit();
	virtual void onClose();
	void onTileWindow();
	bool openFileDialog(wstring& fileName);
	void openIcon();
	void printSetUpDialog(int& leftMargin, int& topMargin, int& rightMargin, int& bottomMargin);
	bool saveFileDialog(wstring& title);
	bool saveFormattedDocumentDialog(wstring& title);
	void setActiveChild(CMDIChildFrame *childFrame);
	void setMenuItemText(int item, const wstring& title, bool enabled);
	void tabDialog(int& spaceNumPerTab);
	void updateFrame();
private:
	virtual void onCommand(int cmd);
	virtual void onDrop();
	virtual void openFile(const wstring& fileName);
	virtual void openFiles(deque<wstring>& fileName);
	virtual bool reviewChanges();

	QMdiArea* mdiArea;
	QMenu* windowMenu;
	QSignalMapper* windowMapper;
	QToolBar* toolbar;
	QLabel status[2];
	deque<wstring> filePaths;

	void closeEvent(QCloseEvent *event);
	void dragEnterEvent(QDragEnterEvent *event);
	void dropEvent(QDropEvent *event);
	void moveEvent(QMoveEvent *event);
	void resizeEvent(QResizeEvent *event);

	void createMDIClient();
	void createMenus();
	void createStatusbar();
	void createToolbar();
	CMDIChildFrame* getActiveChild();
	void onMove(size_t left, size_t top);
	void onOtherInstanceMessage();
	void onSize();
	void startTimer();
private slots:
    void onCommandAux(int cmd);
    void onActivate(QMdiSubWindow* window);
    void onTimer();
    void setActiveSubWindow(QWidget *window);
    void updateWindowMenu();
};

class CMDIChildFrame : public QWidget
{
	Q_OBJECT
	friend class CMDIFrame;
public:
	CRichEdit* textEdit;
	wstring title;

	void drawFocusRect(int left, int top, int right, int bottom);
	void getClientSize(int& width, int& height);
	void hide();
	bool isMaximized();
	void setCursor(int cursor);
	void setWindowModified(bool modified);
	void releaseCapture();
	void setCapture();
	void showMaximized();
protected:
	CMDIChildFrame(CMDIFrame* mdiFrame, bool maximized);
	~CMDIChildFrame();
	virtual void onClose();
	virtual void onDestroy();
	void setTitle(const wstring& title);
    void showDefault();
	void showNormal();
	void startTimer();
private:
    QTimer* timer;
	bool saveMaximized;
	virtual void onCommand(int cmd);
	virtual void onEditCopy();
	virtual void onEditCut();
	virtual void onEditPaste();
	virtual void onEditSelectAll();
	virtual void onFileSaveAs();
	virtual void onLeftButtonDown(int x, int y);
	virtual void onLeftButtonUp();
	virtual void onActivate();
	virtual void onMouseMove(int x, int y, bool leftButtonDown);
	virtual void onSize();
	virtual bool reviewChange();

	QMdiSubWindow* subWindow;

	void closeEvent(QCloseEvent *event);
	void mouseMoveEvent(QMouseEvent *event);
	void mousePressEvent(QMouseEvent *event );
	void mouseReleaseEvent(QMouseEvent *event);
	void resizeEvent(QResizeEvent * event);
	void setDocumentIcon();
private slots:
	virtual void onTimer();
};

class CHighlighter;

class CRichEdit: public QPlainTextEdit
{
	friend class CFindDialog;
	friend class CFindReplaceDialog;
public:
	bool selectionChanged;
	unsigned long *T;
    unsigned int forecolorCount;
	CHighlighter* highlighter;
    size_t commandStart;
    size_t commandEnd;
    bool inBuffer;

    CRichEdit(CMDIChildFrame* win);
    ~CRichEdit();
    int appendFile(const wstring& fileName, int encoding);
    void copy();
    void cut();
    void focus();
    size_t getCaretPosition();
    void getSelection(size_t& selStart, size_t& selEnd);
    wstring getText();
    wstring getText(size_t start, size_t length);
    size_t getTextLength();
    void hideSelection();
    size_t lineFirstChar(size_t lineIndex);
    size_t lineFromChar(size_t charIndex);
    int loadFile(const wstring& fileName, bool guessEnc, int &preferredEncoding);
	void move(int left, int top, int right, int bottom);
    void paste();
    void printFormattedDialog();
    void replaceAll(const wstring& findWhat, const wstring& replaceWith, unsigned long flags);
    void resumeLayout();
    int saveFile(const wstring& pszFileName, int encoding);
    void saveFormattedDocument(const wstring& pszFileName);
    void selectAll();
    void setBackground(unsigned long color);
    void setFont(CFont* font);
    void setReadOnly(bool b = true);
    void setSelectedText(const wstring& s);
    void setTabs(int charSize, int spaceCountPerTab);
    void setText(size_t start, size_t length, const wstring& text, unsigned long color = 0);
    void setTextBackground(size_t a, size_t l, unsigned long backColor);
    void setTextColor(size_t start, size_t length, unsigned long color);
    void setTextDefaultBackground(size_t a, size_t l);
    void setUseTabs(bool IndentWithTabs);
    void setWrapping(bool on);
    void showSelection();
    void suspendLayout();
	void updateCursor();
	void updateView();
	size_t visibleTextEnd();
    size_t visibleTextOffset();
	virtual wstring status();
protected:
	Rect margins;

	virtual void beginPrint();
	virtual void endPrint();
	virtual void setSelection(size_t selStart, size_t selEnd);
    void setColors();
private:
	virtual void onChar(int vKeyCode);
	virtual void onEnterKeyDown();
	virtual void onKeyDown();
	virtual void onKeyUp();
	virtual void onLeftButtonUp(bool ctrlDown);
	virtual void onReplace(const wstring& replaceWith);
    virtual void onReplaceAll(const wstring& findWhat, const wstring& replaceWith, unsigned long flags);
	virtual void onReturnKeyDown();
	virtual void undo();
	virtual void redo();
	virtual void replace(size_t a, size_t l, wstring s);

	CMDIChildFrame* childFrame;

	void onFindClose();
	void onFindNext(const wstring& findWhat, QTextDocument::FindFlags flags);
	void keyPressEvent(QKeyEvent*event);
	void keyReleaseEvent(QKeyEvent*event);
	void mouseMoveEvent(QMouseEvent *event);
	void mouseReleaseEvent(QMouseEvent *event);
};

class CFindDialog : public QDialog
{
    Q_OBJECT
public:
    QLineEdit *lineEdit1;
    QCheckBox* checkBox1;
    QCheckBox* checkBox2;

    CFindDialog(CMDIFrame* frame);
    ~CFindDialog();
	void init();
    void Show(){show(); hidden = false;}
    void Hide(){hide(); hidden = true;}
    bool IsHidden(){return hidden;}
private:
    bool hidden;
    QLabel *label1;
    QPushButton *findButton;
    QHBoxLayout *topLeftLayout;
    QGridLayout *mainLayout;
    QRadioButton* radioButton1;
    QRadioButton* radioButton2;
    QVBoxLayout* layout1;
    QHBoxLayout* layout2;
	void closeEvent(QCloseEvent*);
private slots:
    void find();
    void sync(QString s);
    void sync();
};

class CFindReplaceDialog : public QDialog
{
    Q_OBJECT
public:
    QLineEdit *lineEdit1;
    QLineEdit *lineEdit2;
    QCheckBox* checkBox1;
    QCheckBox* checkBox2;

    CFindReplaceDialog(CMDIFrame* frame);
    ~CFindReplaceDialog();
    void init();
    void Show(){show(); hidden = false;}
    void Hide(){hide(); hidden = true;}
    bool IsHidden(){return hidden;}
    QTextDocument::FindFlags findFlags;
private:
    bool hidden;
    QLabel *label1;
    QLabel *label2;
    QPushButton *findButton;
    QPushButton *replaceButton;
    QPushButton *replaceAllButton;
    QHBoxLayout *topLeftLayout;
    QHBoxLayout *bottomLeftLayout;
    QHBoxLayout* layout1;
    QGridLayout *mainLayout;
	void closeEvent(QCloseEvent*);
private slots:
    void find();
    void replace();
    void replaceAll();
    void sync(QString s);
    void sync();
};

class CFont
{
	friend void CMDIFrame::fontDlg(CFont*& font);
	friend void CRichEdit::setFont(CFont* font);
public:
	wstring* fntName;
	int fntSize;
    bool bold;
    bool italic;

	CFont(const wstring& fontName, int fontSize, bool bold = false, bool italic = false);
	~CFont();
private:
	QFont* qfont;
};

#ifndef Q_OS_WIN
class CProcess: public QProcess
{
public:
	CProcess()
	{
		setProcessChannelMode(QProcess::MergedChannels);
	}
    bool interrupt()
    {
        if (!isRunning()) return true;
        ::kill(pid(), SIGINT);
#ifndef Q_OS_MAC
        QProcess::write("\n");
#endif
        return true;
    }
    bool isRunning()
    {
        return state() != QProcess::NotRunning;
    }

    string readAllStandardOutput()
    {
        return QProcess::readAllStandardOutput().data();
    }
    void stop()
    {
        interrupt();
        QProcess::write("exit 0;;\n");
        int i;
        for (i = 0; i < 10; i++){if (waitForFinished(100)) break;}
        if (i == 10)
        {
            terminate();
        }
    }
};
#else
#define BUFFERSIZE 32768
class CProcess
{
public:
    CProcess()
    {
        ::memset(&pi,0,sizeof(PROCESS_INFORMATION));
        ::memset(&startInfo,0,sizeof(STARTUPINFO));
        exitCode = 0;
        buffer = new char[BUFFERSIZE];
    }
    bool interrupt()
    {
        if (!isRunning()) return true;
        if (::GenerateConsoleCtrlEvent(CTRL_BREAK_EVENT, pi.dwProcessId))
        {
            Sleep(100);
            writeToPipe("\015\012");
            return true;
        }
        return false;
    }
    bool isRunning()
    {
        if (pi.hProcess) ::GetExitCodeProcess(pi.hProcess, &exitCode);
        else exitCode = 0;
        return exitCode == STILL_ACTIVE;
    }
    string read()
    {
        int count;
        while ((count = readFromPipe(buffer, BUFFERSIZE - 1))== 0);
        buffer[count] = 0;
        string st(buffer);
        return st;
    }
    string readAllStandardOutput()
    {
        bA.erase();
        for(;;)
        {
            DWORD count = readFromPipe(buffer, BUFFERSIZE - 1);
            if (count == 0) break;
            buffer[count] ='\0';
            bA.append(buffer);
        }
        return bA;
    }
    bool start(const wstring& commandLine)
    {
        ::memset(&pi,0,sizeof(PROCESS_INFORMATION));
        ::memset(&startInfo,0,sizeof(STARTUPINFO));
        exitCode = 0;

        SECURITY_ATTRIBUTES saAttr;
        saAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
        saAttr.bInheritHandle = TRUE;
        saAttr.lpSecurityDescriptor = NULL;

        if (! ::CreatePipe(&hStdoutRd, &hStdoutWr, &saAttr, 8192))
            return false;

        if (! ::CreatePipe(&hStdinRd, &hStdinWr, &saAttr, 8192))
            return false;

        startInfo.dwFlags = STARTF_USESTDHANDLES|STARTF_USESHOWWINDOW;
        startInfo.wShowWindow = SW_SHOW;
        startInfo.hStdOutput = hStdoutWr;
        startInfo.hStdError = hStdoutWr;
        startInfo.hStdInput = hStdinRd;

        wstring appDirPath = applicationDirectoryPath();

        return ::CreateProcess(NULL,LPWSTR(commandLine.c_str()),NULL,NULL,TRUE,
                               CREATE_NEW_PROCESS_GROUP|NORMAL_PRIORITY_CLASS,NULL,LPWSTR(appDirPath.c_str()),&startInfo, &pi
                               ) ? true : false;
    }
	void stop()
    {
        if (pi.hProcess)
        {
            ::GenerateConsoleCtrlEvent(CTRL_BREAK_EVENT, pi.dwProcessId);
            Sleep(100);
            writeToPipe("\015\012");
            Sleep(100);
            ::TerminateProcess(pi.hProcess, 0);
            ::memset(&pi,0,sizeof(PROCESS_INFORMATION));
            ::memset(&startInfo,0,sizeof(STARTUPINFO));
            exitCode = 0;
        }
    }
    void write(const char *data)
    {
        writeToPipe(data);
    }
private:
	char* buffer;
	string bA;
	HANDLE hStdinRd, hStdinWr, hStdoutRd, hStdoutWr;
	PROCESS_INFORMATION pi;
	STARTUPINFO startInfo;
	DWORD exitCode;

	int readFromPipe(char *data,int len)
	{
		DWORD count;
		::PeekNamedPipe(hStdoutRd,data,len,NULL,&count,NULL);
		if (count == 0)
			return 0;
		if (! ::ReadFile(hStdoutRd, data, len, &count, NULL) || count == 0)
			return 0;
		return (int)count;
	}
	int writeToPipe(const char *data)
	{
		DWORD count;
		if (! ::WriteFile(hStdinWr, data, (DWORD)strlen(data), &count, NULL))
			return 0;
		return (int)count;
	}
};
#endif

// class CSplitterBase ---------------------------------------------------------------------------------------------------

#define MINSIZE     2
#define HALFBARWIDTH   2

class CSplitterBase
{
protected:
    CMDIChildFrame* childFrame;
    bool verticalBar;
	int height;
	int width;

    CSplitterBase(CMDIChildFrame*)
    {
    }

    void moveVerticalSash(int)
    {
    }
    
    void moveHorizontalSash(int)
    {
    }
};
