#include "FishIDEEditor.h"

#include "Settings.h"

#include "Project.h"

// check if data is text or binary
bool  DataIsBinary(String const &data)
{
	if(data.IsEmpty())
		return false;
	const char *s = ~data;
	const char *end = s + (data.GetCount() > 150000 ? 150000 : data.GetCount());
	if(*(end - 1) == '\r' && data.GetCount() > 150000)
		end++;
	while(s < end)
	{
		int c = (byte)*s;
		if(c < 32)
		{
			if(c == '\r')
			{
				if(s[1] != '\n')
					return true;
				s += 2;
			}
			else
				if(c == '\t' || c == '\n')
					s++;
				else
					return true;
		}
		else
			s++;
	}
	return false;
}

// constructor
FishIDEEditorClass::FishIDEEditorClass()
{
	// not active
	_kind = EditorKind::NONE;
	
	// not forced read only
	
	
	_codeEditor.WhenUpdate = THISBACK(changeCb);
}

// destructor
FishIDEEditorClass::~FishIDEEditorClass()
{
}

String FishIDEEditorClass::GetPath()
{
	if(!_editorFile)
		return "";
	return _editorFile->GetPath();
}

// setup code editor defaults
void FishIDEEditorClass::SetupCodeEditor(void)
{
	_codeEditor.DisableBreakpointing();
	String ext = ToLower(GetFileExt(_editorFile->GetPath()));
	String hlType = findarg(ext, ".lst", ".c", ".cpp", ".cxx", ".h", ".hpp", ".hxx", ".ino") >= 0 ? "cpp" : "";
	if(!hlType.IsEmpty())
		_codeEditor.Highlight(hlType);
	else
		// other types are supported -- let highlighter handle them
		_codeEditor.Highlight(ext.Mid(1));
	StyleEditor();
}

// style an editor based on settings
void FishIDEEditorClass::StyleEditor(Theme const *theme)
{
	// if no theme given, get current one
	if(!theme)
		theme = &Themes.GetCurrent();
	
	// get hihglight settings
	VectorMap<String, Theme::HL> const &syntax = theme->GetSyntax();
	
	// apply syntax
	for(int i = 0; i < CodeEditor::HL_COUNT; i++)
	{
		const char *name = CodeEditor::GetHlName(i);
		int idx = syntax.Find(name);
		if(idx < 0)
			continue;
		Theme::HL const &hl = syntax[idx];
		_codeEditor.SetHlStyle(i, hl._color, hl._bold, hl._italic, hl._underline);
	}
	_codeEditor.HiliteScope(theme->GetScopeHL());
	_codeEditor.HiliteBracket(theme->GetBracesHL());
	_codeEditor.HiliteIfDef(theme->GetConditionalsInfo());
	_codeEditor.HiliteIfEndif(theme->GetConditionalTracing());
	_codeEditor.ThousandsSeparator(theme->GetThousandsSeparator());
	Color lineCol;
	if(theme->GetCurrentLine())
	{
		String s = CodeEditor::GetHlName(HighlightSetup::SHOW_LINE);
		int idx = syntax.Find(s);
		if(idx < 0)
			lineCol = (Color)Null;
		else
			lineCol = syntax[idx]._color;
	}
	else
		lineCol = (Color)Null;
	_codeEditor.ShowCurrentLine(lineCol);
	
	_codeEditor.SetFont(theme->GetEditorFont());

	_codeEditor.TabSize(Settings.GetEditorTabsize());
	_codeEditor.IndentAmount(Settings.GetEditorIndentAmount());
	_codeEditor.ShowTabs(Settings.GetEditorShowTabs());
	_codeEditor.IndentSpaces(Settings.GetEditorIndentSpaces());
	_codeEditor.NoParenthesisIndent(Settings.GetEditorNoParenthesisIndent());
	_codeEditor.LineNumbers(Settings.GetEditorShowLineNumbers());
//	bookmarkPos bookmarkPos
// findpicksel bookmarkPos
// findpicktext findpicktext
	_codeEditor.PersistentFindReplace(Settings.GetEditorPersistentFindReplace());
	_codeEditor.FindReplaceRestorePos(Settings.GetEditorFindReplaceRestorePos());
	_codeEditor.AutoEnclose(Settings.GetEditorAutoEnclose());
	
	_codeEditor.Refresh();
}


