/*
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 CamlIndenter.cpp

#include "platform.h"
#include "CamlIndenter.h"

using namespace std;

class token
{
public:
	int num;
	size_t offset;
	size_t length;
	int leading;
	int trailing;
	int newline;
	token(int n, size_t t, size_t l, int ld, int tr, int nl)
	{
		num = n;
		offset = t;
		length = l;
		leading = ld;
		trailing = tr;
		newline = nl;
	}
};
string convert(const wstring& str);
wstring doIndent(wstring* str, int count, int indent_after_in, int indent_filter);
static void match(int tk, int indent);
static void phrase(int i);
static void bindings(int i);
static void binding(int i);
static void expr(int i);
static void elseif(int i);
static void filter(int i);
static void exprsimple(int i);
static void dummy(int tk, int indent);
static void type_def(int i);
static void type_expr(int i);
static void label_decl(int i);
static void constr_decl(int i);
static void more_label_decl(int i);
static void more_constr_decl(int i);
static int wclex();
static void expr2(int indent);

static wstring tab;
static int la;
static wstring result;
static int err;
static int cnt;
static deque<token*> tokenArray;
static int tokenIndex;
static wstring lexbuf;

#define YYEOF 0

typedef struct yy_buffer_state *BUFFER_STATE;
extern BUFFER_STATE zz_scan_string (const char *);
extern size_t zzleng;
extern int zzlex();
extern char* zztext;
extern size_t zzleng;
int leading;
int trailing;
int newline;
char* token_start;
size_t token_length;

static int indentAfterIn;
static int indentFilter;

size_t currentPos;

wstring* scanString;

int wclex()
{
	token* tk = (token*)(tokenArray[tokenIndex++]);
	lexbuf = scanString->substr(tk->offset, tk->length);
	leading = tk->leading;
	trailing = tk->trailing;
	newline = tk->newline;
	return tk->num;
}
wstring doIndent(wstring* str, int count, int indent_after_in, int indent_filter)
{
	if (str->length() == 0) return *str;
	token_start = 0;
	token_length = 0;
	tokenIndex = 0;
	indentAfterIn = indent_after_in;
	indentFilter = indent_filter;
	la = -1;
	newline = 0;
	leading = 0;
	trailing = 0;
	err = 0;
	cnt = (count == 0 ? 1 : count);
	tab = (count == 0 ? L"\t": wstring(count, L' '));
	result = L"";
	lexbuf = L"";
    scanString = str;
    zz_scan_string(convert(*str).c_str());
	currentPos = 0;
	while (la != YYEOF)
	{
		newline = 0;
		la = zzlex();
		token* tk;
		if (la == STRING2 || la == COMMENT2)
		{
            
			tk = new token(la, currentPos - token_length, token_length, leading, trailing, newline);
		}
		else
		{
			tk = new token(la, currentPos - zzleng, zzleng, leading, trailing, newline);
		}
		tokenArray.push_back(tk);
	}
	la = wclex();
	while (la != ENDMARK2 && la != YYEOF)
	{
		phrase(0);
		if (la == ENDMARK2)
		{
			match(la, 0);
		}
	}
	if (la == ENDMARK2)
	{
		match(ENDMARK2, 0);
	}
	tokenArray.clear();
	return result;
}
void match(int t, int k)
{
	if (t != la)
	{
		err = 1;
	}

	while (newline > 1)
	{
		result += L"\n";
		newline--;
	}
	if (newline)
	{
		result += L"\n";
		for(int i = 0; i < k; i++) result += tab;
		newline--;
	}
	int tl = trailing;
	wchar_t c1 = lexbuf[0];
	wstring lexbuf1 = lexbuf;
	la = wclex();
	if (la == YYEOF)
	{
		result += lexbuf1;
		return;
	}
	wchar_t c2 = lexbuf[0];
	if (newline)
		result += lexbuf1;
	else if ((tl && leading) || (c1 == L'(' && c2 == L'*') || (c1 == L'*' && c2 == L')'))
	{
		result += (lexbuf1 + L" ");
	}
	else
	{
		result += lexbuf1;
	}

	if (la == COMMENT2) match(la, k);
}
void phrase(int indent)
{
	if (la == COMMENT2) match(la, indent);

	if (la == LET2)
	{
		match(LET2, indent);
		bindings(indent);
		if (la == IN2) {match(IN2, indent); expr(indent + indentAfterIn);}
	}
	else if (la == TYPE2)
	{
		match(TYPE2, indent);
		type_def(indent + 1);
		while (la == AND2) {match(la, indent + 1); type_def(indent + 1);}
	}
	else if (la == IGNORE2)
	{
		match(la, indent);
		while (la != ENDMARK2 && la != 0) match(la, indent);
	}
	else expr(indent);
	if (la != 0) match(ENDMARK2, indent);
}
void bindings(int indent)
{
	if (err) return;
	binding(indent);
	while (la == AND2)
	{
		match(AND2, indent);
		binding(indent);
	}
}
void binding(int indent)
{
	if (err) return;
	dummy(EQUAL2, indent);
	match(EQUAL2, indent + 1);
	expr(indent + 1);
}
void expr(int indent) 
{
	if (err) return;
	exprsimple(indent);
	while
		(
		la == IDEN2 || la == SEP2  || la == LET2 || la == IF2 || la == LEFTPAR2 || la == LEFTBRACE2 ||
		la == FUNCTION2 || la == FUN2 || la == PARSER2 || la == DOTPAR2 || la == BEGIN2 || la == MATCH2 || la == TRY2 ||
		la == FOR2 || la == WHILE2 || la == STRING2 || la == COMMENT2 || la == EQUAL2 || la == WHERE2 ||
		la == OTHER2 || la == OF2 || la == LSTREAM2 || la == EQUALEQUAL2 || la == RIGHTARROW2 || la == COLON2
		)
	{
		if (err) return;
		if (la == SEP2 || la == OTHER2 || la == IDEN2 || la == STRING2 || la == COMMENT2 || la == EQUAL2 ||
			la == OF2 || la == EQUALEQUAL2 || la == RIGHTARROW2 || la == COLON2)
		{
			match(la, indent);
		}
		exprsimple(indent);
	}
}
void expr2(int indent)
{
	if (err) return;
	exprsimple(indent);
	while
		(
		la == IDEN2  || la == LET2 || la == IF2 || la == LEFTPAR2 || la == LEFTBRACE2 ||
		la == FUNCTION2 || la == FUN2 || la == PARSER2 || la == DOTPAR2 || la == BEGIN2 || la == MATCH2 || la == TRY2 ||
		la == FOR2 || la == WHILE2 || la == STRING2 || la == COMMENT2 || la == EQUAL2 || la == WHERE2 ||
		la == OTHER2 || la == OF2 || la == LSTREAM2 || la == EQUALEQUAL2 || la == RIGHTARROW2 || la == COLON2
		)
	{
		if (err) return;
		if (la == OTHER2 || la == IDEN2 || la == STRING2 || la == COMMENT2 || la == EQUAL2
			|| la == OF2 || la == EQUALEQUAL2 || la == RIGHTARROW2 || la == COLON2)
		{
			match(la, indent);
		}
		exprsimple(indent);
	}
}
void exprsimple(int indent)
{
	if (err) return;
	switch(la)
	{
	case LET2:
		match(LET2, indent); bindings(indent); match(IN2, indent); expr(indent + indentAfterIn);
		break;
	case IF2:
		match(IF2, indent); dummy(THEN2, indent + 1); match(THEN2, indent); expr2(indent + 1); elseif(indent);
		break;
	case LEFTPAR2:
		match(la, indent); expr(indent + 1); match(RIGHTPAR2, indent);
		break;
	case LEFTBRACE2:
		match(la, indent); expr(indent + 1); match(RIGHTBRACE2, indent);
		break;
	case LSTREAM2:
		match(la, indent); expr(indent + 1); match(RSTREAM2, indent);
		break;
	case DOTPAR2:
		while (la == DOTPAR2) { match(DOTPAR2, indent); expr(indent); match(RIGHTPAR2, indent);}
		break;
	case BEGIN2:
		match(BEGIN2, indent); expr(indent + 1); match(END2, indent);
		break;
	case MATCH2:
		match(MATCH2, indent); expr(indent); match(WITH2, indent); filter(indent + indentFilter);
		break;
	case FUNCTION2: case FUN2: case PARSER2:
		match(la, indent); filter(indent + indentFilter);
		break;
	case TRY2:
		match(TRY2, indent); expr(indent + 1); match(WITH2, indent); if (la == BAR2) match(BAR2, indent); filter(indent + indentFilter);
		break;
	case FOR2:
		match(FOR2, indent); match(IDEN2, indent + 1); match(EQUAL2, indent + 1); dummy(TO2, indent + 1); match(TO2, indent);
		dummy( DO2, indent + 1); match(DO2, indent); expr(indent + 1); match(DONE2, indent);
		break;
	case WHILE2:
		match(WHILE2, indent); expr(indent + 1); match(DO2, indent); expr(indent + 1); match(DONE2, indent);
		break;
	case WHERE2:
		match(WHERE2,indent); bindings(indent);
	default:
		break;
	}
}
void dummy(int tk, int indent)
{
	if (err) return;
	int d = 0;
	while((la != tk || d != 0) && la != ENDMARK2 && la != 0)
	{
		if (tk == EQUAL2)
		{
			if (la == LEFTBRACE2) d++;
			else if (la == RIGHTBRACE2) d--;
		}
		match(la, indent);
	}
}
void elseif(int indent)
{
	if (err) return;
	if (la == ELSE2) {match(ELSE2, indent);if (la == IF2) expr(indent);else expr2(indent + 1);}
}
void filter(int indent)
{
	if (err) return;
	int d = 0;
	if (la == LSTREAM2) d++;
	while ((la != RIGHTARROW2 || d != 0) && la != ENDMARK2 && la != 0)
	{
		match(la, indent);
		if (la == LSTREAM2) d++;
		if (la == RSTREAM2) d--;
	}
	if (la == RIGHTARROW2)
	{
		match(RIGHTARROW2, indent);
		expr(indent + (cnt == 1 ? 3 : 2) * indentFilter + 1 - indentFilter);
	}
	if (la == BAR2) filter(indent);
}
void type_def(int i)
{
	if (err) return;
	while (la != EQUAL2 && la != EQUALEQUAL2 && la != ENDMARK2 && la != 0) match(la, i);
	if (la == EQUALEQUAL2) {type_expr(i);}
	else
	{
		match(la, i);
		if (la == OTHER2) match(la, i);
		if (la == LEFTBRACE2)
		{
			match(la, i);
			label_decl(i + 1);
			more_label_decl(i + 1);
			match(RIGHTBRACE2, i);
		}
		else if (la == IDEN2)
		{
			match(la, i);
			if (la == ENDMARK2) return;
			if (la == COLON2)
			{
				match(la, i);
				type_expr(i);
				more_label_decl(i);
			}
			else if (la == OF2)
			{
				match(la, i);
				type_expr(i);
				more_constr_decl(i);
			}
			else if (la == BAR2) more_constr_decl(i);
			else
			{
				dummy(AND2, i);
			}
		}
		else if (la == MUTABLE2)
		{
			match(la, i);
			match(IDEN2, i);
			match(COLON2, i);
			type_expr(i);
			more_label_decl(i);
		}
		else if (la == BAR2) more_constr_decl(i);
		else
		{
			err = 1;
		}
	}
}
void type_expr(int i)
{
	if (err) return;
	while (la != ENDMARK2 && la != AND2 && la != SEP2 && la != RIGHTBRACE2 && la != 0) match(la, i);
}
void label_decl(int i)
{
	if (err) return;
	if (la == IDEN2)
	{
		match(la, i);
		match(COLON2, i);
		type_expr(i);
	}
	else if (la == MUTABLE2)
	{
		match(MUTABLE2,i);
		match(IDEN2, i);
		match(COLON2, i);
		type_expr(i);
	}
}
void more_label_decl(int i)
{
	if (err) return;
	while (la == SEP2)
	{
		match(la, i);
		label_decl(i);
	}
}
void constr_decl(int i)
{
	if (err) return;
	match(IDEN2, i);
	if (la == OF2)
	{
		match(la, i);
		type_expr(i);
	}
}
void more_constr_decl(int i)
{
	if (err) return;
	while (la == BAR2)
	{
		match(la, i);
		constr_decl(i);
	}
}
