#pragma once

/*
editor.hpp

Редактор
*/
/*
Copyright (c) 1996 Eugene Roshal
Copyright (c) 2000 Far Group
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
   notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
   notice, this list of conditions and the following disclaimer in the
   documentation and/or other materials provided with the distribution.
3. The name of the authors may not be used to endorse or promote products
   derived from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

#include "scrobj.hpp"
#include <farplug-wide.h>
#include "poscache.hpp"
#include "bitflags.hpp"
#include "config.hpp"
#include <unordered_map>
#include <vector>
#include "DList.hpp"
#include "noncopyable.hpp"
#include "FARString.hpp"
#include "EcoPool.hpp"
#include "edit.hpp"

class FileEditor;
class KeyBar;
class EditorMenuBar;

struct InternalEditorBookMark
{
	DWORD64 Line[POSCACHE_BOOKMARK_COUNT];
	DWORD64 Cursor[POSCACHE_BOOKMARK_COUNT];
	DWORD64 ScreenLine[POSCACHE_BOOKMARK_COUNT];
	DWORD64 LeftPos[POSCACHE_BOOKMARK_COUNT];
};

struct InternalEditorStackBookMark
{
	DWORD Line;
	DWORD Cursor;
	DWORD ScreenLine;
	DWORD LeftPos;
	InternalEditorStackBookMark *prev, *next;
};

// координаты одного найденного вхождения (поиск по кнопке "Все")
struct EditorFoundCoord
{
	int Line;
	int Pos;
	int SearchLen;
};

// чем закончился сбор вхождений для поиска по кнопке "Все"
enum class EditorFindAllResult
{
	Completed,	// файл просмотрен целиком
	Aborted,	// пользователь прервал поиск
	TooMany,	// вхождений больше, чем имеет смысл показывать списком
};

struct EditorCacheParams
{
	int Line;
	int LinePos;
	int ScreenLine;
	int LeftPos;
	int CodePage;
	int TabSize;
	int ExpandTabs;

	InternalEditorBookMark SavePos;
};

struct EditorUndoData
{
	EcoString Str;
	char EOL[4]{0};
	int StrPos {0};
	int StrNum {0};
	short Type {0};
	bool HasStr{false};

	EditorUndoData() = default;
	~EditorUndoData()
	{
	}
	EditorUndoData(const EditorUndoData& src) : EditorUndoData()
	{
		operator=(src);
	}
	EditorUndoData& operator=(const EditorUndoData &src)
	{
		if (this != &src) {
			Type = src.Type;
			StrPos = src.StrPos;
			StrNum = src.StrNum;
			CharArrayCpyZ(EOL, src.EOL);
			HasStr = src.HasStr;
			Str = src.Str;
		}
		return *this;
	}
	void SetAttributes(short SetType, int SetStrNum, int SetStrPos)
	{
		Type = SetType;
		StrNum = SetStrNum;
		StrPos = SetStrPos;
	}
	void SetData(const wchar_t *SetStr, const wchar_t *Eol, int Length = -1)
	{
		CharArrayCpyZ(EOL, Eol ? Eol : L"");
		if (SetStr) {
			Str.Assign(SetStr, (Length < 0) ? StrLength(SetStr) : Length);
			HasStr = true;
		} else {
			Str.Truncate();
			HasStr = false;
		}
	}
	void SetData(Edit *Line)
	{
		const wchar_t *Eol = nullptr;
		Line->GetString(this->Str, &Eol);
		CharArrayCpyZ(EOL, Eol ? Eol : L"");
		HasStr = true;
	}
	void ToEdit(Edit *Line)
	{
		Line->SetString(Str.CPtr(), Str.Size());
		Line->SetEOL(EOL);	// необходимо дополнительно выставлять, т.к. SetString вызывает Edit::SetBinaryString и... дальше по тексту
		Str.Compact();
	}
};

// Младший байт (маска 0xFF) юзается классом ScreenObject!!!
enum FLAGS_CLASS_EDITOR
{
	FEDITOR_MODIFIED     = 0x00000200,
	FEDITOR_JUSTMODIFIED = 0x00000400,		// 10.08.2000 skv: need to send EE_REDRAW 2.
	// set to 1 by TextChanged, no matter what
	// is value of State.
	FEDITOR_MARKINGBLOCK          = 0x00000800,
	FEDITOR_MARKINGVBLOCK         = 0x00001000,
	FEDITOR_WASCHANGED            = 0x00002000,
	FEDITOR_OVERTYPE              = 0x00004000,
	FEDITOR_NEWUNDO               = 0x00010000,
	FEDITOR_UNDOSAVEPOSLOST       = 0x00020000,
	FEDITOR_DISABLEUNDO           = 0x00040000,		// возможно процесс Undo уже идет?
	FEDITOR_LOCKMODE              = 0x00080000,
	FEDITOR_CURPOSCHANGEDBYPLUGIN = 0x00100000,		// TRUE, если позиция в редакторе была изменена
	// плагином (ECTL_SETPOSITION)
	FEDITOR_ISRESIZEDCONSOLE = 0x00800000,
	FEDITOR_PROCESSCTRLQ     = 0x02000000,		// нажата Ctrl-Q и идет процесс вставки кода символа
	FEDITOR_DIALOGMEMOEDIT   = 0x80000000,		// Editor используется в диалоге в качестве DI_MEMOEDIT
};

class Editor : public ScreenObject
{
	friend class Edit;
	friend class DlgEdit;
	friend class FileEditor;

private:
	/*
		$ 04.11.2003 SKV
		на любом выходе если была нажата кнопка выделения,
		и она его "сняла" (сделала 0-й ширины), то его надо убрать.
	*/
	class EditorBlockGuard : public NonCopyable
	{
		Editor &ed;
		void (Editor::*method)();
		bool needCheckUnmark;

	public:
		void SetNeedCheckUnmark(bool State) { needCheckUnmark = State; }
		EditorBlockGuard(Editor &ed, void (Editor::*method)())
			:
			ed(ed), method(method), needCheckUnmark(false)
		{}
		~EditorBlockGuard()
		{
			if (needCheckUnmark)
				(ed.*method)();
		}
	};

	EcoPool<Edit> EPool;
	DList<EditorUndoData> UndoData;
	EditorUndoData *UndoPos;
	EditorUndoData *UndoSavePos;
	int UndoSkipLevel;

	int LastChangeStrPos;
	int NumLastLine;
	int NumLine;
	/*
		$ 26.02.2001 IS
		Сюда запомним размер табуляции и в дальнейшем будем использовать его,
		а не Opt.TabSize
	*/
	EditorOptions EdOpt;

	int Pasting;
	wchar_t GlobalEOL[10];

	// работа с блоками из макросов (MCODE_F_EDITOR_SEL)
	Edit *MBlockStart;
	int MBlockStartX;

	Edit *BlockStart;
	int BlockStartLine;
	Edit *VBlockStart;

	int VBlockX;
	int VBlockSizeX;
	int VBlockY;
	int VBlockSizeY;

	int MaxRightPos;

	int XX2;	// scrollbar

	std::wstring strTmp, strGet;
	FARString strLastSearchStr;
	/*
		$ 30.07.2000 KM
		Новая переменная для поиска "Whole words"
	*/
	int LastSearchCase, LastSearchWholeWords, LastSearchReverse, LastSearchSelFound, LastSearchRegexp;
	int m_WordWrapPreferredCellPos;

	UINT m_codepage;	// BUGBUG
	uint64_t m_Color;
	uint64_t m_SelColor;
	uint64_t m_ColorUnChanged;

	int StartLine;
	int StartChar;

	InternalEditorBookMark SavePos;

	InternalEditorStackBookMark *StackPos;
	BOOL NewStackPos;

	int EditorID;

	FileEditor *HostFileEditor;
	Edit *TopList;
	Edit *EndList;
	Edit *TopScreen;
	int m_TopScreenVisualLine;
	int m_CachedTotalVisualLines;
	int m_CachedTopVisualLine;
	Edit *m_CachedScrollbarTopScreen;
	int m_CachedScrollbarTopScreenVisualLine;
	bool m_VisualScrollbarDirty;
	Edit *CurLine;
	Edit *LastGetLine;
	int MouseSelStartingLine{-1}, MouseSelStartingPos{-1};
	int LastGetLineNumber;
	bool SaveTabSettings;
	bool m_bWordWrap;
	bool m_MouseButtonIsHeld;

	std::unordered_map<int, uint64_t> m_gutterMarks;
	
	// Line number caching for performance
	int m_CachedTotalLines;
	int m_CachedLineNumWidth;
	bool m_LineCountDirty;
	bool m_BulkLoadMode;  // Skip expensive operations during file loading
	bool m_showCursor;
	clock_t m_BulkLoadStartTime;
	FARString m_virtualFileName;

