#include "Poliz.h"

#include <cstdlib>
#include <cstdio>

#define MAX_NUM_LENGTH 64

String IntToStr(int value)
{
	char buffer[MAX_NUM_LENGTH];
	sprintf(buffer, "%d", value);
	return buffer;
}

String DoubleToStr(double value)
{
	char buffer[MAX_NUM_LENGTH];
	sprintf(buffer, "%f", value);
	return buffer;
}

/*Script Functions*/
ScriptFunctions::ScriptFunctions()
{
	functions["Buy"] = &ScriptFunctions::buy;
	functions["Sell"] = &ScriptFunctions::sell;
	functions["Produce"] = &ScriptFunctions::prod;
	functions["Say"] = &ScriptFunctions::say;
}

void ScriptFunctions::Call(const String &name, const int count)
{
	FunctionsMap::Iterator i = functions.Find(name);
	if (i == functions.End()) throw NoSuchFunction();
	
	Function function = i->second;
	(this->*function)(count);
}

/*VarsTable*/

VarsTable::Variable::Variable()
{	
	this->name = "";
	this->address = 0;
	this->type = Variable::v_unknown;
	this->value = 0;
	this->isConst = false;
}

VarsTable::Variable::~Variable()
{
	smartDelete();
}

void VarsTable::Variable::smartDelete()
{
	if (!value) return;
	
	switch (GetType())
	{
		case v_int:
		{
			int *tmp = (int*)value; 
			delete tmp;  
			break;
		}
			
		case v_float: 
		{
			double *tmp = (double*)value;
			delete tmp;
			break;
		}
	}
	
	value = 0;
}

VarsTable::Variable& VarsTable::Variable::operator =(const Variable &variable)
{
	smartDelete();
	SetType(variable.GetType());
	
	switch (variable.GetType())
	{
		case v_int:		
			value = new int(*(int*)variable.GetValue());
			break;
					
		case v_float: 
			value = new double(*(double*)variable.GetValue());
			break;
			
		case v_array:
			value = variable.GetValue();
	}
	
	return *this;
}

void VarsTable::Variable::Print()
{
	switch (type)
	{
		case v_int:		
			printf("| (int) %d\n", *(int*)GetValue());
			break;
		
		case v_string:		
			printf("| (string) %s\n", (*(String*)GetValue()).CStr());
			break;		
		
		case v_array:
			printf("| (array)\n");
			break;
			
		default:
			printf("| (unknown)\n");
			break;
	}
}

VarsTable::VarsTable()
{
	lastID = 0;
	count = 0;
}

VarsTable::~VarsTable()
{
}

VarsTable::Variable* VarsTable::GetVariable(const String &name)
{
	NamesMap::Iterator i = names.Find(name);
	if (i != names.End()) return variables[i->second];
	
	Variable *var = Append();
	var->SetName(name);
	
	names[name] = var->GetAddress();
	return var;
}

VarsTable::Variable* VarsTable::GetVariable(const Address address)
{
	VarsMap::Iterator i = variables.Find(address);
	if (i != variables.End()) return i->second; else
		throw NoVarAtAddress();
}

VarsTable::Variable* VarsTable::Append()
{
	lastID++;
	count++;
	Variable *variable = new Variable;
	
	variable->SetAddress(lastID);
	
	variables[lastID] = variable;
	return variables[lastID];
}

VarsTable::Variable* VarsTable::Convert(Variable *var, Variable::Type toType)
{
	if (var->GetType() == toType) return var;
	
	Variable *tmp = Append();
	tmp->SetType(toType);
	
	switch (toType)
	{
		case Variable::v_int:
			switch (var->GetType())
			{
				case Variable::v_float: tmp->SetValue(new int(*((double*)var->GetValue()))); break;
				case Variable::v_string: tmp->SetValue(new int(atoi(((String*)var->GetValue())->CStr()))); break;
				default: throw InvalidConversion();
			}
			break;
		
		case Variable::v_float:
			switch (var->GetType())
			{
				case Variable::v_int: tmp->SetValue(new double(*((int*)var->GetValue()))); break;
				case Variable::v_string: tmp->SetValue(new double(atof(((String*)var->GetValue())->CStr()))); break;
				default: throw InvalidConversion();
			}
			break;
			
		case Variable::v_string:
			switch (var->GetType())
			{
				case Variable::v_int: tmp->SetValue(new String(IntToStr(*(int*)var->GetValue()))); break;
				case Variable::v_float: tmp->SetValue(new String(DoubleToStr(*(double*)var->GetValue()))); break;
				default: throw InvalidConversion();
			}
			break;
		
		default: throw InvalidConversion();
	}
	
	return tmp;
}

