/*
	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;
			if (_singleLine)
				_stream << ws;
			_stream << 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 :
				formatRawParameters(m_call.expectations.result);
		}
		else
		{
			bytes output = m_rawBytes;
			bool const isFailure = m_failure;
			result = isFailure ?
				failure :
				matchesExpectation() ?
					formatRawParameters(m_call.expectations.result) :
					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);
	return _stream.str();
}
string TestFunctionCall::formatBytesParameters(bytes const& _bytes, dev::solidity::test::ParameterList const& _params) const
{
	stringstream resultStream;
	if (_bytes.empty())
		return {};
	auto sizeFold = [](size_t const _a, Parameter const& _b) { return _a + _b.abiType.size; };
	size_t encodingSize = std::accumulate(_params.begin(), _params.end(), size_t{0}, sizeFold);
	soltestAssert(
		encodingSize == _bytes.size(),
		"Encoding does not match byte range: the call returned " +
		to_string(_bytes.size()) + " bytes, but " +
		to_string(encodingSize) + " bytes were expected."
	);
	auto it = _bytes.begin();
	for (auto const& param: _params)
	{
		long offset = static_cast(param.abiType.size);
		auto offsetIter = it + offset;
		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.
			if (*byteRange.begin() & 0x80)
				resultStream << u2s(fromBigEndian(byteRange));
			else
				resultStream << fromBigEndian(byteRange);
			break;
		case ABIType::SignedDec:
			if (*byteRange.begin() & 0x80)
				resultStream << u2s(fromBigEndian(byteRange));
			else
				resultStream << fromBigEndian(byteRange);
			break;
		case ABIType::Boolean:
		{
			u256 result = fromBigEndian(byteRange);
			if (result == 0)
				resultStream << "false";
			else if (result == 1)
				resultStream << "true";
			else
				resultStream << result;
			break;
		}
		case ABIType::Hex:
			resultStream << toHex(byteRange, HexPrefix::Add);
			break;
		case ABIType::HexString:
			resultStream << "hex\"" << toHex(byteRange) << "\"";
			break;
		case ABIType::Failure:
			break;
		case ABIType::None:
			break;
		}
		it += offset;
		if (it != _bytes.end() && !(param.abiType.type == ABIType::None))
			resultStream << ", ";
	}
	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();
}