private:
	struct MouseTarget
	{
		Edit* line{nullptr};
		int pos{-1};
		int visual_line{0};
	};

	int GetCurVisualLine() const;
	int GetTotalVisualLines();
	int GetTopVisualLine();
	int GetVisualLinesBelow(Edit* startLine, int startVisual, int limit);
	int GetWordWrapVisibleMaxLineLength() const;
	int GetTopScreenLineNumber();
	void EnsureTopScreenVisual();
	bool DecTopVisualLine();
	bool IncTopVisualLine();
	int VisualOffsetFromTop(Edit* line, int vline) const;
	bool ComputeMouseTarget(int mouse_x, int mouse_y, MouseTarget& target);
	void ApplyMouseTarget(const MouseTarget& target, bool initial_click, bool vblock, bool allow_selection);
	virtual void DisplayObject();
	void SetCursorByVisualLineCellOffset(int VisualLine, int horizontal_cell_pos);
	void RestoreWordWrapPreferredCellPos();
	void SetWordWrapCursorPosition(int NewPos);
	void SetWordWrapCursorPosition(int NewPos, int VisualLine);
	void ShowEditor(int CurLineOnly);
	void DeleteString(Edit *DelPtr, int LineNumber, int DeleteLast, int UndoLine);
	void InsertString();
	void Up();
	void Down();
	void ScrollDown();
	void ScrollUp();
	BOOL Search(int Next);
	BOOL SearchAll(const FARString &strSearchStr, int Case, int WholeWords, int Regexp, int SelectFound);
	EditorFindAllResult CollectFoundItems(const FARString &strSearchStr, int Case, int WholeWords,
			int Regexp, std::vector<EditorFoundCoord> &FoundItems);
	void ShowFoundItems(const std::vector<EditorFoundCoord> &FoundItems, int SelectFound);
	void SelectFoundPattern(const EditorFoundCoord &Coord, int SelectFound);

	void GoToVisualLine(int VisualLine);
	void GoToLine(int Line);
	void GoToPosition();

	void TextChanged(int State);

	int CalcDistance(Edit *From, Edit *To, int MaxDist);
	void Paste(const wchar_t *Src = nullptr);
	void Copy(int Append);
	void DeleteBlock();
	bool MarkBlock(bool SelVBlock, int SelStartLine, int SelStartPos, int SelWidth, int SelHeight);
	bool UnmarkBlock();
	void UnmarkBlockAndShowIt();
	void UnmarkEmptyBlock();
	void UnmarkMacroBlock();

	void ProcessPasteEvent();

	void AddUndoData(short Type, int StrNum, int StrPos, Edit *Line);
	void AddUndoData(short Type, int StrNum = 0, int StrPos = 0, const wchar_t *Str = nullptr, const wchar_t *Eol = nullptr, int Length = -1);
	EditorUndoData *BeginAddingUndoData(short Type, int StrNum, int StrPos);

	void AdjustScreenPosition();
	void Undo(int redo);
	void SelectAll();
	void HighlightAsWrapped(int Y, Edit &ShowString); // new helper function
	int CalculateTotalLines();  // Helper to count total lines
	int CalculateLineNumberWidth();  // Helper to calculate line number display width
	int CalculateTextAreaWidth(int BaseWidth, bool ReserveScrollBar);  // Helper for text viewport width
	void RecalculateAllWordWraps(bool SyncWordWrapState);
	void RememberWordWrapPreferredCellPos();
	void DrawGutterMark(int logical_line, int y, int line_num_x1);
	// void SetStringsTable();
	void BlockLeft();
	void BlockRight();
	void DeleteVBlock();
	void VCopy(int Append);
	void VPaste(wchar_t *ClipText);
	void VBlockShift(int Left);
	bool IsVerticalBlockEditMode() const;
	bool ProcessVerticalBlockEditKey(FarKey Key);
	Edit *GetStringByNumber(int DestLine);
	static void EditorShowMsg(const wchar_t *Title, const wchar_t *Msg, const wchar_t *Name, int Percent);

	int SetBookmark(DWORD Pos);
	int GotoBookmark(DWORD Pos);

	int ClearStackBookmarks();
	int DeleteStackBookmark(InternalEditorStackBookMark *sb_delete);
	int RestoreStackBookmark();
	int AddStackBookmark(BOOL blNewPos = TRUE);
	InternalEditorStackBookMark *PointerToFirstStackBookmark(int *piCount = nullptr);
	InternalEditorStackBookMark *PointerToLastStackBookmark(int *piCount = nullptr);
	InternalEditorStackBookMark *PointerToStackBookmark(int iIdx);
	int BackStackBookmark();
	int PrevStackBookmark();
	int NextStackBookmark();
	int LastStackBookmark();
	int GotoStackBookmark(int iIdx);
	int PushStackBookMark();
	int PopStackBookMark();
	int CurrentStackBookmarkIdx();
	int GetStackBookmark(int iIdx, EditorBookMarks *Param);
	int GetStackBookmarks(EditorBookMarks *Param);

	int BlockStart2NumLine(int *Pos);
	int BlockEnd2NumLine(int *Pos);
	bool CheckLine(Edit *line);
	wchar_t *Block2Text(wchar_t *ptrInitData);
	wchar_t *VBlock2Text(wchar_t *ptrInitData);