/*PolizSimple*/
void PolizSimple::Evaluate(PolizStack *stack, VarsTable *table,
	ScriptFunctions *functions, Position *position)
{
	EvaluateSimple(stack, table);
	(*position)++;
}

/*PolizVariable*/
void PolizVariable::EvaluateSimple(PolizStack *stack, VarsTable *table)
{
	VarsTable::Variable *var = table->GetVariable(name);
	stack->Push(var->GetAddress());
}

/*PolizConst*/
void PolizConst::EvaluateSimple(PolizStack *stack, VarsTable *table)
{
	stack->Push(GetAddress(table));
}

/*PolizInt*/
VarsTable::Address PolizInt::GetAddress(VarsTable *table)
{
	VarsTable::Variable *var = table->Append();
	var->SetIsConst();
	var->SetType(VarsTable::Variable::v_int);
	var->SetValue(new int(value));
	
	return var->GetAddress();
}

/*PolizFloat*/
VarsTable::Address PolizFloat::GetAddress(VarsTable *table)
{
	VarsTable::Variable *var = table->Append();
	var->SetIsConst();
	var->SetType(VarsTable::Variable::v_float);
	var->SetValue(new double(value));
	
	return var->GetAddress();
}

/*PolizString*/
VarsTable::Address PolizString::GetAddress(VarsTable *table)
{
	VarsTable::Variable *var = table->Append();
	var->SetIsConst();
	var->SetType(VarsTable::Variable::v_string);
	var->SetValue(new String(value));
	
	return var->GetAddress();
}

/*PolizBinary*/
void PolizBinary::EvaluateSimple(PolizStack *stack, VarsTable *table)
{
	VarsTable::Variable *var2 = table->GetVariable(stack->Pop());
	VarsTable::Variable *var1 = table->GetVariable(stack->Pop());
	
	var2 = table->Convert(var2, var1->GetType());
	
	stack->Push(EvaluateBinary(table, var1, var2));
}

/*PolizPlus*/
VarsTable::Address PolizPlus::EvaluateBinary(VarsTable *table,
	VarsTable::Variable *op1, VarsTable::Variable *op2)
{
	VarsTable::Variable *tmp = table->Append();
	tmp->SetType(op1->GetType());
	
	switch (op1->GetType())
	{
		case VarsTable::Variable::v_int:
		{
			tmp->SetValue(new int(*(int*)op1->GetValue() + *(int*)op2->GetValue()));
			break;
		}
		
		case VarsTable::Variable::v_float:
		{
			tmp->SetValue(new double(*(double*)op1->GetValue() + *(double*)op2->GetValue()));
			break;
		}
		
		case VarsTable::Variable::v_string:
		{
			tmp->SetValue(new String(*(String*)op1->GetValue() + *(String*)op2->GetValue()));
			break;
		}
		
		default: throw PlusException();
	}
	
	return tmp->GetAddress();
}

/*PolizMinus*/
VarsTable::Address PolizMinus::EvaluateBinary(VarsTable *table,
	VarsTable::Variable *op1, VarsTable::Variable *op2)
{
	VarsTable::Variable *tmp = table->Append();
	tmp->SetType(op1->GetType());
	
	switch (op1->GetType())
	{
		case VarsTable::Variable::v_int:
		{
			tmp->SetValue(new int(*(int*)op1->GetValue() - *(int*)op2->GetValue()));
			break;
		}
		
		case VarsTable::Variable::v_float:
		{
			tmp->SetValue(new double(*(double*)op1->GetValue() - *(double*)op2->GetValue()));
			break;
		}		
		
		default: throw MinusException();
	}
	
	return tmp->GetAddress();
}