// check/replace child control
void FishIDEEditorClass::SetChild(Ctrl *newChild)
{
	Ctrl *child = GetFirstChild();
	if(newChild != child)
	{
		if(child)
			RemoveChild(child);
		if(newChild)
			AddChild(&(newChild->SizePos()));
	}
}

// detect changes in editor
void FishIDEEditorClass::changeCb(void)
{
	if(_editorFile)
	{
		// sets the modified flag
		_editorFile->Set(Get());
		
		// trigger library list update
		_editorFile->GetProject()->editorUpdateCb();
	}
}

void FishIDEEditorClass::reloadCb(void)
{
	// store current editor position
	LineEdit::EditPos pos = GetEditPos();
	
	// reload data
	Set(_editorFile->Get());
	
	// clear undo data
	ClearUndoData();

	// restore current editor position
	SetEditPos(pos);
}

// flush the editor onto belonging FileInfo object
void FishIDEEditorClass::Flush(void)
{
	if(!_editorFile)
		return;
	
	// flush data, if needed
	_editorFile->Set(Get());
	
	// flush cursor position
	_editorFile->SetEditPos(GetEditPos());
}
		
// assign the editor to an IdeFile object
void FishIDEEditorClass::Assign(FileInfo *fileInfo)
{
	// if we want same file, don't loose time
	if(_editorFile == fileInfo)
	{
		if(!fileInfo)
		{
			_kind = EditorKind::NONE;
			SetChild(NULL);
		}
		return;
	}
	
	// flush current editor content, if needed
	Flush();
	
	// flush undo data
	// we can't do into Flush() because that one
	// is used elsewhere and invalidates (picks) undo data
	if(_editorFile)
		_editorFile->SetUndoData(GetUndoData());

	// replace file being edited
	_editorFile = fileInfo;

	// if not given a file, just hide the editor
	if(!fileInfo)
	{
		_kind = EditorKind::NONE;
		SetChild(NULL);
		return;
	}

	// get file kind from extension and content
	String const &data = _editorFile->Get();
	if(DataIsBinary(data))
	{
		String ext = ToLower(GetFileExt(_editorFile->GetPath()));
		if(findarg(ext, ".jpg", ".jpeg", ".tif", ".tiff", ".png", ".bmp", ".gif") >= 0)
			_kind = EditorKind::IMAGE;
		else
			_kind = EditorKind::BINARY;
	}
	else
	{
		String ext = ToLower(GetFileExt(_editorFile->GetPath()));
		if(findarg(ext, ".c", ".cpp", ".cxx", ".h", ".hpp", ".hxx", ".ino") >= 0)
			_kind = EditorKind::CODE;
		else
			_kind = EditorKind::TEXT;
	}
	
	// show content
	Set(data);
	
	// sets the cursor
//	SetCursor(_editorFile->GetCursor());
	SetEditPos(_editorFile->GetEditPos());

	// insert the correct editor as a child
	switch(_kind)
	{
		case EditorKind::CODE:
		case EditorKind::TEXT:
			SetChild(&_codeEditor);
			SetupCodeEditor();
			SetUndoData(_editorFile->GetUndoData());
			_codeEditor.SetEditable(!_editorFile->IsReadOnly());
			
			break;
			
		case EditorKind::IMAGE:
			SetChild(&_imageView);
			break;
			
		case EditorKind::BINARY:
			SetChild(&_hexView);
			break;
			
		default:
			SetChild(NULL);
			break;
	}
}

// reload the file into editor
void FishIDEEditorClass::Reload(void)
{
	if(!_editorFile)
		return;
	_editorFile->Reload();
}

// sets the focus to contained child
void FishIDEEditorClass::SetFocus(void)
{
	Ctrl *child = GetFirstChild();
	if(child)
		child->SetFocus();
	else
		ParentCtrl::SetFocus();
}

