/*
	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 <http://www.gnu.org/licenses/>.
*/
/**
 * @author Christian <c@ethdev.com>
 * @date 2014
 * Utilities for the solidity compiler.
 */

#pragma once

#include <ostream>
#include <libevmface/Instruction.h>
#include <liblll/Assembly.h>
#include <libsolidity/Types.h>

namespace dev {
namespace solidity {


/**
 * Context to be shared by all units that compile the same contract.
 * It stores the generated bytecode and the position of identifiers in memory and on the stack.
 */
class CompilerContext
{
public:
	CompilerContext() {}

	void startNewFunction() { m_localVariables.clear(); m_asm.setDeposit(0); }
	void initializeLocalVariables(unsigned _numVariables);
	void addVariable(VariableDeclaration const& _declaration) { m_localVariables.push_back(&_declaration); }
	/// Returns the distance of the given local variable from the top of the stack.
	int getStackPositionOfVariable(Declaration const& _declaration);

	void addFunction(FunctionDefinition const& _function) { m_functionEntryLabels.insert(std::make_pair(&_function, m_asm.newTag())); }
	eth::AssemblyItem getFunctionEntryLabel(FunctionDefinition const& _function) const;

	void adjustStackOffset(int _adjustment) { m_asm.adjustDeposit(_adjustment); }

	/// Appends a JUMPI instruction to a new tag and @returns the tag
	eth::AssemblyItem appendConditionalJump() { return m_asm.appendJumpI().tag(); }
	/// Appends a JUMPI instruction to @a _tag
	CompilerContext& appendConditionalJumpTo(eth::AssemblyItem const& _tag) { m_asm.appendJumpI(_tag); return *this; }
	/// Appends a JUMP to a new tag and @returns the tag
	eth::AssemblyItem appendJump() { return m_asm.appendJump().tag(); }
	/// Appends a JUMP to a specific tag
	CompilerContext& appendJumpTo(eth::AssemblyItem const& _tag) { m_asm.appendJump(_tag); return *this; }
	/// Appends pushing of a new tag and @returns the new tag.
	eth::AssemblyItem pushNewTag() { return m_asm.append(m_asm.newPushTag()).tag(); }
	/// @returns a new tag without pushing any opcodes or data
	eth::AssemblyItem newTag() { return m_asm.newTag(); }
	/// Adds a subroutine to the code (in the data section) and pushes its size (via a tag)
	/// on the stack. @returns the assembly item corresponding to the pushed subroutine, i.e. its offset.
	eth::AssemblyItem addSubroutine(eth::Assembly const& _assembly) { return m_asm.appendSubSize(_assembly); }

	/// Append elements to the current instruction list and adjust @a m_stackOffset.
	CompilerContext& operator<<(eth::AssemblyItem const& _item) { m_asm.append(_item); return *this; }
	CompilerContext& operator<<(eth::Instruction _instruction) { m_asm.append(_instruction); return *this; }
	CompilerContext& operator<<(u256 const& _value) { m_asm.append(_value); return *this; }
	CompilerContext& operator<<(bytes const& _data) { m_asm.append(_data); return *this; }

	eth::Assembly const& getAssembly() const { return m_asm; }
	void streamAssembly(std::ostream& _stream) const { _stream << m_asm; }
	bytes getAssembledBytecode() const { return m_asm.assemble(); }
private:
	eth::Assembly m_asm;

	/// Offsets of local variables on the stack.
	std::vector<Declaration const*> m_localVariables;
	/// Labels pointing to the entry points of funcitons.
	std::map<FunctionDefinition const*, eth::AssemblyItem> m_functionEntryLabels;
};

}
}