/*PolizMul*/
VarsTable::Address PolizMul::EvaluateBinary(VarsTable *table,
	VarsTable::Variable *op1, VarsTable::Variable *op2)
{
	VarsTable::Variable *tmp = table->Append();
	tmp->SetType(op1->GetType());
	
	switch (op1->GetType())
	{
		case VarsTable::Variable::v_int:
		{
			tmp->SetValue(new int(*(int*)op1->GetValue() * *(int*)op2->GetValue()));
			break;
		}
		
		case VarsTable::Variable::v_float:
		{
			tmp->SetValue(new double(*(double*)op1->GetValue() * *(double*)op2->GetValue()));
			break;
		}		
		
		default: throw MulException();
	}
	
	return tmp->GetAddress();
}

/*PolizDiv*/
VarsTable::Address PolizDiv::EvaluateBinary(VarsTable *table,
	VarsTable::Variable *op1, VarsTable::Variable *op2)
{
	VarsTable::Variable *tmp = table->Append();
	tmp->SetType(op1->GetType());
	
	switch (op1->GetType())
	{
		case VarsTable::Variable::v_int:
		{
			tmp->SetValue(new int(*(int*)op1->GetValue() / *(int*)op2->GetValue()));
			break;
		}
		
		case VarsTable::Variable::v_float:
		{
			tmp->SetValue(new double(*(double*)op1->GetValue() / *(double*)op2->GetValue()));
			break;
		}		
		
		default: throw DivException();
	}
	
	return tmp->GetAddress();
}

/*PolizMod*/
VarsTable::Address PolizMod::EvaluateBinary(VarsTable *table,
	VarsTable::Variable *op1, VarsTable::Variable *op2)
{
	VarsTable::Variable *tmp = table->Append();
	tmp->SetType(op1->GetType());
	
	switch (op1->GetType())
	{
		case VarsTable::Variable::v_int:
		{
			tmp->SetValue(new int(*(int*)op1->GetValue() % *(int*)op2->GetValue()));
			break;
		}		
		
		default: throw ModException();
	}
	
	return tmp->GetAddress();
}

/*PolizOr*/
VarsTable::Address PolizOr::EvaluateBinary(VarsTable *table,
	VarsTable::Variable *op1, VarsTable::Variable *op2)
{
	VarsTable::Variable *tmp = table->Append();
	tmp->SetType(op1->GetType());
	
	switch (op1->GetType())
	{
		case VarsTable::Variable::v_int:
		{
			tmp->SetValue(new int(*(int*)op1->GetValue() || *(int*)op2->GetValue()));
			break;
		}		
		
		default: throw OrException();
	}
	
	return tmp->GetAddress();
}

/*PolizAnd*/
VarsTable::Address PolizAnd::EvaluateBinary(VarsTable *table,
	VarsTable::Variable *op1, VarsTable::Variable *op2)
{
	VarsTable::Variable *tmp = table->Append();
	tmp->SetType(op1->GetType());
	
	switch (op1->GetType())
	{
		case VarsTable::Variable::v_int:
		{
			tmp->SetValue(new int(*(int*)op1->GetValue() && *(int*)op2->GetValue()));
			break;
		}		
		
		default: throw AndException();
	}
	
	return tmp->GetAddress();
}

/*PolizLess*/
VarsTable::Address PolizLess::EvaluateBinary(VarsTable *table,
	VarsTable::Variable *op1, VarsTable::Variable *op2)
{
	VarsTable::Variable *tmp = table->Append();
	tmp->SetType(VarsTable::Variable::v_int);
	
	switch (op1->GetType())
	{
		case VarsTable::Variable::v_int:
		{
			tmp->SetValue(new int(*(int*)op1->GetValue() < *(int*)op2->GetValue()));
			break;
		}
		
		case VarsTable::Variable::v_float:
		{
			tmp->SetValue(new int(*(double*)op1->GetValue() < *(double*)op2->GetValue()));
			break;
		}
		
		case VarsTable::Variable::v_string:
		{
			tmp->SetValue(new int(*(String*)op1->GetValue() < *(String*)op2->GetValue()));
			break;
		}
		
		default: throw LessException();
	}
	
	return tmp->GetAddress();
}

