/*
	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 .
*/
/**
 * Optimiser component that turns subsequent assignments to variable declarations
 * and assignments.
 */
#include 
#include 
#include 
#include 
#include 
using namespace std;
using namespace dev;
using namespace langutil;
using namespace yul;
using namespace dev::solidity;
void SSATransform::operator()(Identifier& _identifier)
{
	if (m_currentVariableValues.count(_identifier.name))
		_identifier.name = m_currentVariableValues[_identifier.name];
}
void SSATransform::operator()(ForLoop& _for)
{
	// This will clear the current value in case of a reassignment inside the
	// init part, although the new variable would still be in scope inside the whole loop.
	// This small inefficiency is fine if we move the pre part of all for loops out
	// of the for loop.
	(*this)(_for.pre);
	Assignments assignments;
	assignments(_for.body);
	assignments(_for.post);
	for (auto const& var: assignments.names())
		m_currentVariableValues.erase(var);
	visit(*_for.condition);
	(*this)(_for.body);
	(*this)(_for.post);
}
void SSATransform::operator()(Block& _block)
{
	set variablesToClearAtEnd;
	// Creates a new variable (and returns its declaration) with value _value
	// and replaces _value by a reference to that new variable.
	auto replaceByNew = [&](SourceLocation _loc, YulString _varName, YulString _type, unique_ptr& _value) -> VariableDeclaration
	{
		YulString newName = m_nameDispenser.newName(_varName);
		m_currentVariableValues[_varName] = newName;
		variablesToClearAtEnd.emplace(_varName);
		unique_ptr v = make_unique(Identifier{_loc, newName});
		_value.swap(v);
		return VariableDeclaration{_loc, {TypedName{_loc, std::move(newName), std::move(_type)}}, std::move(v)};
	};
	iterateReplacing(
		_block.statements,
		[&](Statement& _s) -> boost::optional>
		{
			if (_s.type() == typeid(VariableDeclaration))
			{
				VariableDeclaration& varDecl = boost::get(_s);
				if (varDecl.value)
					visit(*varDecl.value);
				if (varDecl.variables.size() != 1 || !m_variablesToReplace.count(varDecl.variables.front().name))
					return {};
				vector v;
				// Replace "let a := v" by "let a_1 := v  let a := a_1"
				v.emplace_back(replaceByNew(
					varDecl.location,
					varDecl.variables.front().name,
					varDecl.variables.front().type,
					varDecl.value
				));
				v.emplace_back(move(varDecl));
				return v;
			}
			else if (_s.type() == typeid(Assignment))
			{
				Assignment& assignment = boost::get(_s);
				visit(*assignment.value);
				if (assignment.variableNames.size() != 1)
					return {};
				assertThrow(m_variablesToReplace.count(assignment.variableNames.front().name), OptimizerException, "");
				vector v;
				// Replace "a := v" by "let a_1 := v  a := v"
				v.emplace_back(replaceByNew(
					assignment.location,
					assignment.variableNames.front().name,
					{}, // TODO determine type
					assignment.value
				));
				v.emplace_back(move(assignment));
				return v;
			}
			else
				visit(_s);
			return {};
		}
	);
	for (auto const& var: variablesToClearAtEnd)
		m_currentVariableValues.erase(var);
}
void SSATransform::run(Block& _ast, NameDispenser& _nameDispenser)
{
	Assignments assignments;
	assignments(_ast);
	SSATransform{_nameDispenser, assignments.names()}(_ast);
}