#include "lgi/common/Lgi.h"
#include "LgiIde.h"
#include "DocEdit.h"
#define COMP_STYLE				1
#define LOG_STYLE				0
#define PROFILE_STYLE			0
#if PROFILE_STYLE
	#define PROF(str)			Prof.Add(str)
#else
	#define PROF(str)
#endif
#define ColourComment			LColour(0, 140, 0)
#define ColourHashDef			LColour(0, 0, 222)
#define ColourLiteral			LColour(192, 0, 0)
#define ColourKeyword			LColour::Black
#define ColourType				LColour(0, 0, 222)
#define ColourCode				LColour(140, 140, 180)
#define ColourHtml				LColour(80, 80, 255)
#define ColourPre				LColour(150, 110, 110)
#define ColourStyle				LColour(110, 110, 150)
#define IsSymbolChar(ch)		(IsAlpha(ch) || (ch) == '_')
#define DetectKeyword()			\
	Node *n = &Root;			\
	char16 *e = s;				\
	do							\
	{							\
		int idx = n->Map(*e);	\
		if (idx < 0)			\
			break;				\
		n = n->Next[idx];		\
		e++;					\
	}							\
	while (n);
					
struct LanguageParams
{
	const char **Keywords;
	const char **Types;
	const char **Edges;
};
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// CPP
const char *DefaultKeywords[] = {"if", "elseif", "endif", "else", "ifeq", "ifdef", "ifndef", "ifneq", "include", NULL};
const char *CppKeywords[] = {"extern", "class", "struct", "static", "default", "case", "break",
							"switch", "new", "delete", "sizeof", "return", "enum", "else",
							"if", "for", "while", "do", "continue", "public", "virtual", 
							"protected", "friend", "union", "template", "typedef", "dynamic_cast",
							NULL};
const char *CppTypes[] = {	"int", "char", "short", "long", "signed", "unsigned", "double", "float", "bool", "const", "void",
							"int8", "int16", "int32", "int64",
							"uint8", "uint16", "uint32", "uint64",
							"char16", "wchar_t",
							"LArray", "GHashTbl", "List", "LString", "LAutoString", "LAutoWString",
							"LAutoPtr", "LHashTbl",
							NULL};