/*PolizMore*/
VarsTable::Address PolizMore::EvaluateBinary(VarsTable *table,
	VarsTable::Variable *op1, VarsTable::Variable *op2)
{
	VarsTable::Variable *tmp = table->Append();
	tmp->SetType(VarsTable::Variable::v_int);
	
	switch (op1->GetType())
	{
		case VarsTable::Variable::v_int:
		{
			tmp->SetValue(new int(*(int*)op1->GetValue() > *(int*)op2->GetValue()));
			break;
		}
		
		case VarsTable::Variable::v_float:
		{
			tmp->SetValue(new int(*(double*)op1->GetValue() > *(double*)op2->GetValue()));
			break;
		}
		
		case VarsTable::Variable::v_string:
		{
			tmp->SetValue(new int(*(String*)op1->GetValue() > *(String*)op2->GetValue()));
			break;
		}
		
		default: throw MoreException();
	}
	
	return tmp->GetAddress();
}

/*PolizLessEq*/
VarsTable::Address PolizLessEq::EvaluateBinary(VarsTable *table,
	VarsTable::Variable *op1, VarsTable::Variable *op2)
{
	VarsTable::Variable *tmp = table->Append();
	tmp->SetType(VarsTable::Variable::v_int);
	
	switch (op1->GetType())
	{
		case VarsTable::Variable::v_int:
		{
			tmp->SetValue(new int(*(int*)op1->GetValue() <= *(int*)op2->GetValue()));
			break;
		}
		
		case VarsTable::Variable::v_float:
		{
			tmp->SetValue(new int(*(double*)op1->GetValue() <= *(double*)op2->GetValue()));
			break;
		}
		
		case VarsTable::Variable::v_string:
		{
			tmp->SetValue(new int(*(String*)op1->GetValue() <= *(String*)op2->GetValue()));
			break;
		}
		
		default: throw LessEqException();
	}
	
	return tmp->GetAddress();
}

/*PolizMoreEq*/
VarsTable::Address PolizMoreEq::EvaluateBinary(VarsTable *table,
	VarsTable::Variable *op1, VarsTable::Variable *op2)
{
	VarsTable::Variable *tmp = table->Append();
	tmp->SetType(VarsTable::Variable::v_int);
	
	switch (op1->GetType())
	{
		case VarsTable::Variable::v_int:
		{
			tmp->SetValue(new int(*(int*)op1->GetValue() >= *(int*)op2->GetValue()));
			break;
		}
		
		case VarsTable::Variable::v_float:
		{
			tmp->SetValue(new int(*(double*)op1->GetValue() >= *(double*)op2->GetValue()));
			break;
		}
		
		case VarsTable::Variable::v_string:
		{
			tmp->SetValue(new int(*(String*)op1->GetValue() >= *(String*)op2->GetValue()));
			break;
		}
		
		default: throw MoreEqException();
	}
	
	return tmp->GetAddress();
}

/*PolizEq*/
VarsTable::Address PolizEq::EvaluateBinary(VarsTable *table,
	VarsTable::Variable *op1, VarsTable::Variable *op2)
{
	VarsTable::Variable *tmp = table->Append();
	tmp->SetType(VarsTable::Variable::v_int);
	
	switch (op1->GetType())
	{
		case VarsTable::Variable::v_int:
		{
			tmp->SetValue(new int(*(int*)op1->GetValue() == *(int*)op2->GetValue()));
			break;
		}
		
		case VarsTable::Variable::v_float:
		{
			tmp->SetValue(new int(*(double*)op1->GetValue() == *(double*)op2->GetValue()));
			break;
		}
		
		case VarsTable::Variable::v_string:
		{
			tmp->SetValue(new int(*(String*)op1->GetValue() == *(String*)op2->GetValue()));
			break;
		}
		
		default: throw EqException();
	}
	
	return tmp->GetAddress();
}

