/*
	This file is part of cpp-ethereum.
	cpp-ethereum 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.
	cpp-ethereum 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 cpp-ethereum.  If not, see .
*/
/**
 * @author Christian 
 * @date 2015
 * Component that translates Solidity code into the why3 programming language.
 */
#include 
#include 
using namespace std;
using namespace dev;
using namespace dev::solidity;
bool Why3Translator::process(SourceUnit const& _source)
{
	try
	{
		if (m_lines.size() != 1 || !m_lines.back().contents.empty())
			fatalError(_source, "Multiple source units not yet supported");
		appendPreface();
		_source.accept(*this);
	}
	catch (NoFormalType&)
	{
		solAssert(false, "There is a call to toFormalType() that does not catch NoFormalType exceptions.");
	}
	catch (FatalError& /*_e*/)
	{
		solAssert(m_errorOccured, "");
	}
	return !m_errorOccured;
}
string Why3Translator::translation() const
{
	string result;
	for (auto const& line: m_lines)
		result += string(line.indentation, '\t') + line.contents + "\n";
	return result;
}
void Why3Translator::error(ASTNode const& _node, string const& _description)
{
	auto err = make_shared(Error::Type::Why3TranslatorError);
	*err <<
		errinfo_sourceLocation(_node.location()) <<
		errinfo_comment(_description);
	m_errors.push_back(err);
	m_errorOccured = true;
}
void Why3Translator::fatalError(ASTNode const& _node, string const& _description)
{
	error(_node, _description);
	BOOST_THROW_EXCEPTION(FatalError());
}
string Why3Translator::toFormalType(Type const& _type) const
{
	if (_type.category() == Type::Category::Bool)
		return "bool";
	else if (auto type = dynamic_cast(&_type))
	{
		if (!type->isAddress() && !type->isSigned() && type->numBits() == 256)
			return "uint256";
	}
	else if (auto type = dynamic_cast(&_type))
	{
		if (!type->isByteArray() && type->isDynamicallySized() && type->dataStoredIn(DataLocation::Memory))
		{
			// Not catching NoFormalType exception.  Let the caller deal with it.
			string base = toFormalType(*type->baseType());
			return "array " + base;
		}
	}
	else if (auto mappingType = dynamic_cast(&_type))
	{
		solAssert(mappingType->keyType(), "A mappingType misses a keyType.");
		if (dynamic_cast(&*mappingType->keyType()))
		{
			//@TODO Use the information from the key type and specify the length of the array as an invariant.
			// Also the constructor need to specify the length of the array.
			solAssert(mappingType->valueType(), "A mappingType misses a valueType.");
			// Not catching NoFormalType exception.  Let the caller deal with it.
			string valueTypeFormal = toFormalType(*mappingType->valueType());
			return "array " + valueTypeFormal;
		}
	}
	BOOST_THROW_EXCEPTION(NoFormalType()
		<< errinfo_noFormalTypeFrom(_type.toString(true)));
}
void Why3Translator::addLine(string const& _line)
{
	newLine();
	add(_line);
	newLine();
}
void Why3Translator::add(string const& _str)
{
	m_lines.back().contents += _str;
}
void Why3Translator::newLine()
{
	if (!m_lines.back().contents.empty())
		m_lines.push_back({"", m_lines.back().indentation});
}
void Why3Translator::unindent()
{
	newLine();
	solAssert(m_lines.back().indentation > 0, "");
	m_lines.back().indentation--;
}
bool Why3Translator::visit(ContractDefinition const& _contract)
{
	if (m_seenContract)
		error(_contract, "More than one contract not supported.");
	m_seenContract = true;
	m_currentContract.contract = &_contract;
	if (_contract.isLibrary())
		error(_contract, "Libraries not supported.");
	addLine("module Contract_" + _contract.name());
	indent();
	addLine("use import int.Int");
	addLine("use import ref.Ref");
	addLine("use import map.Map");
	addLine("use import array.Array");
	addLine("use import int.ComputerDivision");
	addLine("use import mach.int.Unsigned");
	addLine("use import UInt256");
	addLine("exception Revert");
	addLine("exception Return");
	if (_contract.stateVariables().empty())
		addLine("type state = ()");
	else
	{
		addLine("type state = {");
		indent();
		m_currentContract.stateVariables = _contract.stateVariables();
		for (VariableDeclaration const* variable: m_currentContract.stateVariables)
		{
			string varType;
			try
			{
				varType = toFormalType(*variable->annotation().type);
			}
			catch (NoFormalType &err)
			{
				string const* typeNamePtr = boost::get_error_info(err);
				string typeName = typeNamePtr ? " \"" + *typeNamePtr + "\"" : "";
				fatalError(*variable, "Type" + typeName + " not supported for state variable.");
			}
			addLine("mutable _" + variable->name() + ": " + varType);
		}
		unindent();
		addLine("}");
	}
	addLine("type account = {");
	indent();
	addLine("mutable balance: uint256;");
	addLine("storage: state");
	unindent();
	addLine("}");
	addLine("val external_call (this: account): bool");
	indent();
	addLine("ensures { result = false -> this = (old this) }");
	addLine("writes { this }");
	addSourceFromDocStrings(m_currentContract.contract->annotation());
	unindent();
	if (!_contract.baseContracts().empty())
		error(*_contract.baseContracts().front(), "Inheritance not supported.");
	if (!_contract.definedStructs().empty())
		error(*_contract.definedStructs().front(), "User-defined types not supported.");
	if (!_contract.definedEnums().empty())
		error(*_contract.definedEnums().front(), "User-defined types not supported.");
	if (!_contract.events().empty())
		error(*_contract.events().front(), "Events not supported.");
	if (!_contract.functionModifiers().empty())
		error(*_contract.functionModifiers().front(), "Modifiers not supported.");
	ASTNode::listAccept(_contract.definedFunctions(), *this);
	return false;
}
void Why3Translator::endVisit(ContractDefinition const&)
{
	m_currentContract.reset();
	unindent();
	addLine("end");
}
bool Why3Translator::visit(FunctionDefinition const& _function)
{
	if (!_function.isImplemented())
	{
		error(_function, "Unimplemented functions not supported.");
		return false;
	}
	if (_function.name().empty())
	{
		error(_function, "Fallback functions not supported.");
		return false;
	}
	if (!_function.modifiers().empty())
	{
		error(_function, "Modifiers not supported.");
		return false;
	}
	m_localVariables.clear();
	for (auto const& var: _function.parameters())
		m_localVariables[var->name()] = var.get();
	for (auto const& var: _function.returnParameters())
		m_localVariables[var->name()] = var.get();
	for (auto const& var: _function.localVariables())
		m_localVariables[var->name()] = var;
	add("let rec _" + _function.name());
	add(" (this: account)");
	for (auto const& param: _function.parameters())
	{
		string paramType;
		try
		{
			paramType = toFormalType(*param->annotation().type);
		}
		catch (NoFormalType &err)
		{
			string const* typeName = boost::get_error_info(err);
			error(*param, "Parameter type \"" + (typeName ? *typeName : "") + "\" not supported.");
		}
		if (param->name().empty())
			error(*param, "Anonymous function parameters not supported.");
		add(" (arg_" + param->name() + ": " + paramType + ")");
	}
	add(":");
	indent();
	indent();
	string retString = "(";
	for (auto const& retParam: _function.returnParameters())
	{
		string paramType;
		try
		{
			paramType = toFormalType(*retParam->annotation().type);
		}
		catch (NoFormalType &err)
		{
			string const* typeName = boost::get_error_info(err);
			error(*retParam, "Parameter type " + (typeName ? *typeName : "") + " not supported.");
		}
		if (retString.size() != 1)
			retString += ", ";
		retString += paramType;
	}
	add(retString + ")");
	unindent();
	addSourceFromDocStrings(_function.annotation());
	if (!m_currentContract.contract)
		error(_function, "Only functions inside contracts allowed.");
	addSourceFromDocStrings(m_currentContract.contract->annotation());
	if (_function.isDeclaredConst())
		addLine("ensures { (old this) = this }");
	else
		addLine("writes { this }");
	addLine("=");
	// store the prestate in the case we need to revert
	addLine("let prestate = {balance = this.balance; storage = " + copyOfStorage() + "} in ");
	// initialise local variables
	for (auto const& variable: _function.parameters())
		addLine("let _" + variable->name() + " = ref arg_" + variable->name() + " in");
	for (auto const& variable: _function.returnParameters())
	{
		if (variable->name().empty())
			error(*variable, "Unnamed return variables not yet supported.");
		string varType;
		try
		{
			varType = toFormalType(*variable->annotation().type);
		}
		catch (NoFormalType &err)
		{
			string const* typeNamePtr = boost::get_error_info(err);
			error(*variable, "Type " + (typeNamePtr ? *typeNamePtr : "") + "in return parameter not yet supported.");
		}
		addLine("let _" + variable->name() + ": ref " + varType + " = ref (of_int 0) in");
	}
	for (VariableDeclaration const* variable: _function.localVariables())
	{
		if (variable->name().empty())
			error(*variable, "Unnamed variables not yet supported.");
		string varType;
		try
		{
			varType = toFormalType(*variable->annotation().type);
		}
		catch (NoFormalType &err)
		{
			string const* typeNamePtr = boost::get_error_info(err);
			error(*variable, "Type " + (typeNamePtr ? *typeNamePtr : "") + "in variable declaration not yet supported.");
		}
		addLine("let _" + variable->name() + ": ref " + varType + " = ref (of_int 0) in");
	}
	addLine("try");
	_function.body().accept(*this);
	add(";");
	addLine("raise Return");
	string retVals;
	for (auto const& variable: _function.returnParameters())
	{
		if (!retVals.empty())
			retVals += ", ";
		retVals += "!_" + variable->name();
	}
	addLine("with Return -> (" + retVals + ") |");
	string reversion = "     Revert -> this.balance <- prestate.balance; ";
	for (auto const* variable: m_currentContract.stateVariables)
		reversion += "this.storage._" + variable->name() + " <- prestate.storage._" + variable->name() + "; ";
	//@TODO in case of reversion the return values are wrong - we need to change the
	// return type to include a bool to signify if an exception was thrown.
	reversion += "(" + retVals + ")";
	addLine(reversion);
	unindent();
	addLine("end");
	addLine("");
	return false;
}
void Why3Translator::endVisit(FunctionDefinition const&)
{
	m_localVariables.clear();
}
bool Why3Translator::visit(Block const& _node)
{
	addSourceFromDocStrings(_node.annotation());
	add("begin");
	indent();
	for (size_t i = 0; i < _node.statements().size(); ++i)
	{
		_node.statements()[i]->accept(*this);
		if (i != _node.statements().size() - 1)
		{
			auto it = m_lines.end() - 1;
			while (it != m_lines.begin() && it->contents.empty())
				--it;
			if (!boost::algorithm::ends_with(it->contents, "begin"))
				it->contents += ";";
		}
		newLine();
	}
	unindent();
	add("end");
	return false;
}
bool Why3Translator::visit(IfStatement const& _node)
{
	addSourceFromDocStrings(_node.annotation());
	add("if ");
	_node.condition().accept(*this);
	add(" then");
	visitIndentedUnlessBlock(_node.trueStatement());
	if (_node.falseStatement())
	{
		newLine();
		add("else");
		visitIndentedUnlessBlock(*_node.falseStatement());
	}
	return false;
}
bool Why3Translator::visit(WhileStatement const& _node)
{
	addSourceFromDocStrings(_node.annotation());
	add("while ");
	_node.condition().accept(*this);
	newLine();
	add("do");
	visitIndentedUnlessBlock(_node.body());
	add("done");
	return false;
}
bool Why3Translator::visit(Return const& _node)
{
	addSourceFromDocStrings(_node.annotation());
	if (_node.expression())
	{
		solAssert(!!_node.annotation().functionReturnParameters, "");
		auto const& params = _node.annotation().functionReturnParameters->parameters();
		if (params.size() != 1)
		{
			error(_node, "Directly returning tuples not supported. Rather assign to return variable.");
			return false;
		}
		add("begin _" + params.front()->name() + " := ");
		_node.expression()->accept(*this);
		add("; raise Return end");
	}
	else
		add("raise Return");
	return false;
}
bool Why3Translator::visit(Throw const& _node)
{
	addSourceFromDocStrings(_node.annotation());
	add("raise Revert");
	return false;
}
bool Why3Translator::visit(VariableDeclarationStatement const& _node)
{
	addSourceFromDocStrings(_node.annotation());
	if (_node.declarations().size() != 1)
	{
		error(_node, "Multiple variables not supported.");
		return false;
	}
	if (_node.initialValue())
	{
		add("_" + _node.declarations().front()->name() + " := ");
		_node.initialValue()->accept(*this);
	}
	return false;
}
bool Why3Translator::visit(ExpressionStatement const& _node)
{
	addSourceFromDocStrings(_node.annotation());
	return true;
}
bool Why3Translator::visit(Assignment const& _node)
{
	if (_node.assignmentOperator() != Token::Assign)
		error(_node, "Compound assignment not supported.");
	_node.leftHandSide().accept(*this);
	add(m_currentLValueIsRef ? " := " : " <- ");
	_node.rightHandSide().accept(*this);
	return false;
}
bool Why3Translator::visit(TupleExpression const& _node)
{
	if (_node.components().size() != 1)
		error(_node, "Only tuples with exactly one component supported.");
	add("(");
	return true;
}
bool Why3Translator::visit(UnaryOperation const& _unaryOperation)
{
	try
	{
		toFormalType(*_unaryOperation.annotation().type);
	}
	catch (NoFormalType &err)
	{
		string const* typeNamePtr = boost::get_error_info(err);
		error(_unaryOperation, "Type \"" + (typeNamePtr ? *typeNamePtr : "") + "\" supported in unary operation.");
	}
	switch (_unaryOperation.getOperator())
	{
	case Token::Not: // !
		add("(not ");
		break;
	default:
		error(_unaryOperation, "Operator not supported.");
		break;
	}
	_unaryOperation.subExpression().accept(*this);
	add(")");
	return false;
}
bool Why3Translator::visit(BinaryOperation const& _binaryOperation)
{
	Expression const& leftExpression = _binaryOperation.leftExpression();
	Expression const& rightExpression = _binaryOperation.rightExpression();
	solAssert(!!_binaryOperation.annotation().commonType, "");
	Type const& commonType = *_binaryOperation.annotation().commonType;
	Token::Value const c_op = _binaryOperation.getOperator();
	if (commonType.category() == Type::Category::RationalNumber)
	{
		auto const& constantNumber = dynamic_cast(commonType);
		if (constantNumber.isFractional())
			error(_binaryOperation, "Fractional numbers not supported.");
		else
			add("(of_int " + toString(commonType.literalValue(nullptr)) + ")");
		return false;
	}
	static const map optrans({
		{Token::And, " && "},
		{Token::Or, " || "},
		{Token::BitOr, " lor "},
		{Token::BitXor, " lxor "},
		{Token::BitAnd, " land "},
		{Token::Add, " + "},
		{Token::Sub, " - "},
		{Token::Mul, " * "},
		{Token::Div, " / "},
		{Token::Mod, " mod "},
		{Token::Equal, " = "},
		{Token::NotEqual, " <> "},
		{Token::LessThan, " < "},
		{Token::GreaterThan, " > "},
		{Token::LessThanOrEqual, " <= "},
		{Token::GreaterThanOrEqual, " >= "}
	});
	if (!optrans.count(c_op))
	{
		error(_binaryOperation, "Operator not supported.");
		return true;
	}
	add("(");
	leftExpression.accept(*this);
	add(optrans.at(c_op));
	rightExpression.accept(*this);
	add(")");
	return false;
}
bool Why3Translator::visit(FunctionCall const& _node)
{
	if (_node.annotation().isTypeConversion || _node.annotation().isStructConstructorCall)
	{
		error(_node, "Only ordinary function calls supported.");
		return true;
	}
	FunctionType const& function = dynamic_cast(*_node.expression().annotation().type);
	switch (function.location())
	{
	case FunctionType::Location::AddMod:
	case FunctionType::Location::MulMod:
	{
		//@todo require that third parameter is not zero
		add("(of_int (mod (Int.(");
		add(function.location() == FunctionType::Location::AddMod ? "+" : "*");
		add(") (to_int ");
		_node.arguments().at(0)->accept(*this);
		add(") (to_int ");
		_node.arguments().at(1)->accept(*this);
		add(")) (to_int ");
		_node.arguments().at(2)->accept(*this);
		add(")))");
		return false;
	}
	case FunctionType::Location::Internal:
	{
		if (!_node.names().empty())
		{
			error(_node, "Function calls with named arguments not supported.");
			return true;
		}
		//@TODO check type conversions
		add("(");
		_node.expression().accept(*this);
		add(" state");
		for (auto const& arg: _node.arguments())
		{
			add(" ");
			arg->accept(*this);
		}
		add(")");
		return false;
	}
	case FunctionType::Location::Bare:
	{
		if (!_node.arguments().empty())
		{
			error(_node, "Function calls with named arguments not supported.");
			return true;
		}
		add("(");
		indent();
		add("let amount = 0 in ");
		_node.expression().accept(*this);
		addLine("if amount <= this.balance then");
		indent();
		addLine("let balance_precall = this.balance in");
		addLine("begin");
		indent();
		addLine("this.balance <- this.balance - amount;");
		addLine("if not (external_call this) then begin this.balance = balance_precall; false end else true");
		unindent();
		addLine("end");
		unindent();
		addLine("else false");
		unindent();
		add(")");
		return false;
	}
	case FunctionType::Location::SetValue:
	{
		add("let amount = ");
		solAssert(_node.arguments().size() == 1, "");
		_node.arguments()[0]->accept(*this);
		add(" in ");
		return false;
	}
	default:
		error(_node, "Only internal function calls supported.");
		return true;
	}
}
bool Why3Translator::visit(MemberAccess const& _node)
{
	if (
		_node.expression().annotation().type->category() == Type::Category::Array &&
		_node.memberName() == "length" &&
		!_node.annotation().lValueRequested
	)
	{
		add("(of_int ");
		_node.expression().accept(*this);
		add(".length");
		add(")");
	}
	else if (
		_node.memberName() == "call" &&
		*_node.expression().annotation().type == IntegerType(160, IntegerType::Modifier::Address)
	)
	{
		// Do nothing, do not even visit the address because this will be an external call
		//@TODO ensure that the expression itself does not have side-effects
		return false;
	}
	else
		error(_node, "Member access: Only call and array length supported.");
	return false;
}
bool Why3Translator::visit(IndexAccess const& _node)
{
	auto baseType = dynamic_cast(_node.baseExpression().annotation().type.get());
	if (!baseType)
	{
		error(_node, "Index access only supported for arrays.");
		return true;
	}
	if (_node.annotation().lValueRequested)
	{
		error(_node, "Assignment to array elements not supported.");
		return true;
	}
	add("(");
	_node.baseExpression().accept(*this);
	add("[to_int ");
	_node.indexExpression()->accept(*this);
	add("]");
	add(")");
	return false;
}
bool Why3Translator::visit(Identifier const& _identifier)
{
	Declaration const* declaration = _identifier.annotation().referencedDeclaration;
	if (FunctionDefinition const* functionDef = dynamic_cast(declaration))
		add("_" + functionDef->name());
	else if (auto variable = dynamic_cast(declaration))
	{
		bool isStateVar = isStateVariable(variable);
		bool lvalue = _identifier.annotation().lValueRequested;
		if (isStateVar)
			add("this.storage.");
		else if (!lvalue)
			add("!(");
		add("_" + variable->name());
		if (!isStateVar && !lvalue)
			add(")");
		m_currentLValueIsRef = !isStateVar;
	}
	else
		error(_identifier, "Not supported.");
	return false;
}
bool Why3Translator::visit(Literal const& _literal)
{
	TypePointer type = _literal.annotation().type;
	switch (type->category())
	{
	case Type::Category::Bool:
		if (type->literalValue(&_literal) == 0)
			add("false");
		else
			add("true");
		break;
	case Type::Category::RationalNumber:
	{
		auto const& constantNumber = dynamic_cast(*type);
		if (constantNumber.isFractional())
			error(_literal, "Fractional numbers not supported.");
		else
			add("(of_int " + toString(type->literalValue(&_literal)) + ")");
		break;
	}
	default:
		error(_literal, "Not supported.");
	}
	return false;
}
bool Why3Translator::isStateVariable(VariableDeclaration const* _var) const
{
	return contains(m_currentContract.stateVariables, _var);
}
bool Why3Translator::isStateVariable(string const& _name) const
{
	for (auto const& var: m_currentContract.stateVariables)
		if (var->name() == _name)
			return true;
	return false;
}
bool Why3Translator::isLocalVariable(VariableDeclaration const* _var) const
{
	for (auto const& var: m_localVariables)
		if (var.second == _var)
			return true;
	return false;
}
bool Why3Translator::isLocalVariable(string const& _name) const
{
	return m_localVariables.count(_name);
}
string Why3Translator::copyOfStorage() const
{
	if (m_currentContract.stateVariables.empty())
		return "()";
	string ret = "{";
	bool first = true;
	for (auto const* variable: m_currentContract.stateVariables)
	{
		if (first)
			first = false;
		else
			ret += "; ";
		ret += "_" + variable->name() + " = this.storage._" + variable->name();
	}
	return ret + "}";
}
void Why3Translator::visitIndentedUnlessBlock(Statement const& _statement)
{
	bool isBlock = !!dynamic_cast(&_statement);
	if (isBlock)
		newLine();
	else
		indent();
	_statement.accept(*this);
	if (isBlock)
		newLine();
	else
		unindent();
}
void Why3Translator::addSourceFromDocStrings(DocumentedAnnotation const& _annotation)
{
	auto why3Range = _annotation.docTags.equal_range("why3");
	for (auto i = why3Range.first; i != why3Range.second; ++i)
		addLine(transformVariableReferences(i->second.content));
}
string Why3Translator::transformVariableReferences(string const& _annotation)
{
	string ret;
	auto pos = _annotation.begin();
	while (true)
	{
		auto hash = find(pos, _annotation.end(), '#');
		ret.append(pos, hash);
		if (hash == _annotation.end())
			break;
		auto hashEnd = find_if(hash + 1, _annotation.end(), [](char _c)
		{
			return
				(_c != '_' && _c != '$') &&
				!('a' <= _c && _c <= 'z') &&
				!('A' <= _c && _c <= 'Z') &&
				!('0' <= _c && _c <= '9');
		});
		string varName(hash + 1, hashEnd);
		if (isLocalVariable(varName))
			ret += "(!_" + varName + ")";
		else if (isStateVariable(varName))
			ret += "(this.storage._" + varName + ")";
		//@todo return variables
		else
			ret.append(hash, hashEnd);
		pos = hashEnd;
	}
	return ret;
}
void Why3Translator::appendPreface()
{
	m_lines.push_back(Line{R"(
module UInt256
	use import mach.int.Unsigned
	type uint256
	constant max_uint256: int = 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
	clone export mach.int.Unsigned with
		type t = uint256,
		constant max = max_uint256
end
module Address
	use import mach.int.Unsigned
	type address
	constant max_address: int = 0xffffffffffffffffffffffffffffffffffffffff (* 160 bit = 40 f's *)
	clone export mach.int.Unsigned with
		type t = address,
		constant max = max_address
end
   )", 0});
}