const char *CppEdges[] = {	"/*", "*/", "\"", NULL };
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Python
const char *PythonKeywords[] = {"def", "try", "except", "import", "if", "for", "elif", "else", "class", NULL};
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// XML
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// HTML
const char *HtmlEdges[] = {	"<?php", "?>", "/*", "*/", "<style", "</style>", "<pre", "</pre>", "\"", "\'", NULL };
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
LanguageParams LangParam[] =
{
	// Unknown
	{NULL, NULL, NULL},
	// Plain text
	{DefaultKeywords, NULL, NULL},
	// C/C++
	{CppKeywords, CppTypes, CppEdges},
	// Python
	{PythonKeywords, NULL, NULL},
	// Xml
	{NULL, NULL, NULL},
	// Html/Php
	{NULL, NULL, HtmlEdges}
};
DocEditStyling::DocEditStyling(DocEdit *view) : 
	LThread("DocEditStyling.Thread"),
	LMutex("DocEditStyling.Lock"),
	View(view),
	Event("DocEditStyling.Event"),
	ParentState(KWaiting),
	WorkerState(KWaiting),
	Params(view)
{
}
void DocEdit::OnApplyStyles()
{
	#if PROFILE_STYLE
	LProfile Prof("OnApplyStyles");
	#endif
	PROF("Lock");
	LTextView3::LStyle Vis(STYLE_NONE);
	GetVisible(Vis);
	if (DocEditStyling::Lock(_FL))
	{
		PROF("Insert");
		
		if (Params.Styles.Length())
		{
			Style.Swap(Params.Styles);
			#if LOG_STYLE
			LgiTrace("Swapped in %i styles.\n", (int)Style.Length());
			#endif
			PROF("Inval");
			if (Params.Dirty.Start >= 0)
			{
				#if LOG_STYLE
				LgiTrace("Visible rgn: %i + %i = %i\n", Vis.Start, Vis.Len, Vis.End());
				LgiTrace("Dirty rgn: %i + %i = %i\n", Params.Dirty.Start, Params.Dirty.Len, Params.Dirty.End());
				#endif
				ssize_t CurLine = -1, DirtyStartLine = -1, DirtyEndLine = -1;
				GetTextLine(Cursor, &CurLine);
				LTextLine *Start = GetTextLine(Params.Dirty.Start, &DirtyStartLine);
				LTextLine *End = GetTextLine(MIN(Size, Params.Dirty.End()), &DirtyEndLine);
				if (CurLine >= 0 &&
					DirtyStartLine >= 0 &&
					DirtyEndLine >= 0)
				{
					#if LOG_STYLE
					LgiTrace("Dirty lines %i, %i, %i\n", CurLine, DirtyStartLine, DirtyEndLine);
					#endif
						
					if (DirtyStartLine != CurLine ||
						DirtyEndLine != CurLine)
					{
						LRect c = GetClient();
						LRect r(c.x1,
								Start->r.Valid() ? DocToScreen(Start->r).y1 : c.y1,
								c.x2,
								Params.Dirty.End() >= Vis.End() ? c.y2 : DocToScreen(End->r).y2);
							
						#if LOG_STYLE
						LgiTrace("Cli: %s, Start rgn: %s, End rgn: %s, Update: %s\n",
								c.GetStr(), Start->r.GetStr(), End->r.GetStr(), r.GetStr());
						#endif
						Invalidate(&r);
					}						
				}
				else
				{
					#if LOG_STYLE
					LgiTrace("No Change: %i, %i, %i\n", CurLine, DirtyStartLine, DirtyEndLine);
					#endif
				}
			}
			else
			{
				#if LOG_STYLE
				LgiTrace("Invalidate everything\n");
				#endif
				Invalidate();
			}
		}
		PROF("Unlock");
		
		DocEditStyling::Unlock();
	}
}
int DocEditStyling::Main()
{
	LThreadEvent::WaitStatus s;
	while (ParentState != KExiting && (s = Event.Wait()) == LThreadEvent::WaitSignaled)
	{
		StylingParams p(View);
		if (Lock(_FL))
		{
			Params.Dirty.Empty();
			Params.Styles.Empty();
			p = Params;
			Unlock();
		}
		WorkerState = KStyling;
		#if LOG_STYLE
		LgiTrace("DocEdit.Worker starting style...\n");
		#endif
		switch (FileType)
		{
			case SrcCpp:
				StyleCpp(p);
				break;
			case SrcPython:
				StylePython(p);
				break;
			case SrcXml:
				StyleXml(p);
				break;
			case SrcHtml:
				StyleHtml(p);
				break;
			default:
				StyleDefault(p);
				break;
		}
		if (ParentState != KCancel)
		{
			#if LOG_STYLE
			LgiTrace("DocEdit.Worker finished style... Items=%i ParentState=%i\n", (int)p.Styles.Length(), ParentState);
			#endif
			auto r = View->PostEvent(M_STYLING_DONE);
			if (ParentState != KExiting)
			{
				LAssert(r);
			}
		}
		else
		{
			#if LOG_STYLE
			LgiTrace("DocEdit.Worker canceled style...\n");
			#endif
		}
		if (LMutex::Lock(_FL))
		{
			Params.Dirty = p.Dirty;
			Params.Styles.Swap(p.Styles);
			LMutex::Unlock();
		}
		WorkerState = KWaiting;
	}
	return 0;
}