/*PolizNotEq*/
VarsTable::Address PolizNotEq::EvaluateBinary(VarsTable *table,
	VarsTable::Variable *op1, VarsTable::Variable *op2)
{
	VarsTable::Variable *tmp = table->Append();
	tmp->SetType(VarsTable::Variable::v_int);
	
	switch (op1->GetType())
	{
		case VarsTable::Variable::v_int:
		{
			tmp->SetValue(new int(*(int*)op1->GetValue() != *(int*)op2->GetValue()));
			break;
		}
		
		case VarsTable::Variable::v_float:
		{
			tmp->SetValue(new int(*(double*)op1->GetValue() != *(double*)op2->GetValue()));
			break;
		}
		
		case VarsTable::Variable::v_string:
		{
			tmp->SetValue(new int(*(String*)op1->GetValue() != *(String*)op2->GetValue()));
			break;
		}
		
		default: throw NotEqException();
	}
	
	return tmp->GetAddress();
}

/*PolizAssign*/
void PolizAssign::EvaluateSimple(PolizStack *stack, VarsTable *table)
{
	VarsTable::Variable *what = table->GetVariable(stack->Pop());
	VarsTable::Variable *to = table->GetVariable(stack->Pop());
	
	if (to->GetIsConst() || what->GetType() == VarsTable::Variable::v_unknown)
		throw AssignException();
	
	*to = *what;
}

/*PolizUnar*/
void PolizUnar::EvaluateSimple(PolizStack *stack, VarsTable *table)
{
	VarsTable::Variable *op = table->GetVariable(stack->Pop());
	
	VarsTable::Variable *tmp = table->Append();
	tmp->SetType(op->GetType());
	
	switch (op->GetType())
	{
		case VarsTable::Variable::v_int:
		{
			tmp->SetValue(new int(-*(int*)op->GetValue()));
			break;
		}
		
		case VarsTable::Variable::v_float:
		{
			tmp->SetValue(new double(-*(double*)op->GetValue()));
			break;
		}		
		
		default: throw UnarException();
	}
}

/*PolizUnar*/
void PolizNot::EvaluateSimple(PolizStack *stack, VarsTable *table)
{
	VarsTable::Variable *op = table->GetVariable(stack->Pop());
	
	VarsTable::Variable *tmp = table->Append();
	tmp->SetType(op->GetType());
	
	switch (op->GetType())
	{
		case VarsTable::Variable::v_int:
		{
			tmp->SetValue(new int(! *(int*)op->GetValue()));
			break;
		}		
		
		default: throw NotException();
	}
}

/*PolizFunction*/
void PolizFunction::Evaluate(PolizStack *stack, VarsTable *table,
	ScriptFunctions *functions, Position *position)
{
	VarsTable::Variable *count = table->GetVariable(stack->Pop());
	if (count->GetType() != VarsTable::Variable::v_int)
		throw FuncException();
	
	functions->Call(name, *(int*)count->GetValue());
	
	(*position)++;
}

/*PolizArray*/
void PolizArray::EvaluateSimple(PolizStack *stack, VarsTable *table)
{
	VarsTable::Variable *var = table->GetVariable(stack->Pop());
	for (unsigned i = 0; i < count; i++)
	{
		VarsTable::Variable *iVar = table->Convert(table->GetVariable(stack->Pop()),
			VarsTable::Variable::v_string);
		if (var->GetType() != VarsTable::Variable::v_array)
		{
			var->SetValue(new Array);
			var->SetType(VarsTable::Variable::v_array);
		}
		
		Array *array = (Array*)var->GetValue();
		String *index = (String*)iVar->GetValue();
		VarsTable::Variable *tmp;
				
		if (array->Find(*index) == array->End())
		{
			tmp = table->Append();
			tmp->SetType(VarsTable::Variable::v_unknown);
			(*array)[*index] = tmp->GetAddress();
		} else tmp = table->GetVariable((*array)[*index]);
		
		var = tmp;
	}
	
	stack->Push(var->GetAddress());
}