bool FishIDEEditorClass::IsReadOnly(void)
{
	switch(_kind)
	{
		case EditorKind::CODE:
		case EditorKind::TEXT:
			return _codeEditor.IsReadOnly();
		default:
			break;
	}
	return true;
}

void FishIDEEditorClass::SetReadOnly(bool b)
{
	if(!_editorFile)
		return;
	_editorFile->SetReadOnly(b);
	switch(_kind)
	{
		case EditorKind::CODE:
		case EditorKind::TEXT:
			if(b)
				_codeEditor.SetReadOnly();
			else
				_codeEditor.SetEditable();
		default:
			break;
	}
}

bool FishIDEEditorClass::IsSelection() const
{
	switch(_kind)
	{
		case EditorKind::CODE:
		case EditorKind::TEXT:
			return _codeEditor.IsSelection();
		default:
			break;
	}
	return false;
}

bool FishIDEEditorClass::GetSelection(int& l, int& h) const
{
	switch(_kind)
	{
		case EditorKind::CODE:
		case EditorKind::TEXT:
			return _codeEditor.GetSelection(l, h);
		default:
			break;
	}
	return false;
}

WString FishIDEEditorClass::GetSelectionW() const
{
	switch(_kind)
	{
		case EditorKind::CODE:
		case EditorKind::TEXT:
			return _codeEditor.GetSelectionW();
		default:
			break;
	}
	return "";
}

void FishIDEEditorClass::SetSelection(int anchor, int cursor)
{
	switch(_kind)
	{
		case EditorKind::CODE:
		case EditorKind::TEXT:
			_codeEditor.SetSelection(anchor, cursor);
			break;
		default:
			break;
	}
}

void FishIDEEditorClass::SelectAll()
{
	switch(_kind)
	{
		case EditorKind::CODE:
		case EditorKind::TEXT:
			_codeEditor.SelectAll();
			break;
		default:
			break;
	}
}


WString FishIDEEditorClass::GetW() const
{
	switch(_kind)
	{
		case EditorKind::CODE:
		case EditorKind::TEXT:
			return _codeEditor.GetW();
		default:
			break;
	}
	return "";
}

WString FishIDEEditorClass::GetI()
{
	switch(_kind)
	{
		case EditorKind::CODE:
		case EditorKind::TEXT:
			return _codeEditor.GetI();
		default:
			break;
	}
	return "";
}

String  FishIDEEditorClass::Get(byte charset) const
{
	switch(_kind)
	{
		case EditorKind::CODE:
		case EditorKind::TEXT:
			return _codeEditor.Get(charset);

		// we don't edit images or binary files, so just return file content on get
		case EditorKind::IMAGE:
		case EditorKind::BINARY:
			return _editorFile->Get();
		default:
			break;
	}
	return "";
}

void FishIDEEditorClass::Set(const WString& s)
{
	switch(_kind)
	{
		case EditorKind::CODE:
		case EditorKind::TEXT:
			_codeEditor.Set(s);
			changeCb();
			break;
		default:
			break;
	}
}

void FishIDEEditorClass::Set(const String& s, byte charset)
{
	switch(_kind)
	{
		case EditorKind::CODE:
		case EditorKind::TEXT:
			_codeEditor.Set(s, charset);
			break;
		case EditorKind::IMAGE:
			_imageView.Set(s);
			break;
		case EditorKind::BINARY:
			_hexView.Set(s);
			break;
		default:
			break;
	}
}

int FishIDEEditorClass::GetLength() const
{
	switch(_kind)
	{
		case EditorKind::CODE:
		case EditorKind::TEXT:
			return _codeEditor.GetLength();
		case EditorKind::IMAGE:
		case EditorKind::BINARY:
			return _editorFile->Get().GetCount();
		default:
			break;
	}
	return 0;
}

int FishIDEEditorClass::GetIndexLinePos(Point pos) const
{
	switch(_kind)
	{
		case EditorKind::CODE:
		case EditorKind::TEXT:
			return _codeEditor.GetIndexLinePos(pos);
		default:
			break;
	}
	return 0;
}