void DocEditStyling::StyleCpp(StylingParams &p)
{
	#if PROFILE_STYLE
	LProfile Prof("DocEdit::StyleCpp");
	#endif
	if (!p.Text.Length())
		return;
	char16 *Text = p.Text.AddressOf();
	char16 *s = Text;
	char16 *e = s + p.Text.Length();
		
	PROF("Scan");
	LUnrolledList<LTextView3::LStyle> Out;
	for (; ParentState != KCancel && s < e; s++)
	{
		switch (*s)
		{
			case '\"':
			case '\'':
				p.StyleString(s, e, ColourLiteral, &Out);
				break;
			case '#':
			{
				// Check that the '#' is the first non-whitespace on the line
				bool IsWhite = true;
				for (char16 *w = s - 1; w >= Text && *w != '\n'; w--)
				{
					if (!IsWhiteSpace(*w))
					{
						IsWhite = false;
						break;
					}
				}
				if (IsWhite)
				{
					auto &st = Out.New().Construct(View, LTextView3::STYLE_IDE);
					st.Start = s - Text;
					st.Font = View->GetFont();
							
					char LastNonWhite = 0;
					while (s < e)
					{
						if
						(
							// Break at end of line
							(*s == '\n' && LastNonWhite != '\\')
							||
							// Or the start of a comment
							(*s == '/' && s[1] == '/')
							||
							(*s == '/' && s[1] == '*')
						)
							break;
						if (!IsWhiteSpace(*s))
							LastNonWhite = *s;
						s++;
					}
							
					st.Len = (s - Text) - st.Start;
					st.Fore = ColourHashDef;
					s--;
				}
				break;
			}
			case '0':
			case '1':
			case '2':
			case '3':
			case '4':
			case '5':
			case '6':
			case '7':
			case '8':
			case '9':
			{
				if (s == Text || !IsSymbolChar(s[-1]))
				{
					auto &st = Out.New().Construct(View, LTextView3::STYLE_IDE);
					st.Start = s - Text;
					st.Font = View->GetFont();
					bool IsHex = false;
					if (s[0] == '0' &&
						ToLower(s[1]) == 'x')
					{
						s += 2;
						IsHex = true;
					}
							
					while (s < e)
					{
						if
						(
							IsDigit(*s)
							||
							*s == '.'
							||
							(
								IsHex
								&&
								(
									(*s >= 'a' && *s <= 'f')
									||
									(*s >= 'A' && *s <= 'F')
								)
							)
						)
							s++;
						else
							break;
					}
							
					st.Len = (s - Text) - st.Start;
					st.Fore = ColourLiteral;
					s--;
				}
				while (s < e - 1 && IsDigit(s[1]))
					s++;
				break;
			}
			case '/':
			{
				if (s[1] == '/')
				{
					auto &st = Out.New().Construct(View, LTextView3::STYLE_IDE);
					st.Start = s - Text;
					st.Font = View->GetFont();
					while (s < e && *s != '\n')
						s++;
					st.Len = (s - Text) - st.Start;
					st.Fore = ColourComment;
					s--;
				}
				else if (s[1] == '*')
				{
					auto &st = Out.New().Construct(View, LTextView3::STYLE_IDE);
					st.Start = s - Text;
					st.Font = View->GetFont();
					s += 2;
					while (s < e && !(s[-2] == '*' && s[-1] == '/'))
						s++;
					st.Len = (s - Text) - st.Start;
					st.Fore = ColourComment;
					s--;
				}
				break;
			}
			default:
			{
				wchar_t Ch = ToLower(*s);
				if (Ch >= 'a' && Ch <= 'z')
				{
					DetectKeyword();
					if (n && n->Type)
					{
						auto &st = Out.New().Construct(View, LTextView3::STYLE_IDE);
						st.Start = s - Text;
						#ifdef MAC
						#warning "Weird mac bold issues fixme.")
						st.Font = View->GetFont();
						#else
						st.Font = n->Type == KType ? View->GetFont() : View->GetBold();
						#endif
						st.Len = e - s;
						st.Fore = n->Type == KType ? ColourType : ColourKeyword;
					}
					else
					{
						while (	IsAlpha(*e) ||
								IsDigit(*e) ||
								*e == '_')
							e++;
					}
					s = e - 1;
				}
			}
		}
	}
	#if COMP_STYLE
	PROF("Compare");
	auto &Vis = p.Visible;
	if (Vis.Valid() &&
		ParentState != KCancel &&
		PrevStyle.Length())
	{
		LArray<LTextView3::LStyle*> Old, Cur;
		for (auto s : PrevStyle)
		{
			if (s.Overlap(Vis))
				Old.Add(&s);
			else if (Old.Length())
				break;
		}
		for (auto s : Out)
		{
			if (s.Overlap(Vis))
				Cur.Add(&s);
			else if (Cur.Length())
				break;
		}
		for (int o=0; o<Old.Length(); o++)
		{
			bool Match = false;
			LTextView3::LStyle *OldStyle = Old[o];
			for (int n=0; n<Cur.Length(); n++)
			{
				if (*OldStyle == *Cur[n])
				{
					Old.DeleteAt(o--);
					Cur.DeleteAt(n--);
					Match = true;
					break;
				}
			}
			if (!Match)
				p.Dirty.Union(*OldStyle);
		}
		for (int n=0; n<Cur.Length(); n++)
		{
			p.Dirty.Union(*Cur[n]);
		}
		PrevStyle = Out;
	}	
	#endif
	p.Styles.Swap(Out);
}
void DocEditStyling::StylePython(StylingParams &p)
{
	char16 *Text = p.Text.AddressOf();
	char16 *e = Text + p.Text.Length();
		
	auto &Style = p.Styles;
	for (char16 *s = Text; s < e; s++)
	{
		switch (*s)
		{
			case '\"':
			case '\'':
				p.StyleString(s, e, ColourLiteral);
				break;
			case '#':
			{
				// Single line comment
				auto &st = Style.New().Construct(View, LTextView3::STYLE_IDE);
				st.Start = s - Text;
				st.Font = View->GetFont();
				while (s < e && *s != '\n')
					s++;
				st.Len = (s - Text) - st.Start;
				st.Fore = ColourComment;
				s--;
				break;
			}
			case '0':
			case '1':
			case '2':
			case '3':
			case '4':
			case '5':
			case '6':
			case '7':
			case '8':
			case '9':
			{
				if (s == Text || !IsSymbolChar(s[-1]))
				{
					auto &st = Style.New().Construct(View, LTextView3::STYLE_IDE);
					st.Start = s - Text;
					st.Font = View->GetFont();
					bool IsHex = false;
					if (s[0] == '0' &&
						ToLower(s[1]) == 'x')
					{
						s += 2;
						IsHex = true;
					}
							
					while (s < e)
					{
						if
						(
							IsDigit(*s)
							||
							*s == '.'
							||
							(
								IsHex
								&&
								(
									(*s >= 'a' && *s <= 'f')
									||
									(*s >= 'A' && *s <= 'F')
								)
							)
						)
							s++;
						else
							break;
					}
							
					st.Len = (s - Text) - st.Start;
					st.Fore = ColourLiteral;
					s--;
				}
				while (s < e - 1 && IsDigit(s[1]))
					s++;
				break;
			}
			default:
			{
				wchar_t Ch = ToLower(*s);
				if (Ch >= 'a' && Ch <= 'z')
				{
					DetectKeyword();
					if (n && n->Type)
					{
						auto &st = Style.New().Construct(View, LTextView3::STYLE_IDE);
						st.Start = s - Text;
						st.Font = n->Type == KType ? View->GetFont() : View->GetBold();
						st.Len = e - s;
						st.Fore = n->Type == KType ? ColourType : ColourKeyword;
					}
					s = e - 1;
				}
				break;
			}
		}
	}
}
void DocEditStyling::StyleDefault(StylingParams &p)
{
	char16 *Text = p.Text.AddressOf();
	char16 *e = Text + p.Text.Length();
		
	auto &Style = p.Styles;
	for (char16 *s = Text; s < e && ParentState < KCancel; s++)
	{
		switch (*s)
		{
			case '\"':
			case '\'':
			case '`':
			{
				auto &st = Style.New().Construct(View, LTextView3::STYLE_IDE);
				bool Quoted = s > Text && s[-1] == '\\';
				st.Start = s - Text - Quoted;
				st.Font = View->GetFont();
				char16 Delim = *s++;
				while
				(
					s < e
					&&
					*s != Delim
					&&
					!(Delim == '`' && *s == '\'')
				)
				{
					if (*s == '\\')
					{
						if (!Quoted || s[1] != Delim)
							s++;
					}
					else if (*s == '\n')
						break;
					s++;
				}
				st.Len = (s - Text) - st.Start + 1;
				st.Fore = ColourLiteral;
				break;
			}
			case '#':
			{
				// Single line comment
				auto &st = Style.New().Construct(View, LTextView3::STYLE_IDE);
				st.Start = s - Text;
				st.Font = View->GetFont();
				while (s < e && *s != '\n')
					s++;
				st.Len = (s - Text) - st.Start;
				st.Fore = ColourComment;
				s--;
				break;
			}
			case '0':
			case '1':
			case '2':
			case '3':
			case '4':
			case '5':
			case '6':
			case '7':
			case '8':
			case '9':
			{
				if (s == Text || !IsSymbolChar(s[-1]))
				{
					auto &st = Style.New().Construct(View, LTextView3::STYLE_IDE);
					st.Start = s - Text - ((s > Text && strchr("-+", s[-1])) ? 1 : 0);
					st.Font = View->GetFont();
					bool IsHex = false;
					if (s[0] == '0' &&
						ToLower(s[1]) == 'x')
					{
						s += 2;
						IsHex = true;
					}
							
					while (s < e)
					{
						if
						(
							IsDigit(*s)
							||
							*s == '.'
							||
							(
								IsHex
								&&
								(
									(*s >= 'a' && *s <= 'f')
									||
									(*s >= 'A' && *s <= 'F')
								)
							)
						)
							s++;
						else
							break;
					}
					if (*s == '%')
						s++;
							
					st.Len = (s - Text) - st.Start;
					st.Fore = ColourLiteral;
					s--;
				}
				while (s < e - 1 && IsDigit(s[1]))
					s++;
				break;
			}
			default:
			{
				if (*s >= 'a' && *s <= 'z')
				{
					/*
					if (HasKeyword[*s - 'a'])
					{
						static char16 buf[64], *o = buf;
						char16 *e = s;
						while (IsSymbolChar(*e))
							*o++ = *e++;
						*o = 0;
						KeyworkType type = Keywords.Find(buf);
						
						if (type != KNone)
						{
							LAutoPtr<LStyle> st(new LTextView3::LStyle(STYLE_IDE));
							if (st)
							{
								st->View = this;
								st->Start = s - Text;
								st->Font = Bold;
								st->Len = e - s;
								st->Fore = ColourKeyword;
								InsertStyle(st);
								s = e - 1;
							}
						}
					}
					*/
				}
				break;
			}
		}
	}
}
void DocEditStyling::StyleXml(StylingParams &p)
{
	char16 *Text = p.Text.AddressOf();
	char16 *e = Text + p.Text.Length();
		
	auto &Style = p.Styles;
	for (char16 *s = Text; s < e; s++)
	{
		switch (*s)
		{
			case '\"':
			case '\'':
				p.StyleString(s, e, ColourLiteral);
				break;
			case '0':
			case '1':
			case '2':
			case '3':
			case '4':
			case '5':
			case '6':
			case '7':
			case '8':
			case '9':
			{
				if (s == Text || !IsSymbolChar(s[-1]))
				{
					auto &st = Style.New().Construct(View, LTextView3::STYLE_IDE);
					st.Start = s - Text;
					st.Font = View->GetFont();
					bool IsHex = false;
					if (s[0] == '0' &&
						ToLower(s[1]) == 'x')
					{
						s += 2;
						IsHex = true;
					}
							
					while (s < e)
					{
						if
						(
							IsDigit(*s)
							||
							*s == '.'
							||
							(
								IsHex
								&&
								(
									(*s >= 'a' && *s <= 'f')
									||
									(*s >= 'A' && *s <= 'F')
								)
							)
						)
							s++;
						else
							break;
					}
							
					st.Len = (s - Text) - st.Start;
					st.Fore = ColourLiteral;
					s--;
				}
				while (s < e - 1 && IsDigit(s[1]))
					s++;
				break;
			}
			case '<':
			{
				if (s[1] == '!' &&
					s[2] == '-' &&
					s[3] == '-')
				{
					auto &st = Style.New().Construct(View, LTextView3::STYLE_IDE);
					st.Start = s - Text;
					st.Font = View->GetFont();
					s += 4;
					while
					(
						s < e
						&&
						!
						(
							s[-3] == '-'
							&&
							s[-2] == '-'
							&&
							s[-1] == '>'
						)
					)
						s++;
					st.Len = (s - Text) - st.Start;
					st.Fore = ColourComment;
					s--;
				}
				break;
			}
			default:
			{
				if (*s >= 'a' && *s <= 'z')
				{
					/*
					if (HasKeyword[*s - 'a'])
					{
						static char16 buf[64], *o = buf;
						char16 *e = s;
						while (IsSymbolChar(*e))
							*o++ = *e++;
						*o = 0;
						KeyworkType type = Keywords.Find(buf);
						
						if (type != KNone)
						{
							LAutoPtr<LStyle> st(new LTextView3::LStyle(STYLE_IDE));
							if (st)
							{
								st->View = this;
								st->Start = s - Text;
								st->Font = Bold;
								st->Len = e - s;
								st->Fore = ColourKeyword;
								InsertStyle(st);
								s = e - 1;
							}
						}
					}
					*/
				}
				break;
			}
		}
	}
}
LColour DocEditStyling::ColourFromType(DocType t)
{
	switch (t)
	{
		default:
			return LColour::Black;
		case CodePhp:
		case CodeJavascript:
			return ColourCode;
		case CodeCss:
			return ColourStyle;
		case CodePre:
			return ColourPre;
		case CodeComment:
			return ColourComment;
	}
}
void DocEditStyling::StyleHtml(StylingParams &p)
{
	char16 *Text = p.Text.AddressOf();
	char16 *e = Text + p.Text.Length();
	auto &Style = p.Styles;
	LString Ext = LGetExtension(p.FileName);
	DocType Type = CodeHtml;
	LTextView3::LStyle *Cur = NULL;
	#define START_CODE() \
		if (Type != CodeHtml) \
		{ \
			Cur = &Style.New().Construct(View, LTextView3::STYLE_IDE); \
			Cur->Start = s - Text; \
			Cur->Font = View->GetFont(); \
			Cur->Fore = ColourFromType(Type); \
		}
	#define END_CODE() \
		if (Cur) \
		{ \
			Cur->Len = (s - Text) - Cur->Start; \
			if (Cur->Len <= 0) \
				Style.Delete(Style.rbegin()); \
			Cur = NULL; \
		}
	char16 *s = Text;
	bool IsJavascript = Ext.Equals("js");
	bool IsPHP = Ext.Equals("php");
	bool IsCSS = Ext.Equals("css");
	if (IsJavascript)
	{
		Type = CodeJavascript;
		START_CODE();
	}
	else if (IsPHP)
	{
		Type = CodePhp;
		START_CODE();
	}
	else if (IsCSS)
	{
		Type = CodeCss;
		START_CODE();
	}
	for (s = Text; s < e; s++)
	{
		switch (*s)
		{
			case '`':
			{
				if (Type == CodeJavascript)
				{
					END_CODE();
					p.StyleString(s, e, ColourLiteral);
					s++;
					START_CODE();
					s--;
				}
				break;
			}
			case '\'':
			{
				if (s > Text &&
					IsAlpha(s[-1]))
					break;
				// else fall through
			}
			case '\"':
			{
				if (Type != CodeComment)
				{
					END_CODE();
					p.StyleString(s, e, ColourLiteral);
					s++;
					START_CODE();
					s--;
				}
				break;
			}
			case '/':
			{
				if (Type == CodeJavascript ||
					Type == CodePhp ||
					Type == CodeCss)
				{
					if (s[1] == '/')
					{
						END_CODE();
						char16 *nl = Strchr(s, '\n');
						if (!nl) nl = s + Strlen(s);
						
						auto &st = Style.New().Construct(View, LTextView3::STYLE_IDE);
						st.Start = s - Text;
						st.Font = View->GetFont();
						st.Fore = ColourComment;
						st.Len = nl - s;
						s = nl;
						START_CODE();
					}
					else if (s[1] == '*')
					{
						END_CODE();
						char16 *end_comment = Stristr(s, L"*/");
						if (!end_comment) end_comment = s + Strlen(s);
						else end_comment += 2;
						
						auto &st = Style.New().Construct(View, LTextView3::STYLE_IDE);
						st.Start = s - Text;
						st.Font = View->GetFont();
						st.Fore = ColourComment;
						st.Len = end_comment - s;
						s = end_comment;
						START_CODE();
					}
				}
				break;
			}
			case '<':
			{
				#define IS_TAG(name) \
					((tmp = strlen(#name)) && \
						len == tmp && \
						!Strnicmp(tag, L ## #name, tmp))
				#define SCAN_TAG() \
					char16 *tag = s + 1; \
					while (tag < e && strchr(WhiteSpace, *tag)) tag++; \
					char16 *c = tag; \
					while (c < e && (IsAlpha(*c) || strchr("!_/0123456789", *c))) c++; \
					size_t len = c - tag, tmp;
				if (Type == CodeHtml)
				{
					if (s[1] == '?' &&
						s[2] == 'p' &&
						s[3] == 'h' &&
						s[4] == 'p')
					{
						// Start PHP block
						Type = CodePhp;
						START_CODE();
						s += 4;
					}
					else if (	s[1] == '!' &&
								s[2] == '-' &&
								s[3] == '-')
					{
						// Start comment
						Type = CodeComment;
						START_CODE();
						s += 3;
					}
					else
					{
						// Html element
						SCAN_TAG();
						bool start = false;
						if (IS_TAG(pre))
						{
							Type = CodePre;
							while (*c && *c != '>') c++;
							if (*c) c++;
							start = true;
						}
						else if (IS_TAG(style))
						{
							Type = CodeCss;
							while (*c && *c != '>') c++;
							if (*c) c++;
							start = true;
						}
						else if (IS_TAG(script))
						{
							Type = CodeJavascript;
							while (c < e && *c != '>') c++;
							if (c < e) c++;
							start = true;
						}
						auto &st = Style.New().Construct(View, LTextView3::STYLE_IDE);
						st.Start = s - Text;
						st.Font = View->GetFont();
						st.Fore = ColourHtml;
						st.Len = c - s;
						s = c - 1;
						if (start)
						{
							s++;
							START_CODE();
							s--;
						}
					}
				}
				else if (Type == CodePre)
				{
					SCAN_TAG();
					if (IS_TAG(/pre))
					{
						END_CODE();
						Type = CodeHtml;
						s--;
					}
				}
				else if (Type == CodeCss)
				{
					SCAN_TAG();
					if (IS_TAG(/style))
					{
						END_CODE();
						Type = CodeHtml;
						s--;
					}
				}
				break;
			}
			case '>':
			{
				if (Type == CodeHtml)
				{
					auto &st = Style.New().Construct(View, LTextView3::STYLE_IDE);
					st.Start = s - Text;
					st.Font = View->GetFont();
					st.Fore = ColourHtml;
					st.Len = 1;
				}
				else if (Type == CodeComment)
				{
					if (s - 2 >= Text &&
						s[-1] == '-' &&
						s[-2] == '-')
					{
						s++;
						END_CODE();
						Type = CodeHtml;
					}
				}
				else if (Type == CodeJavascript)
				{
					// Check for </script>
					if (!Strnicmp(s - 8, L"</script>", 9))
					{
						s -= 8;
						END_CODE();
						auto &st = Style.New().Construct(View, LTextView3::STYLE_IDE);
						st.Start = s - Text;
						st.Font = View->GetFont();
						st.Fore = ColourHtml;
						st.Len = 9;
						
						s += 9;						
						Type = CodeHtml;
					}
				}
				break;
			}
			case '?':
			{
				if (Type == CodePhp &&
					s[1] == '>')
				{
					Type = CodeHtml;
					s += 2;
					END_CODE();
					s--;
				}
				break;
			}
		}
	}
	END_CODE();
}
	
void DocEditStyling::AddKeywords(const char **keys, bool IsType)
{
	for (const char **k = keys; *k; k++)
	{
		Node *n = &Root;
		for (const char *c = *k; *c && n; c++)
		{
			int idx = n->Map(*c);
			LAssert(idx >= 0);
			if (!n->Next[idx])
				n->Next[idx] = new Node;
			n = n->Next[idx];
		}
		if (n)
			n->Type = IsType ? KType : KLang;
	}
}
void DocEdit::PourStyle(size_t Start, ssize_t EditSize)
{
	if (FileType == SrcUnknown)
	{
		char *Ext = LGetExtension(Doc->GetFileName());
		if (!Ext)
			FileType = SrcPlainText;
		else if (!stricmp(Ext, "c") ||
				!stricmp(Ext, "cpp") ||
				!stricmp(Ext, "mm") ||
				!stricmp(Ext, "cc") ||
				!stricmp(Ext, "h") ||
				!stricmp(Ext, "hpp") ||
				!stricmp(Ext, "java") )
			FileType = SrcCpp;
		else if (!stricmp(Ext, "py"))
			FileType = SrcPython;
		else if (!stricmp(Ext, "xml"))
			FileType = SrcXml;
		else if (!stricmp(Ext, "html") ||
				!stricmp(Ext, "htm") ||
				!stricmp(Ext, "php") ||
				!stricmp(Ext, "js"))
			FileType = SrcHtml;
		else
			FileType = SrcPlainText;
		if (LangParam[FileType].Keywords)
			AddKeywords(LangParam[FileType].Keywords, false);
		if (LangParam[FileType].Types)
			AddKeywords(LangParam[FileType].Types, true);
		if (LangParam[FileType].Edges)
		{
			RefreshEdges = LangParam[FileType].Edges;
			RefreshSize = 0;
			for (const char **e = RefreshEdges; *e; e++)
				RefreshSize = MAX((int)strlen(*e), RefreshSize);
		}
	}
	// Get into the waiting mode (so the worker is not using 'StyleIn' data)
	if (WorkerState == KStyling)
	{
		#ifdef _DEBUG
		uint64 Start = LgiMicroTime();
		#endif
		ParentState = KCancel;
		while (WorkerState != KWaiting)
			LSleep(1);
		#ifdef _DEBUG
		uint64 Tm = LgiMicroTime() - Start;
		if (Tm >= 10000)
			LgiTrace("DocEdit: Styling->Waiting time: %.1g ms\n", (double) Tm / 1000.0);
		#endif
	}
	// Create the StyleIn array from the current document
	// Lock the object and give the text to the worker
	#if LOG_STYLE
	LgiTrace("DocEdit starting style...\n");
	#endif
	ParentState = KStyling;
	if (DocEditStyling::Lock(_FL))
	{
		Params.PourStart = Start;
		Params.PourSize = EditSize;
		Params.Dirty.Empty();
		Params.Text.Length(Size);
		Params.FileName = Doc->GetFileName();
		GetVisible(Params.Visible);
		memcpy(Params.Text.AddressOf(), Text, sizeof(*Text) * Size);
		DocEditStyling::Unlock();
		Event.Signal();
	}
}
int DocEdit::CountRefreshEdges(size_t At, ssize_t Len)
{
	if (!RefreshEdges)
		return 0;
	size_t s = MAX(0, At - (RefreshSize - 1));
	bool t[256] = {0};
	for (const char **Edge = RefreshEdges; *Edge; Edge++)
	{
		const char *e = *Edge;
		t[(int)e[0]] = true;
	}
	int Edges = 0;
	for (size_t i = s; i <= At; i++)
	{
		if (Text[i] < 256 && t[Text[i]])
		{
			for (const char **Edge = RefreshEdges; *Edge; Edge++)
			{
				auto n = i;
				const char *e;
				for (e = *Edge; *e; e++)
					if (Text[n++] != *e)
						break;
				if (!*e)
					Edges++;
			}
		}
	}
	return Edges;
}