/*
	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 
#include 
#include 
#include 
#include 
using namespace solidity;
using namespace solidity::util;
using namespace solidity::frontend::test;
using namespace std;
string TestFunctionCall::format(
	ErrorReporter& _errorReporter,
	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 wei = formatToken(Token::Wei);
		string newline = formatToken(Token::Newline);
		string failure = formatToken(Token::Failure);
		if (m_call.isLibrary)
		{
			stream << _linePrefix << newline << ws << "library:" << ws << m_call.signature;
			return;
		}
		/// Formats the function signature. This is the same independent from the display-mode.
		stream << _linePrefix << newline << ws << m_call.signature;
		if (m_call.value.value > u256(0))
		{
			if (m_call.value.unit == FunctionValueUnit::Ether)
				stream << comma << ws << (m_call.value.value / exp256(10, 18)) << ws << ether;
			else if (m_call.value.unit == FunctionValueUnit::Wei)
				stream << comma << ws << m_call.value.value << ws << wei;
			else
				soltestAssert(false, "");
		}
		if (!m_call.arguments.rawBytes().empty())
		{
			string output = formatRawParameters(m_call.arguments.parameters, _linePrefix);
			stream << colon;
			if (!m_call.arguments.parameters.at(0).format.newline)
				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;
			if (m_call.omitsArrow)
			{
				if (_renderResult && (m_failure || !matchesExpectation()))
					stream << ws << arrow;
			}
			else
				stream << ws << arrow;
		}
		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;
		}
		/// Format either the expected output or the actual result output
		string result;
		if (!_renderResult)
		{
			bool const isFailure = m_call.expectations.failure;
			result = isFailure ?
				formatFailure(_errorReporter, m_call, m_rawBytes, _renderResult, highlight) :
				formatRawParameters(m_call.expectations.result);
			if (!result.empty())
				AnsiColorized(stream, highlight, {util::formatting::RED_BACKGROUND}) << ws << result;
		}
		else
		{
			if (m_calledNonExistingFunction)
				_errorReporter.warning("The function \"" + m_call.signature + "\" is not known to the compiler.");
			bytes output = m_rawBytes;
			bool const isFailure = m_failure;
			result = isFailure ?
				formatFailure(_errorReporter, m_call, output, _renderResult, highlight) :
				matchesExpectation() ?
					formatRawParameters(m_call.expectations.result) :
					formatBytesParameters(
						_errorReporter,
						output,
						m_call.signature,
						m_call.expectations.result,
						highlight
					);
			if (!matchesExpectation())
			{
				std::optional abiParams;
				if (isFailure)
				{
					if (!output.empty())
						abiParams = ContractABIUtils::failureParameters(output);
				}
				else
					abiParams = ContractABIUtils::parametersFromJsonOutputs(
						_errorReporter,
						m_contractABI,
						m_call.signature
					);
				string bytesOutput = abiParams ?
					BytesUtils::formatRawBytes(output, abiParams.value(), _linePrefix) :
					BytesUtils::formatRawBytes(
						output,
						ContractABIUtils::defaultParameters(ceil(output.size() / 32)),
						_linePrefix
					);
				_errorReporter.warning(
					"The call to \"" + m_call.signature + "\" returned \n" +
					bytesOutput
				);
			}
			if (isFailure)
				AnsiColorized(stream, highlight, {util::formatting::RED_BACKGROUND}) << ws << result;
			else
				if (!result.empty())
					stream << ws << 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;
			}
		}
	};
	formatOutput(m_call.displayMode == FunctionCall::DisplayMode::SingleLine);
	return stream.str();
}
string TestFunctionCall::formatBytesParameters(
	ErrorReporter& _errorReporter,
	bytes const& _bytes,
	string const& _signature,
	solidity::frontend::test::ParameterList const& _parameters,
	bool _highlight,
	bool _failure
) const
{
	using ParameterList = solidity::frontend::test::ParameterList;
	stringstream os;
	if (_bytes.empty())
		return {};
	if (_failure)
	{
		os << BytesUtils::formatBytesRange(
			_bytes,
			ContractABIUtils::failureParameters(_bytes),
			_highlight
		);
		return os.str();
	}
	else
	{
		std::optional abiParams = ContractABIUtils::parametersFromJsonOutputs(
			_errorReporter,
			m_contractABI,
			_signature
		);
		if (abiParams)
		{
			std::optional preferredParams = ContractABIUtils::preferredParameters(
				_errorReporter,
				_parameters,
				abiParams.value(),
				_bytes
			);
			if (preferredParams)
			{
				ContractABIUtils::overwriteParameters(_errorReporter, preferredParams.value(), abiParams.value());
				os << BytesUtils::formatBytesRange(_bytes, preferredParams.value(), _highlight);
			}
		}
		else
		{
			ParameterList defaultParameters = ContractABIUtils::defaultParameters(ceil(_bytes.size() / 32));
			ContractABIUtils::overwriteParameters(_errorReporter, defaultParameters, _parameters);
			os << BytesUtils::formatBytesRange(_bytes, defaultParameters, _highlight);
		}
		return os.str();
	}
}
string TestFunctionCall::formatFailure(
	ErrorReporter& _errorReporter,
	solidity::frontend::test::FunctionCall const& _call,
	bytes const& _output,
	bool _renderResult,
	bool _highlight
) const
{
	using Token = soltest::Token;
	stringstream os;
	os << formatToken(Token::Failure);
	if (!_output.empty())
		os << ", ";
	if (_renderResult)
		os << formatBytesParameters(
			_errorReporter,
			_output,
			_call.signature,
			_call.expectations.result,
			_highlight,
			true
		);
	else
		os << formatRawParameters(_call.expectations.result);
	return os.str();
}
string TestFunctionCall::formatRawParameters(
	solidity::frontend::test::ParameterList const& _params,
	std::string const& _linePrefix
) const
{
	stringstream os;
	for (auto const& param: _params)
		if (!param.rawString.empty())
		{
			if (param.format.newline)
				os << endl << _linePrefix << "// ";
			for (auto const c: param.rawString)
				os << (c >= ' ' ? string(1, c) : "\\x" + toHex(static_cast(c)));
			if (¶m != &_params.back())
				os << ", ";
		}
	return os.str();
}
void TestFunctionCall::reset()
{
	m_rawBytes = bytes{};
	m_failure = true;
	m_calledNonExistingFunction = false;
}
bool TestFunctionCall::matchesExpectation() const
{
	return m_failure == m_call.expectations.failure && m_rawBytes == m_call.expectations.rawBytes();
}