int FishIDEEditorClass::GetCursor() const
{
	switch(_kind)
	{
		case EditorKind::CODE:
		case EditorKind::TEXT:
			return _codeEditor.GetCursor();
		case EditorKind::BINARY:
			return _hexView.GetCursor();
		default:
			break;
	}
	return 0;
}

void FishIDEEditorClass::SetCursor(int cursor)
{
	switch(_kind)
	{
		case EditorKind::CODE:
		case EditorKind::TEXT:
			_codeEditor.SetCursor(cursor);
			break;
		case EditorKind::BINARY:
			_hexView.SetCursor(cursor);
			break;
		default:
			break;
	}
}

void FishIDEEditorClass::CenterCursor()
{
	switch(_kind)
	{
		case EditorKind::CODE:
		case EditorKind::TEXT:
			_codeEditor.CenterCursor();
			break;
		default:
			break;
	}
}

LineEdit::EditPos FishIDEEditorClass::GetEditPos(void) const
{
	switch(_kind)
	{
		case EditorKind::CODE:
		case EditorKind::TEXT:
			return _codeEditor.GetEditPos();
			break;
		default:
			break;
	}
	return LineEdit::EditPos();
}

void FishIDEEditorClass::SetEditPos(LineEdit::EditPos const &pos)
{
	switch(_kind)
	{
		case EditorKind::CODE:
		case EditorKind::TEXT:
			_codeEditor.SetEditPosSb(pos);
			break;
		default:
			break;
	}
}

LineEdit::UndoData FishIDEEditorClass::GetUndoData(void)
{
	switch(_kind)
	{
		case EditorKind::CODE:
		case EditorKind::TEXT:
			return _codeEditor.PickUndoData();
			break;
		default:
			break;
	}
	return LineEdit::UndoData();
}

void FishIDEEditorClass::SetUndoData(LineEdit::UndoData &&data)
{
	switch(_kind)
	{
		case EditorKind::CODE:
		case EditorKind::TEXT:
			_codeEditor.SetPickUndoData(pick(data));
			break;
		default:
			break;
	}
}

void FishIDEEditorClass::ClearUndoData(void)
{
	switch(_kind)
	{
		case EditorKind::CODE:
		case EditorKind::TEXT:
			_codeEditor.ClearUndo();
			_codeEditor.ClearRedo();
			break;
		default:
			break;
	}
}


void FishIDEEditorClass::Remove(int pos, int size)
{
	switch(_kind)
	{
		case EditorKind::CODE:
		case EditorKind::TEXT:
			_codeEditor.Remove(pos, size);
			break;
		default:
			break;
	}
}

int FishIDEEditorClass::Insert(int pos, const WString& txt)
{
	switch(_kind)
	{
		case EditorKind::CODE:
		case EditorKind::TEXT:
			return _codeEditor.Insert(pos, txt);
		default:
			break;
	}
	return 0;
}


void FishIDEEditorClass::Cut()
{
	switch(_kind)
	{
		case EditorKind::CODE:
		case EditorKind::TEXT:
			_codeEditor.Cut();
			break;
		default:
			break;
	}
}

void FishIDEEditorClass::Copy()
{
	switch(_kind)
	{
		case EditorKind::CODE:
		case EditorKind::TEXT:
			_codeEditor.Copy();
			break;
		default:
			break;
	}
}

void FishIDEEditorClass::Paste()
{
	switch(_kind)
	{
		case EditorKind::CODE:
		case EditorKind::TEXT:
			_codeEditor.Paste();
			break;
		default:
			break;
	}
}

void FishIDEEditorClass::Undo()
{
	switch(_kind)
	{
		case EditorKind::CODE:
		case EditorKind::TEXT:
			_codeEditor.Undo();
			break;
		default:
			break;
	}
}

void FishIDEEditorClass::Redo()
{
	switch(_kind)
	{
		case EditorKind::CODE:
		case EditorKind::TEXT:
			_codeEditor.Redo();
			break;
		default:
			break;
	}
}

