/*
	This file is part of solidity.
	solidity 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.
	solidity 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 solidity.  If not, see .
*/
/**
 * @author Alex Beregszaszi
 * @date 2017
 * Component that translates Solidity code into Yul.
 */
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
using namespace std;
using namespace solidity;
using namespace solidity::util;
using namespace solidity::frontend;
pair IRGenerator::run(
	ContractDefinition const& _contract,
	map const& _otherYulSources
)
{
	string const ir = yul::reindent(generate(_contract, _otherYulSources));
	yul::AssemblyStack asmStack(m_evmVersion, yul::AssemblyStack::Language::StrictAssembly, m_optimiserSettings);
	if (!asmStack.parseAndAnalyze("", ir))
	{
		string errorMessage;
		for (auto const& error: asmStack.errors())
			errorMessage += langutil::SourceReferenceFormatter::formatErrorInformation(*error);
		solAssert(false, ir + "\n\nInvalid IR generated:\n" + errorMessage + "\n");
	}
	asmStack.optimize();
	string warning =
		"/*******************************************************\n"
		" *                       WARNING                       *\n"
		" *  Solidity to Yul compilation is still EXPERIMENTAL  *\n"
		" *       It can result in LOSS OF FUNDS or worse       *\n"
		" *                !USE AT YOUR OWN RISK!               *\n"
		" *******************************************************/\n\n";
	return {warning + ir, warning + asmStack.print()};
}
string IRGenerator::generate(
	ContractDefinition const& _contract,
	map const& _otherYulSources
)
{
	auto subObjectSources = [&_otherYulSources](std::set const& subObjects) -> string
	{
		std::string subObjectsSources;
		for (ContractDefinition const* subObject: subObjects)
			subObjectsSources += _otherYulSources.at(subObject);
		return subObjectsSources;
	};
	Whiskers t(R"(
		object "" {
			code {
				
				
				
				 let  := () 
				()
				
				
				
			}
			object "" {
				code {
					
					
					
				}
				
			}
			
		}
	)");
	resetContext(_contract);
	t("CreationObject", m_context.creationObjectName(_contract));
	t("memoryInit", memoryInit());
	t("notLibrary", !_contract.isLibrary());
	FunctionDefinition const* constructor = _contract.constructor();
	t("callValueCheck", !constructor || !constructor->isPayable() ? callValueCheck() : "");
	vector constructorParams;
	if (constructor && !constructor->parameters().empty())
	{
		for (size_t i = 0; i < constructor->parameters().size(); ++i)
			constructorParams.emplace_back(m_context.newYulVariable());
		t(
			"copyConstructorArguments",
			m_utils.copyConstructorArgumentsToMemoryFunction(_contract, m_context.creationObjectName(_contract))
		);
	}
	t("constructorParams", joinHumanReadable(constructorParams));
	t("constructorHasParams", !constructorParams.empty());
	t("implicitConstructor", implicitConstructorName(_contract));
	t("deploy", deployCode(_contract));
	generateImplicitConstructors(_contract);
	generateQueuedFunctions();
	t("functions", m_context.functionCollector().requestedFunctions());
	t("subObjects", subObjectSources(m_context.subObjectsCreated()));
	resetContext(_contract);
	t("RuntimeObject", m_context.runtimeObjectName(_contract));
	t("dispatch", dispatchRoutine(_contract));
	generateQueuedFunctions();
	t("runtimeFunctions", m_context.functionCollector().requestedFunctions());
	t("runtimeSubObjects", subObjectSources(m_context.subObjectsCreated()));
	return t.render();
}
string IRGenerator::generate(Block const& _block)
{
	IRGeneratorForStatements generator(m_context, m_utils);
	_block.accept(generator);
	return generator.code();
}
void IRGenerator::generateQueuedFunctions()
{
	while (!m_context.functionGenerationQueueEmpty())
		// NOTE: generateFunction() may modify function generation queue
		generateFunction(*m_context.dequeueFunctionForCodeGeneration());
}
string IRGenerator::generateFunction(FunctionDefinition const& _function)
{
	string functionName = m_context.functionName(_function);
	return m_context.functionCollector().createFunction(functionName, [&]() {
		Whiskers t(R"(
			function ()  {
				
				
			}
		)");
		t("functionName", functionName);
		string params;
		for (auto const& varDecl: _function.parameters())
			params += (params.empty() ? "" : ", ") + m_context.addLocalVariable(*varDecl).commaSeparatedList();
		t("params", params);
		string retParams;
		string retInit;
		for (auto const& varDecl: _function.returnParameters())
		{
			retParams += (retParams.empty() ? "" : ", ") + m_context.addLocalVariable(*varDecl).commaSeparatedList();
			retInit += generateInitialAssignment(*varDecl);
		}
		t("returns", retParams.empty() ? "" : " -> " + retParams);
		t("initReturnVariables", retInit);
		t("body", generate(_function.body()));
		return t.render();
	});
}
string IRGenerator::generateGetter(VariableDeclaration const& _varDecl)
{
	string functionName = m_context.functionName(_varDecl);
	Type const* type = _varDecl.annotation().type;
	solAssert(!_varDecl.isConstant(), "");
	solAssert(!_varDecl.immutable(), "");
	solAssert(_varDecl.isStateVariable(), "");
	if (auto const* mappingType = dynamic_cast(type))
		return m_context.functionCollector().createFunction(functionName, [&]() {
			pair slot_offset = m_context.storageLocationOfVariable(_varDecl);
			solAssert(slot_offset.second == 0, "");
			FunctionType funType(_varDecl);
			solUnimplementedAssert(funType.returnParameterTypes().size() == 1, "");
			TypePointer returnType = funType.returnParameterTypes().front();
			unsigned num_keys = 0;
			stringstream indexAccesses;
			string slot = m_context.newYulVariable();
			do
			{
				solUnimplementedAssert(
					mappingType->keyType()->sizeOnStack() == 1,
					"Multi-slot mapping key unimplemented - might not be a problem"
				);
				indexAccesses <<
					slot <<
					" := " <<
					m_utils.mappingIndexAccessFunction(*mappingType, *mappingType->keyType()) <<
					"(" <<
					slot;
				if (mappingType->keyType()->sizeOnStack() > 0)
					indexAccesses <<
						", " <<
						suffixedVariableNameList("key", num_keys, num_keys + mappingType->keyType()->sizeOnStack());
				indexAccesses << ")\n";
				num_keys += mappingType->keyType()->sizeOnStack();
			}
			while ((mappingType = dynamic_cast(mappingType->valueType())));
			return Whiskers(R"(
				function () -> rval {
					let  := 
					
					rval := ()
				}
			)")
			("functionName", functionName)
			("keys", suffixedVariableNameList("key", 0, num_keys))
			("readStorage", m_utils.readFromStorage(*returnType, 0, false))
			("indexAccesses", indexAccesses.str())
			("slot", slot)
			("base", slot_offset.first.str())
			.render();
		});
	else
	{
		solUnimplementedAssert(type->isValueType(), "");
		return m_context.functionCollector().createFunction(functionName, [&]() {
			pair slot_offset = m_context.storageLocationOfVariable(_varDecl);
			return Whiskers(R"(
				function () -> rval {
					rval := ()
				}
			)")
			("functionName", functionName)
			("readStorage", m_utils.readFromStorage(*type, slot_offset.second, false))
			("slot", slot_offset.first.str())
			.render();
		});
	}
}
string IRGenerator::generateInitialAssignment(VariableDeclaration const& _varDecl)
{
	IRGeneratorForStatements generator(m_context, m_utils);
	generator.initializeLocalVar(_varDecl);
	return generator.code();
}
pair> IRGenerator::evaluateConstructorArguments(
	ContractDefinition const& _contract
)
{
	map constructorParams;
	vector>const *>> baseConstructorArguments;
	for (ASTPointer const& base: _contract.baseContracts())
		if (FunctionDefinition const* baseConstructor = dynamic_cast(
					base->name().annotation().referencedDeclaration
			)->constructor(); baseConstructor && base->arguments())
			baseConstructorArguments.emplace_back(
					dynamic_cast(baseConstructor->scope()),
					base->arguments()
			);
	if (FunctionDefinition const* constructor = _contract.constructor())
		for (auto const& modifier: constructor->modifiers())
			if (FunctionDefinition const* baseConstructor = dynamic_cast(
				modifier->name()->annotation().referencedDeclaration
			)->constructor(); baseConstructor && modifier->arguments())
				baseConstructorArguments.emplace_back(
					dynamic_cast(baseConstructor->scope()),
					modifier->arguments()
				);
	IRGeneratorForStatements generator{m_context, m_utils};
	for (auto&& [baseContract, arguments]: baseConstructorArguments)
	{
		solAssert(baseContract && arguments, "");
		if (baseContract->constructor() && !arguments->empty())
		{
			vector params;
			for (size_t i = 0; i < arguments->size(); ++i)
				params.emplace_back(generator.evaluateExpression(
						*(arguments->at(i)),
						*(baseContract->constructor()->parameters()[i]->type())
				).commaSeparatedList());
			constructorParams[baseContract] = joinHumanReadable(params);
		}
	}
	return {generator.code(), constructorParams};
}
string IRGenerator::initStateVariables(ContractDefinition const& _contract)
{
	IRGeneratorForStatements generator{m_context, m_utils};
	for (VariableDeclaration const* variable: _contract.stateVariables())
		if (!variable->isConstant() && !variable->immutable())
			generator.initializeStateVar(*variable);
	return generator.code();
}
void IRGenerator::generateImplicitConstructors(ContractDefinition const& _contract)
{
	auto listAllParams = [&](
		map const& baseParams) -> string
	{
		vector params;
		for (ContractDefinition const* contract: _contract.annotation().linearizedBaseContracts)
			if (baseParams.count(contract))
				params.emplace_back(baseParams.at(contract));
		return joinHumanReadable(params);
	};
	map baseConstructorParams;
	for (size_t i = 0; i < _contract.annotation().linearizedBaseContracts.size(); ++i)
	{
		ContractDefinition const* contract = _contract.annotation().linearizedBaseContracts[i];
		baseConstructorParams.erase(contract);
		m_context.functionCollector().createFunction(implicitConstructorName(*contract), [&]() {
			Whiskers t(R"(
				function () {
					
					 () 
					
					
				}
			)");
			string params;
			if (contract->constructor())
				for (ASTPointer const& varDecl: contract->constructor()->parameters())
					params += (params.empty() ? "" : ", ") + m_context.addLocalVariable(*varDecl).commaSeparatedList();
			t("params", params);
			string baseParamsString = listAllParams(baseConstructorParams);
			t("baseParams", baseParamsString);
			t("comma", !params.empty() && !baseParamsString.empty() ? ", " : "");
			t("functionName", implicitConstructorName(*contract));
			pair> evaluatedArgs = evaluateConstructorArguments(*contract);
			baseConstructorParams.insert(evaluatedArgs.second.begin(), evaluatedArgs.second.end());
			t("evalBaseArguments", evaluatedArgs.first);
			if (i < _contract.annotation().linearizedBaseContracts.size() - 1)
			{
				t("hasNextConstructor", true);
				ContractDefinition const* nextContract = _contract.annotation().linearizedBaseContracts[i + 1];
				t("nextConstructor", implicitConstructorName(*nextContract));
				t("nextParams", listAllParams(baseConstructorParams));
			}
			else
				t("hasNextConstructor", false);
			t("initStateVariables", initStateVariables(*contract));
			t("userDefinedConstructorBody", contract->constructor() ? generate(contract->constructor()->body()) : "");
			return t.render();
		});
	}
}
string IRGenerator::deployCode(ContractDefinition const& _contract)
{
	Whiskers t(R"X(
		codecopy(0, dataoffset("