/*
	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 .
*/
#include 
#include 
#include 
#include 
using namespace dev;
using namespace solidity;
using namespace dev::solidity::test;
using namespace std;
string TestFunctionCall::format(string const& _linePrefix, bool const _renderResult, bool const _highlight) const
{
	using namespace soltest;
	using Token = soltest::Token;
	stringstream _stream;
	bool highlight = !matchesExpectation() && _highlight;
	auto formatOutput = [&](bool const _singleLine)
	{
		string ws = " ";
		string arrow = formatToken(Token::Arrow);
		string colon = formatToken(Token::Colon);
		string comma = formatToken(Token::Comma);
		string comment = formatToken(Token::Comment);
		string ether = formatToken(Token::Ether);
		string newline = formatToken(Token::Newline);
		string failure = formatToken(Token::Failure);
		/// Formats the function signature. This is the same independent from the display-mode.
		_stream << _linePrefix << newline << ws << m_call.signature;
		if (m_call.value > u256(0))
			_stream << comma << ws << m_call.value << ws << ether;
		if (!m_call.arguments.rawBytes().empty())
		{
			string output = formatRawParameters(m_call.arguments.parameters, _linePrefix);
			_stream << colon << output;
		}
		/// Formats comments on the function parameters and the arrow taking
		/// the display-mode into account.
		if (_singleLine)
		{
			if (!m_call.arguments.comment.empty())
				_stream << ws << comment << m_call.arguments.comment << comment;
			_stream << ws << arrow << ws;
		}
		else
		{
			_stream << endl << _linePrefix << newline << ws;
			if (!m_call.arguments.comment.empty())
			{
				 _stream << comment << m_call.arguments.comment << comment;
				 _stream << endl << _linePrefix << newline << ws;
			}
			_stream << arrow << ws;
		}
		/// Format either the expected output or the actual result output
		string result;
		if (!_renderResult)
		{
			bytes output = m_call.expectations.rawBytes();
			bool const isFailure = m_call.expectations.failure;
			result = isFailure ? failure : formatBytesParameters(output, m_call.expectations.result);
		}
		else
		{
			bytes output = m_rawBytes;
			bool const isFailure = m_failure;
			result = isFailure ? failure : formatBytesParameters(output, m_call.expectations.result);
		}
		AnsiColorized(_stream, highlight, {dev::formatting::RED_BACKGROUND}) << result;
		/// Format comments on expectations taking the display-mode into account.
		if (_singleLine)
		{
			if (!m_call.expectations.comment.empty())
				_stream << ws << comment << m_call.expectations.comment << comment;
		}
		else
		{
			if (!m_call.expectations.comment.empty())
			{
				_stream << endl << _linePrefix << newline << ws;
				_stream << comment << m_call.expectations.comment << comment;
			}
		}
	};
	if (m_call.displayMode == FunctionCall::DisplayMode::SingleLine)
		formatOutput(true);
	else
		formatOutput(false);
//	_stream << endl;
	return _stream.str();
}
string TestFunctionCall::formatBytesParameters(bytes const& _bytes, dev::solidity::test::ParameterList const& _params) const
{
	stringstream resultStream;
	if (_bytes.empty())
		return {};
	auto it = _bytes.begin();
	for (auto const& param: _params)
	{
		long offset = static_cast(param.abiType.size);
		auto offsetIter = it + offset;
		soltestAssert(offsetIter <= _bytes.end(), "Byte range can not be extended past the end of given bytes.");
		bytes byteRange{it, offsetIter};
		switch (param.abiType.type)
		{
		case ABIType::UnsignedDec:
			// Check if the detected type was wrong and if this could
			// be signed. If an unsigned was detected in the expectations,
			// but the actual result returned a signed, it would be formatted
			// incorrectly.
			soltestAssert(param.abiType.align == ABIType::AlignRight, "Unsigned decimals must be right-aligned.");
			if (*byteRange.begin() & 0x80)
				resultStream << u2s(fromBigEndian(byteRange));
			else
				resultStream << fromBigEndian(byteRange);
			break;
		case ABIType::SignedDec:
			soltestAssert(param.abiType.align == ABIType::AlignRight, "Signed decimals must be right-aligned.");
			if (*byteRange.begin() & 0x80)
				resultStream << u2s(fromBigEndian(byteRange));
			else
				resultStream << fromBigEndian(byteRange);
			break;
		case ABIType::Boolean:
		{
			soltestAssert(param.abiType.align == ABIType::AlignRight, "Booleans must be right-aligned.");
			u256 result = fromBigEndian(byteRange);
			if (result == 0)
				resultStream << "false";
			else
				resultStream << "true";
			break;
		}
		case ABIType::Hex:
			soltestAssert(param.abiType.align == ABIType::AlignLeft, "Hex numbers must be left-aligned.");
			byteRange.erase(
				std::remove(byteRange.begin(), byteRange.end(), 0), byteRange.end()
			);
			resultStream << toHex(byteRange, HexPrefix::Add);
			break;
		case ABIType::Failure:
			break;
		case ABIType::None:
			break;
		}
		it += offset;
		if (it != _bytes.end() && !(param.abiType.type == ABIType::None))
			resultStream << ", ";
	}
	soltestAssert(it == _bytes.end(), "Parameter encoding too short for the given byte range.");
	return resultStream.str();
}
string TestFunctionCall::formatRawParameters(dev::solidity::test::ParameterList const& _params, std::string const& _linePrefix) const
{
	stringstream resultStream;
	for (auto const& param: _params)
	{
		if (param.format.newline)
			resultStream << endl << _linePrefix << "//";
		resultStream << " " << param.rawString;
		if (¶m != &_params.back())
			resultStream << ",";
	}
	return resultStream.str();
}
void TestFunctionCall::reset()
{
	m_rawBytes = bytes{};
	m_failure = true;
}
bool TestFunctionCall::matchesExpectation() const
{
	return m_failure == m_call.expectations.failure && m_rawBytes == m_call.expectations.rawBytes();
}