/***************************************************************************

  gbc_preprocess.c

  (c) 2000-2017 Benoît Minisini <g4mba5@gmail.com>

  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 2, 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, write to the Free Software
  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
  MA 02110-1301, USA.

***************************************************************************/

#define __GBC_PREPROCESS_C

#include "gb_common.h"
#include "gb_common_case.h"
#include "gb_error.h"
#include "gb_table.h"

#include "gbc_compile.h"
#include "gbc_read.h"
#include "gbc_trans.h"
#include "gbc_preprocess.h"

typedef
	struct {
		bool ignore;
		int ignore_level;
	}
	PREP_STATE;

#define MAX_LEVEL 16

int PREP_next_line;
	
static PREP_STATE _stack[MAX_LEVEL];

static int _level;
static bool _ignore;
static int _ignore_level;
static PATTERN *_current;


static int get_expression(void);

static bool is_current(int res)
{
	if (PATTERN_is(*_current, res))
	{
		_current++;
		return TRUE;
	}
	else
		return FALSE;
}

static bool compare_value(const char *value)
{
	PATTERN op;
	SYMBOL *sym;
	char version[8];
	int n, major, minor;
	int diff;
	
	op = *_current++;
	if (!PATTERN_is_reserved(op))
		THROW("Missing operator");
	
	if (!PATTERN_is_string(*_current))
		THROW("String expected");
	
	sym = TABLE_get_symbol(JOB->class->string, PATTERN_index(*_current));
	_current++;
	
	if (value)
	{
		if (strlen(value) != sym->len)
			diff = 1;
		else
			diff = strncasecmp(value, sym->name, sym->len);
		
		switch (PATTERN_index(op))
		{
			case RS_EQUAL: return diff == 0;
			case RS_NE: return diff != 0;
			default: THROW("Equality or inequality operator expected");
		}
	}
	else
	{
		if (sym->len < 1 || sym->len >= sizeof(version))
			THROW("Bad version string");
		
		memcpy(version, sym->name, sym->len);
		version[sym->len] = 0;
		n = sscanf(version, "%d.%d", &major, &minor);
		if (n == 0)
			THROW("Bad version string");
		else if (n == 1)
			minor = 0;
		
		diff = GAMBAS_VERSION - major;
		if (!diff) 
			diff = GAMBAS_MINOR_VERSION - minor;
		
		switch (PATTERN_index(op))
		{
			case RS_EQUAL: return diff == 0;
			case RS_NE: return diff != 0;
			case RS_GT: return diff > 0;
			case RS_LT: return diff < 0;
			case RS_GE: return diff >= 0;
			case RS_LE: return diff <= 0;
			default: THROW("Comparison operator expected");
		}
	}
}

#define compare_symbol(_symbol, _name, _len) ((strlen(_symbol) == _len) && !strncasecmp((_symbol), (_name), (_len)))

static int get_symbol(const char *name, int len)
{
	if (compare_symbol("system", name, len))
		return compare_value(SYSTEM);

	if (compare_symbol("arch", name, len) || compare_symbol("architecture", name, len))
		return compare_value(ARCHITECTURE);

	if (compare_symbol("version", name, len) || compare_symbol("gambas", name, len))
		return compare_value(NULL);
	
	/*if (compare_symbol("debug", name, len))
		return JOB->debug;*/
	
	/*if (compare_symbol("true", name, len))
		return TRUE;

	if (compare_symbol("false", name, len))
		return FALSE;*/

	return FALSE;
}

static int get_value(void)
{
	int value;
	SYMBOL *sym;
	
	if (is_current(RS_LBRA))
	{
		value = get_expression();
		if (!is_current(RS_RBRA))
			THROW("Missing right brace");
		return value;
	}
	
	if (is_current(RS_NOT))
		return !get_value();
	
	if (PATTERN_is_identifier(*_current))
	{
		sym = TABLE_get_symbol(JOB->class->table, PATTERN_index(*_current));
		_current++;
		value = get_symbol(sym->name, sym->len);
		return value;
	}
	else if (is_current(RS_FALSE))
		return 0;
	else if (is_current(RS_TRUE))
		return 1;
	else if (is_current(RS_DEBUG))
		return JOB->debug;
	else if (is_current(RS_EXEC))
		return JOB->exec;
	
	return 0;
}

static int get_expression(void)
{
	int value;
	
	for(;;)
	{
		value = get_value();
	
		if (is_current(RS_AND))
		{
			if (!value)
				return FALSE;
			
			continue;
		}
		
		if (is_current(RS_OR))
		{
			if (value)
				return TRUE;
			
			continue;
		}
		
		if (PATTERN_is_newline(*_current) || PATTERN_is(*_current, RS_RBRA))
			return value;
		
		THROW(E_SYNTAX);
	}
}

void PREP_init(void)
{
	_level = 0;
	_ignore = FALSE;
	_current = NULL;
}

void PREP_exit(void)
{
	if (_level)
		THROW("Missing #Endif");
}

int PREP_analyze(PATTERN *line)
{
	bool test;
	
	if (PATTERN_is(*line, RS_P_IF))
	{
		if (_level >= MAX_LEVEL)
			THROW("Too many imbricated #If...#Endif");
		
		_stack[_level].ignore = _ignore;
		_stack[_level].ignore_level = _ignore_level;
		
		line++;
		_level++;
		
		if (!_ignore)
		{
			_current = line;
			if (!get_expression())
			{
				_ignore = TRUE;
				_ignore_level = _level;
			}
		}
	}
	else if (PATTERN_is(*line, RS_P_ELSE))
	{
		//bool else_if = FALSE;
		
		if (!_level)
			THROW_UNEXPECTED(line);
		
		test = TRUE;
		line++;
		
		if (!_ignore)
		{
			_ignore = TRUE;
			_ignore_level = _level - 1; 
		}
		else
		{
			if (_level == _ignore_level)
			{
				if (PATTERN_is(*line, RS_IF))
				{
					line++;
					
					_current = line;
					test = get_expression();
					
					//else_if = TRUE;
				}
			}
			
			if (_level == _ignore_level)
				_ignore = !test;
		}
	}
	else if (PATTERN_is(*line, RS_P_ENDIF))
	{
		if (!_level)
			THROW_UNEXPECTED(line);
		
		_level--;
		_ignore = _stack[_level].ignore;
		_ignore_level = _stack[_level].ignore_level;
	}
	else if (PATTERN_is(*line, RS_P_LINE))
	{
		TRANS_NUMBER result;
		
		line++;
		if (!PATTERN_is_number(*line))
			THROW_UNEXPECTED(line);
		
		TRANS_get_number(PATTERN_index(*line), &result);
		if (result.type != T_INTEGER)
			THROW_UNEXPECTED(line);
		
		PREP_next_line = result.ival - 1;
		return PREP_LINE;
	}
	else if (PATTERN_is(*line, RS_P_CONST))
	{
		// TODO
	}
	else
		THROW(E_SYNTAX);
	
	return _ignore ? PREP_IGNORE : PREP_CONTINUE;
}