public:
	int GetEditorID() const { return EditorID; }
	void SetVirtualFileName(const wchar_t *name) { m_virtualFileName = name; }
	Editor(ScreenObject *pOwner = nullptr, bool DialogUsed = false);
	virtual ~Editor();

public:
	void EnableSaveTabSettings();
	void SetCacheParams(EditorCacheParams *pp);
	void GetCacheParams(EditorCacheParams *pp);

	bool SetCodePage(UINT codepage);											// BUGBUG
	UINT GetCodePage();															// BUGBUG

	int SetRawData(const wchar_t *SrcBuf, int SizeSrcBuf, int TextFormat);		// преобразование из буфера в список
	int GetRawData(wchar_t **DestBuf, int &SizeDestBuf, int TextFormat = 0);	// преобразование из списка в буфер

	virtual int ProcessKey(FarKey Key);
	virtual int ProcessMouse(MOUSE_EVENT_RECORD *MouseEvent);
	virtual int64_t VMProcess(MacroOpcode OpCode, void *vParam = nullptr, int64_t iParam = 0);

	void KeepInitParameters();
	void SetStartPos(int LineNum, int CharNum);
	BOOL IsFileModified() const;
	BOOL IsFileChanged() const;
	void MarkSaved();
	void SetTitle(const wchar_t *Title);
	long GetCurPos();
	int EditorControl(int Command, void *Param);
	void SetHostFileEditor(FileEditor *Editor) { HostFileEditor = Editor; };
	static void SetReplaceMode(int Mode);
	FileEditor *GetHostFileEditor() { return HostFileEditor; };
	void PrepareResizedConsole() { Flags.Set(FEDITOR_ISRESIZEDCONSOLE); }

	void SetTabSize(int NewSize);
	int GetTabSize() const { return EdOpt.TabSize; }

	void SetWordWrap(int NewMode);
	int GetWordWrap() const { return m_bWordWrap; }
	void SetConvertTabs(int NewMode);
	int GetConvertTabs() const { return EdOpt.ExpandTabs; }

	void SetDelRemovesBlocks(int NewMode);
	int GetDelRemovesBlocks() const { return EdOpt.DelRemovesBlocks; }

	void SetPersistentBlocks(int NewMode);
	int GetPersistentBlocks() const { return EdOpt.PersistentBlocks; }

	void SetAutoIndent(int NewMode) { EdOpt.AutoIndent = NewMode; }
	int GetAutoIndent() const { return EdOpt.AutoIndent; }

	void SetAutoDetectCodePage(int NewMode) { EdOpt.AutoDetectCodePage = NewMode; }
	int GetAutoDetectCodePage() const { return EdOpt.AutoDetectCodePage; }

	void SetCursorBeyondEOL(int NewMode);
	int GetCursorBeyondEOL() const { return EdOpt.CursorBeyondEOL; }

	void SetBSLikeDel(int NewMode) { EdOpt.BSLikeDel = NewMode; }
	int GetBSLikeDel() const { return EdOpt.BSLikeDel; }

	void SetCharCodeBase(int NewMode) { EdOpt.CharCodeBase = NewMode % 3; }
	int GetCharCodeBase() const { return EdOpt.CharCodeBase; }

	void SetReadOnlyLock(int NewMode) { EdOpt.ReadOnlyLock = NewMode & 3; }
	int GetReadOnlyLock() const { return EdOpt.ReadOnlyLock; }

	void SetShowScrollBar(int NewMode) { EdOpt.ShowScrollBar = NewMode; }

	void SetSearchPickUpWord(int NewMode) { EdOpt.SearchPickUpWord = NewMode; }

	void SetWordDiv(const wchar_t *WordDiv) { EdOpt.strWordDiv = WordDiv; }
	const wchar_t *GetWordDiv() { return EdOpt.strWordDiv; }

	int GetShowWhiteSpace() const { return EdOpt.ShowWhiteSpace; }
	void SetShowWhiteSpace(int NewMode);

	int GetShowLineNumbers() const { return EdOpt.ShowLineNumbers; }
	void SetShowLineNumbers(int NewMode);
	int GetShowGutterMarks() const { return EdOpt.ShowGutterMarks; }
	void SetShowGutterMarks(int NewMode);

	void GetSavePosMode(int &SavePos, int &SaveShortPos);

	// передавайте в качестве значения параметра "-1" для параметра,
	// который не нужно менять
	void SetSavePosMode(int SavePos, int SaveShortPos);

	void GetRowCol(const wchar_t *argv, int *row, int *col);

	bool BeginVBlockMarking();
	void AdjustVBlock(int PrevX);

	void Xlat();
	static void PR_EditorShowMsg();

	void FreeAllocatedData(bool FreeUndo = true);

	Edit *CreateString(const wchar_t *lpwszStr, int nLength);
	Edit *
	InsertString(const wchar_t *lpwszStr, int nLength, Edit *pAfter = nullptr, int AfterLineNumber = -1);

	void SetDialogParent(DWORD Sets);
	void SetReadOnly(int NewReadOnly) { Flags.Change(FEDITOR_LOCKMODE, NewReadOnly); };
	int GetReadOnly() { return Flags.Check(FEDITOR_LOCKMODE); };

	// Bulk load mode - skips expensive per-line operations during file loading
	void BeginBulkLoad();
	void EndBulkLoad();
	void SetOvertypeMode(int Mode);
	int GetOvertypeMode();
	void SetEditBeyondEnd(int Mode);
	void SetClearFlag(int Flag);
	int GetClearFlag();

	int GetCurCol();
	int GetCurRow() { return NumLine; }
	void SetCurPos(int NewCol, int NewRow = -1);
	void SetCursorType(bool Visible, DWORD Size);
	void GetCursorType(bool &Visible, DWORD &Size);
	void SetShowCursor(bool Enable) { m_showCursor = Enable; }
	void SetTopScreenLine(int LineNumber, int VisualLine = 0);
	void SetCursorByVisualLineCellOffset(int LineNumber, int VisualLine, int CellOffset);
	int GetCursorLine() const { return NumLine; }
	int GetCursorVisualLine() const { return GetCurVisualLine(); }
	int GetVisualLineCount(int LineNumber);
	bool GetVisualLineHighlightCells(int LineNumber, int VisualLine, int RangeStart, int RangeEnd,
			int DrawX1, int &CellX1, int &CellX2);
	bool RenderVisualLine(int LineNumber, int VisualLine, int X1, int Y, int X2);
	void SetObjectColor(uint64_t Color, uint64_t SelColor, uint64_t ColorUnChanged);
	void DrawScrollbar();

	virtual void SetPosition(int X1, int Y1, int X2, int Y2);
};

#define POSCACHE_EDIT_PARAM4_PACK(VALUE, CP, EXPAND_TABS, TAB_SIZE)                                            \
	do {                                                                                                       \
		VALUE = ((DWORD(CP) & 0xffff) | ((DWORD(EXPAND_TABS) & 0xff) << 16)                                    \
				| ((DWORD(TAB_SIZE) & 0xff) << 24));                                                           \
	} while (0)

#define POSCACHE_EDIT_PARAM4_UNPACK(VALUE, CP, EXPAND_TABS, TAB_SIZE)                                          \
	do {                                                                                                       \
		CP = (((VALUE)&0xffff) == 0xffff) ? -1 : ((VALUE)&0xffff);                                             \
		EXPAND_TABS = ((VALUE) == 0xffffffff) ? 0 : ((VALUE) >> 16) & 0xff;                                    \
		TAB_SIZE = ((VALUE) == 0xffffffff) ? 0 : ((VALUE) >> 24) & 0xff;                                       \
	} while (0)
