/*
 * JsonComparer.cpp, part of VCMI engine
 *
 * Authors: listed in file AUTHORS in main folder
 *
 * License: GNU General Public License v2.0 or later
 * Full text of license available in license.txt file, in main folder
 *
 */
#include "StdInc.h"

#include "JsonComparer.h"

#include "../lib/ScopeGuard.h"

//JsonComparer
JsonComparer::JsonComparer(bool strict_)
	: strict(strict_)
{

}

vstd::ScopeGuard<JsonComparer::TScopeGuard> JsonComparer::pushName(const std::string & name)
{
	namePath.push_back(name);
	return vstd::makeScopeGuard<TScopeGuard>([this](){namePath.pop_back();});
}

std::string JsonComparer::buildMessage(const std::string & message)
{
	std::stringstream buf;

	for(auto & s : namePath)
		buf << s << "|";
	buf << " " << message;
	return buf.str();
}

bool JsonComparer::isEmpty(const JsonNode & value)
{
	switch (value.getType())
	{
	case JsonNode::JsonType::DATA_NULL:
		return true;
	case JsonNode::JsonType::DATA_BOOL:
		return !value.Bool();
	case JsonNode::JsonType::DATA_FLOAT:
		return value.Float() == 0;
	case JsonNode::JsonType::DATA_INTEGER:
		return value.Integer() == 0;
	case JsonNode::JsonType::DATA_STRING:
		return value.String() == "";
	case JsonNode::JsonType::DATA_VECTOR:
		return value.Vector().empty();
	case JsonNode::JsonType::DATA_STRUCT:
		return value.Struct().empty();
		break;
	default:
		EXPECT_TRUE(false) << "Unknown Json type";
		return false;
	}
}

void JsonComparer::check(const bool condition, const std::string & message)
{
	if(!condition)
	{
		if(strict)
		{
			GTEST_FAIL() << buildMessage(message);
		}
		else
		{
			ADD_FAILURE() << buildMessage(message);
		}
	}
}

void JsonComparer::checkEqualInteger(const si64 actual, const si64 expected)
{
	if(actual != expected)
	{
		check(false, boost::str(boost::format("'%d' != '%d'") % actual % expected));
	}
}

void JsonComparer::checkEqualFloat(const double actual, const double expected)
{
	if(std::abs(actual - expected) > 1e-8)
	{
		check(false, boost::str(boost::format("'%d' != '%d' (diff %d)") % actual % expected % (expected - actual)));
	}
}

void JsonComparer::checkEqualString(const std::string & actual, const std::string & expected)
{
	if(actual != expected)
	{
		check(false, boost::str(boost::format("'%s' != '%s'") % actual % expected));
	}
}

void JsonComparer::checkEqualJson(const JsonMap & actual, const JsonMap & expected)
{
	for(const auto & p : expected)
		checkStructField(actual, p.first, p.second);
	for(const auto & p : actual)
		checkExcessStructField(p.second, p.first, expected);
}

void JsonComparer::checkEqualJson(const JsonVector & actual, const JsonVector & expected)
{
	check(actual.size() == expected.size(), "size mismatch");

	size_t sz = std::min(actual.size(), expected.size());

	for(size_t idx = 0; idx < sz; idx ++)
	{
		auto guard = pushName(boost::to_string(idx));

		checkEqualJson(actual.at(idx), expected.at(idx));
	}
}

void JsonComparer::checkEqualJson(const JsonNode & actual, const JsonNode & expected)
{
	//name has been pushed before

	const bool validType = actual.getType() == expected.getType();

	//do detail checks avoiding assertions in JsonNode

	if(validType)
	{
		switch (actual.getType())
		{
		case JsonNode::JsonType::DATA_NULL:
			break; //do nothing
		case JsonNode::JsonType::DATA_BOOL:
			check(actual.Bool() == expected.Bool(), "mismatch");
			break;
		case JsonNode::JsonType::DATA_FLOAT:
			checkEqualFloat(actual.Float(),expected.Float());
			break;
		case JsonNode::JsonType::DATA_STRING:
			checkEqualString(actual.String(),expected.String());
			break;
		case JsonNode::JsonType::DATA_VECTOR:
			checkEqualJson(actual.Vector(), expected.Vector());
			break;
		case JsonNode::JsonType::DATA_STRUCT:
			checkEqualJson(actual.Struct(), expected.Struct());
			break;
		case JsonNode::JsonType::DATA_INTEGER:
			checkEqualInteger(actual.Integer(), expected.Integer());
			break;
		default:
			check(false, "Unknown Json type");
			break;
		}
	}
	else if(actual.isNumber() && expected.isNumber())
	{
		checkEqualFloat(actual.Float(),expected.Float());
	}
	else
	{
		check(false, "type mismatch. \n expected:\n"+expected.toJson(true)+"\n actual:\n" +actual.toJson(true));
	}
}

void JsonComparer::checkExcessStructField(const JsonNode & actualValue, const std::string & name, const JsonMap & expected)
{
	auto guard = pushName(name);

	if(!vstd::contains(expected, name))
		check(isEmpty(actualValue), "excess");
}

void JsonComparer::checkStructField(const JsonMap & actual, const std::string & name, const JsonNode & expectedValue)
{
	auto guard = pushName(name);

	if(!vstd::contains(actual, name))
		check(isEmpty(expectedValue), "missing");
	else
		checkEqualJson(actual.at(name), expectedValue);
}

void JsonComparer::compare(const std::string & name, const JsonNode & actual, const JsonNode & expected)
{
	auto guard = pushName(name);
	checkEqualJson(actual, expected);
}