/*
 Program WinCaml: Graphical User Interface
 for interactive use of Caml-Light and Ocaml.
 Copyright (C) 2005-2015 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 CHighlighter.cpp

#include "CChildFrame.h"
#include "CHighlighter.h"
#include "HighlightLexer.h"
#include "CInput.h"
#include "CUndoManager.h"

typedef struct yy_buffer_state *BUFFER_STATE;
string convert(const wstring& str);
extern BUFFER_STATE yy_scan_string(const char*);
extern int yylex();
void doSleep(int milliseconds);

int stringToken[] = {STRING, 0};
int operatorToken[] = {OPERATOR, 0};
int commentToken[] = {LCOMMENT, RCOMMENT, 0};
int numberToken[] = {INTEGER, FLOAT1, CHAR1, 0};
int keywordToken[] = {KEYWORD, DO, DONE, BEGIN1, END, LET, IN1, TRY, WITH, IF, ELSE, THEN, 0};
int delimiterToken[] = {KEYSYM, LPAR, RPAR, LBRACKET, RBRACKET, LBRACE, RBRACE, LLIST, RLIST, LSTREAM, RSTREAM, LOBJECT, ROBJECT, 0};
int otherToken[] = {UNKNOWN, WHITESPACE, IDENTIFIER, BACKSLASH, 0};

const int COLORCOUNT = 10;
const int otherColor = 0;
const int commentColor = 1;
const int stringColor = 2;
const int numberColor = 3;
const int keywordColor = 4;
const int delimiterColor = 5;
const int operatorColor = 6;
const int inputBackground = 7;
const int bufferBackground = 8;
const int matchingBackground = 9;


CHighlighter::CHighlighter(CInput* cInput)
{
	textEdit = cInput;
	p1 = p2 = (size_t)-1;
	textChanged = false;
	tokenColor = new int[TOKENMAX];
	tokenColor[0] = 0;
	tokenColor[STRING] = stringColor;
	tokenColor[OPERATOR] = operatorColor;
	int i;
	for (i = 0; commentToken[i] != 0; i++) tokenColor[commentToken[i]] = commentColor;
	for (i = 0; numberToken[i] != 0; i++) tokenColor[numberToken[i]] = numberColor;
	for (i = 0; keywordToken[i] != 0; i++) tokenColor[keywordToken[i]] = keywordColor;
	for (i = 0; delimiterToken[i] != 0; i++) tokenColor[delimiterToken[i]] = delimiterColor;
	for (i = 0; otherToken[i] != 0; i++) tokenColor[otherToken[i]] = otherColor;
	tokenArray = new deque<tokenClass*>;
	shouldBlink = false;
	blinking = false;
	toMatch = 0;
}
void CHighlighter::tokenize(size_t firstPos, size_t lastPos, size_t d, bool negative)

{
	if (textEdit->getTextLength() == 0)
	{
		tokenArray->clear();
		tkMin = 0;
		tkMax = 0;
		return;
	}
	size_t sz = tokenArray->size();
    
	if (negative)
		for (size_t m = 0; m < sz; m++)
		{
			tokenClass* tok = (*tokenArray)[m];
			if (tok->tokenBegin >= lastPos)
			{
				tok->tokenBegin -= d;
				tok->tokenEnd -= d;
			}
		}
	else
		for (size_t m = 0; m < sz; m++)
		{
			tokenClass* tok = (*tokenArray)[m];
			if (tok->tokenBegin >= lastPos)
			{
				tok->tokenBegin += d;
				tok->tokenEnd += d;
			}
		}
    
    size_t i = 0;
    for (size_t m = 0;  m < sz; m++)
    {
        tokenClass* tok = tokenArray->at(m);
        if (tok->tokenEnd < firstPos) i++;
        else
            break;
    }
    while (i > 0 && i < sz && (*tokenArray)[i]->tokenType != WHITESPACE) i--;
    size_t firstIndex = i > 0 ? (*tokenArray)[i - 1]->tokenEnd : 0;
    string s = convert(textEdit->getText());
    yy_scan_string((char*)s.substr(firstIndex).c_str());
    
    token_begin = 0;
    token_end = 0;
    deque<tokenClass*> v;
    size_t k = sz;
    
    int type = yylex();
    while (type)
    {
        tokenClass* token = new tokenClass(type, token_begin + firstIndex, token_end + firstIndex);
        bool b = false;
        if (token->tokenBegin >= lastPos)
            for (k = i; k < sz; k++)
            {
                tokenClass* tok = (*tokenArray)[k];
                if (token->tokenBegin == tok->tokenBegin) {b = true; break;}
            }
        if (b) break;
        v.push_back(token);
        type = yylex();
    }
    size_t vsz = v.size();
    if (vsz)
    {
        if ( i < sz)
        {
            tokenArray->erase(tokenArray->begin() + i, tokenArray->begin() + k);
            tokenArray->insert(tokenArray->begin() + i, v.begin(), v.end());
        }
        else
        {
            tokenArray->insert(tokenArray->end(),v.begin(), v.end());
        }
    }
    sz = tokenArray->size();
    if (sz == 0)
    {
        tkMin = 0;
        tkMax = 0;
        return;
    }
    size_t n = i;
    bool b = false;
    while ( n < sz  && (n < i + vsz || b == false))
    {
        b = setTokenState(n);
        n++;
    }
    
    size_t p;
    for (p = i; p < n; p++)
    {
        tokenClass* token = (*tokenArray)[p];
        if (token->tokenEnd > location  && token->tokenBegin <= location) break;
    }
    if (p == n && p != 0) p--;
    tokenClass* token = (*tokenArray)[p];
    int t = token->tokenType;
    if (((t == RPAR || t == RBRACE || t == RBRACKET || t == RLIST || t == RSTREAM || t == ROBJECT || t == DONE
          || t == END || t == IN1 || t == WITH || t == ELSE) && token->depth == 0 && token->str == 0) || t == RCOMMENT)
    {
        if (!(token->tokenEnd == location || negative))
        {
            shouldBlink = textEdit->undoManager->getType() == UR_TYPING;
            toMatch = p;
        }
    }
    tkMin = i;
    tkMax = n;
    update();
    textChanged = true;
}
size_t CHighlighter::matching(size_t n)
{
	tokenClass* tk = (*tokenArray)[n];
	int type = tk->tokenType;
	int d = -1;
	int dp1 = 0;
	int dp2 = 0;
	int db = 0;
	int de = 0;
	if (type == ELSE)
	{
		while (d != 0 && n > 0)
		{
			tk = (*tokenArray)[--n];
			if (tk->depth == 0 && tk->str == 0)
			{
				if (tk->tokenType == RPAR) dp2++;
				else if (tk->tokenType == LPAR) dp1++;
				else if (tk->tokenType == END) de++;
				else if (tk->tokenType == BEGIN1) db++;
				else if (dp1 == dp2 && db == de) {if (tk->tokenType == type - 1) d++; else if (tk->tokenType == type) d--;}
			}
		}
	}
	else
	{
		while (d != 0 && n > 0)
		{
			tk = (*tokenArray)[--n];
			if ((tk->depth == 0 || type == RCOMMENT) && tk->str == 0)
			{
				if (tk->tokenType == type - 1) d++;
				else if  (tk->tokenType == type) d--;
			}
		}
	}
	return d != 0 ? (size_t)-1 : n;
}
void CHighlighter::doBlink(size_t tokenIndex)
{
    
	if (tokenIndex == (size_t)-1) return;
	tokenClass* tk = (*tokenArray)[tokenIndex];
	textEdit->setTextBackground(tk->tokenBegin, tk->tokenEnd - tk->tokenBegin, textEdit->T[matchingBackground]);
}
bool CHighlighter::setTokenState(size_t i){
	int depth = i > 0 ? (*tokenArray)[i - 1]->depth : 0;
	int str = i > 0 ? (*tokenArray)[i - 1]->str : 0;
	tokenClass* token = (*tokenArray)[i];
	switch(token->tokenType)
	{
        case STRING:
            str = 1 - str;
            break;
        case LCOMMENT:
            if (str == 0) depth++;
            break;
        case RCOMMENT:
            if (str == 0 && depth > 0) depth--;
            break;
        default:
            break;
	}
	if (str == token->str && depth == token->depth) {return true;}
	token->str = str;
	token->depth = depth;
	token->needsRefresh = true;
	return false;
}
void CHighlighter::update()
{
	if (tokenArray->size() == 0) return;
    
	size_t k = tkMin;
	size_t tokenCount = tokenArray->size();
	size_t max = tkMax >= tokenCount - 1 ? tokenCount : tkMax;
	while (k < max)
	{
		tokenClass* token = (*tokenArray)[k];
		while (k < max && (token->depth > 0 || (token->tokenType == RCOMMENT && token->str == 0)))
		{
			token->needsRefresh = true;
			token->color = commentColor;
			k++;
			if (k < max) token = (*tokenArray)[k];
            
		}
		while (k < max && (token->str == 1 || token->tokenType == STRING))
		{
			token->needsRefresh = true;
			token->color = stringColor;
			k++;
			if (k < max) token = (*tokenArray)[k];
            
		}
		if (k < max)
		{
			token->needsRefresh = true;
			int type = token->tokenType;
			token->color = tokenColor[type];
			k++;
		}
	}
}
void CHighlighter::updateColors()
{
	size_t sz = tokenArray->size();
	for(size_t k = 0; k < sz; k++)
	{
		tokenClass* token = (*tokenArray)[k];
		token->needsRefresh = true;
	}
}
void CHighlighter::updateColor(int tokenType[])
{
	int l = 0;
	while (tokenType[l] != 0)
	{
		l++;
	}
	size_t max = tokenArray->size();
	if (max == 0) return;
    
	size_t k = 0;
	while (k < max)
	{
		int tp = (*tokenArray)[k]->tokenType;
		for (int i = 0; i < l && tokenType[i] != tp; i++)
		{
			if (i == l) k++;
		}
		if (k < max)
		{
			tokenClass* token = (*tokenArray)[k];
			size_t k1 = k;
			while (k < max && (token->depth > 0 || (token->tokenType == RCOMMENT && token->str == 0)))
			{
				token = (*tokenArray)[k];
				token->needsRefresh = true;
				token->color = commentColor;
				k++;
			}
			if (k > k1) k--;
		}
		if (k < max)
		{
			tokenClass* token = (*tokenArray)[k];
			size_t k1 = k;
			while (k < max && (token->str == 1 || token->tokenType == STRING))
			{
				token = (*tokenArray)[k];
				token->needsRefresh = true;
				token->color = stringColor;
				k++;
			}
			if (k > k1) k--;
		}
		if (k < max)
		{
			tokenClass* token = (*tokenArray)[k];
			int type = token->tokenType;
			token->needsRefresh = true;
			token->color = tokenColor[type];
			k++;
		}
	}
}
size_t CHighlighter::tokenIndexFromCharIndex(size_t charIndex)
{
	if (tokenArray->size() == 0) return (size_t)-1;
	size_t i = 0;
	size_t j = tokenArray->size() - 1; // size of tokenArray must be at least 1
	size_t d = j;
	while (d > 0)
	{
		size_t s = i + j;
		if ((*tokenArray)[i]->tokenEnd <= charIndex)
		{
			size_t k;
			if (s % 2 == 0) k = s / 2;
			else k = (s + 1) / 2;
			if ((*tokenArray)[k]->tokenBegin > charIndex)
				j = k;
			else
				i = k;
		}
		else if ((*tokenArray)[j]->tokenBegin > charIndex)
		{
			size_t k;
			if (s % 2 == 0) k = s / 2;
			else k = (s - 1) / 2;
			if ((*tokenArray)[k]->tokenEnd <= charIndex)
				i = k;
			else
				j = k;
		}
		d = j - i;
	}
	return i;
}
void CHighlighter::colorize()
{
	if (shouldBlink)
	{
		doBlink(matching(toMatch));
		shouldBlink = false;
		blinking = true;
		textEdit->updateView();
		return;
	}
    
	if (blinking)
	{
		blinking = false;
		doSleep(300);
	}
    size_t r1, r2;
    size_t q1 = textEdit->visibleTextOffset();
    size_t q2 = textEdit->visibleTextEnd();
    
    size_t cmdStart = textEdit->commandStart;
    size_t cmdEnd = textEdit->commandEnd;
    textEdit->commandRange();
    
    if (cmdStart != textEdit->commandStart || cmdEnd != textEdit->commandEnd || p1 != q1 || p2 != q2 || textChanged)
    {
        textEdit->setTextDefaultBackground(0, textEdit->getTextLength());
        unsigned long c = textEdit->inBuffer ? textEdit->cChildFrame->InputBufferBackground : textEdit->cChildFrame->InputCommandBackground;
        textEdit->setTextBackground(textEdit->commandStart, textEdit->commandEnd - textEdit->commandStart, c);
        textEdit->updateView();
    }
    if (q1 == p1 && q2 == p2 && !textChanged)
    {
        return;
    }
    p1 = q1; p2 = q2;
    r1 = tokenIndexFromCharIndex(p1);
    r2 = tokenIndexFromCharIndex(p2);
	textChanged = false;
	if (r1 == (size_t)-1) return;
	textEdit->suspendLayout();
	for (size_t i = r1; i <= r2; i++)
	{
        tokenClass* tok = (*tokenArray)[i];
        size_t a = tok->tokenBegin;
        size_t l = tok->tokenEnd - a;
        if (tok->needsRefresh)
        {
            textEdit->setTextColor(a, l, textEdit->T[tok->color]);
            tok->needsRefresh = false;
        }
	}
	textEdit->resumeLayout();
	textEdit->updateView();
}
void CHighlighter::colorizeAll()
{
	textEdit->suspendLayout();
	size_t sz = tokenArray->size();
	for(size_t i = 0; i < sz; i++)
	{
		tokenClass* tok = (*tokenArray)[i];
		if (tok->needsRefresh)
		{
			size_t a = tok->tokenBegin;
			size_t l = tok->tokenEnd - a;
			textEdit->setTextColor(a, l, textEdit->T[tok->color]);
			tok->needsRefresh = false;
		}
	}
	textEdit->resumeLayout();
}
void CHighlighter::invalidate(size_t startPos, size_t endPos)
{
	size_t sz = tokenArray->size();
	for (size_t i = 0; i < sz; i++)
	{
		tokenClass* tok = (*tokenArray)[i];
		if (tok->tokenBegin >= startPos && tok->tokenEnd <= endPos)
		{
			tok->needsRefresh = true;
		}
	}
}
void CHighlighter::invalidate()
{
	size_t sz = tokenArray->size();
	for (size_t i = 0; i < sz; i++)
	{
		(*tokenArray)[i]->needsRefresh = true;
	}
}