bool FishIDEEditorClass::IsUndo() const
{
	switch(_kind)
	{
		case EditorKind::CODE:
		case EditorKind::TEXT:
			return _codeEditor.IsUndo();
		default:
			break;
	}
	return false;
}

bool FishIDEEditorClass::IsRedo() const
{
	switch(_kind)
	{
		case EditorKind::CODE:
		case EditorKind::TEXT:
			return _codeEditor.IsRedo();
		default:
			break;
	}
	return false;
}

void FishIDEEditorClass::NextUndo()
{
	switch(_kind)
	{
		case EditorKind::CODE:
		case EditorKind::TEXT:
			_codeEditor.NextUndo();
			break;
		default:
			break;
	}
}

bool FishIDEEditorClass::Find(bool back, bool block)
{
	switch(_kind)
	{
		case EditorKind::CODE:
		case EditorKind::TEXT:
			return _codeEditor.Find(back, block);
			break;
		default:
			return false;
			break;
	}
}

void FishIDEEditorClass::FindReplace(bool pick_selection, bool pick_text, bool replace)
{
	switch(_kind)
	{
		case EditorKind::CODE:
		case EditorKind::TEXT:
			_codeEditor.FindReplace(pick_selection, pick_text, replace);
			break;
		default:
			break;
	}
}

void FishIDEEditorClass::FindNext()
{
	switch(_kind)
	{
		case EditorKind::CODE:
		case EditorKind::TEXT:
			_codeEditor.FindNext();
			break;
		default:
			break;
	}
}

void FishIDEEditorClass::FindPrev()
{
	switch(_kind)
	{
		case EditorKind::CODE:
		case EditorKind::TEXT:
			_codeEditor.FindPrev();
			break;
		default:
			break;
	}
}

bool FishIDEEditorClass::FindString(bool back)
{
	switch(_kind)
	{
		case EditorKind::CODE:
		case EditorKind::TEXT:
			return _codeEditor.FindString(back);
			break;
		default:
			return false;
			break;
	}
}

int FishIDEEditorClass::BlockReplace()
{
	switch(_kind)
	{
		case EditorKind::CODE:
		case EditorKind::TEXT:
			return _codeEditor.BlockReplace();
			break;
		default:
			return 0;
			break;
	}
}

CodeEditor::FindReplaceData FishIDEEditorClass::GetFindReplaceData()
{
	return _codeEditor.GetFindReplaceData();
}

void FishIDEEditorClass::SetFindReplaceData(const CodeEditor::FindReplaceData& d)
{
	_codeEditor.SetFindReplaceData(d);
}

void FishIDEEditorClass::Renumber(void)
{
	switch(_kind)
	{
		case EditorKind::CODE:
		case EditorKind::TEXT:
			_codeEditor.Renumber();
			break;
		default:
			break;
	}
}

void FishIDEEditorClass::CachePos(int pos)
{
	switch(_kind)
	{
		case EditorKind::CODE:
		case EditorKind::TEXT:
			_codeEditor.CachePos(pos);
			break;
		default:
			break;
	}
}

int FishIDEEditorClass::GetLinePos(int& pos) const
{
	switch(_kind)
	{
		case EditorKind::CODE:
		case EditorKind::TEXT:
			return _codeEditor.GetLinePos(pos);
			break;
		default:
			break;
	}
	return 0;
}

WString FishIDEEditorClass::GetWLine(int i) const
{
	switch(_kind)
	{
		case EditorKind::CODE:
		case EditorKind::TEXT:
			return _codeEditor.GetWLine(i);
			break;
		default:
			break;
	}
	return "";
}

void FishIDEEditorClass::PutI(WithDropChoice<EditString>& edit)
{
	switch(_kind)
	{
		case EditorKind::CODE:
		case EditorKind::TEXT:
			_codeEditor.PutI(edit);
			break;
		default:
			break;
	}
}

FishIDEEditorClass &__GetFishIDEEditor(void)
{
	static FishIDEEditorClass fish;
	return fish;
}
