/*
 * This file is part of OpenTTD.
 * OpenTTD 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, version 2.
 * OpenTTD 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 OpenTTD. If not, see <http://www.gnu.org/licenses/>.
 */

/** @file script_event_types.cpp Implementation of all EventTypes. */

#include "../../stdafx.h"
#include "script_event_types.hpp"
#include "script_vehicle.hpp"
#include "script_log.hpp"
#include "../../strings_func.h"
#include "../../settings_type.h"
#include "../../engine_base.h"
#include "../../articulated_vehicles.h"
#include "../../string_func.h"
#include "../../economy_cmd.h"
#include "../../engine_cmd.h"
#include "table/strings.h"

#include "../../safeguards.h"

bool ScriptEventEnginePreview::IsEngineValid() const
{
	const Engine *e = ::Engine::GetIfValid(this->engine);
	return e != nullptr && e->IsEnabled();
}

char *ScriptEventEnginePreview::GetName()
{
	if (!this->IsEngineValid()) return nullptr;

	::SetDParam(0, this->engine);
	return GetString(STR_ENGINE_NAME);
}

CargoID ScriptEventEnginePreview::GetCargoType()
{
	if (!this->IsEngineValid()) return CT_INVALID;
	CargoArray cap = ::GetCapacityOfArticulatedParts(this->engine);

	CargoID most_cargo = CT_INVALID;
	uint amount = 0;
	for (CargoID cid = 0; cid < NUM_CARGO; cid++) {
		if (cap[cid] > amount) {
			amount = cap[cid];
			most_cargo = cid;
		}
	}

	return most_cargo;
}

int32 ScriptEventEnginePreview::GetCapacity()
{
	if (!this->IsEngineValid()) return -1;
	const Engine *e = ::Engine::Get(this->engine);
	switch (e->type) {
		case VEH_ROAD:
		case VEH_TRAIN: {
			CargoArray capacities = GetCapacityOfArticulatedParts(this->engine);
			for (CargoID c = 0; c < NUM_CARGO; c++) {
				if (capacities[c] == 0) continue;
				return capacities[c];
			}
			return -1;
		}

		case VEH_SHIP:
		case VEH_AIRCRAFT:
			return e->GetDisplayDefaultCapacity();

		default: NOT_REACHED();
	}
}

int32 ScriptEventEnginePreview::GetMaxSpeed()
{
	if (!this->IsEngineValid()) return -1;
	const Engine *e = ::Engine::Get(this->engine);
	int32 max_speed = e->GetDisplayMaxSpeed(); // km-ish/h
	if (e->type == VEH_AIRCRAFT) max_speed /= _settings_game.vehicle.plane_speed;
	return max_speed;
}

Money ScriptEventEnginePreview::GetPrice()
{
	if (!this->IsEngineValid()) return -1;
	return ::Engine::Get(this->engine)->GetCost();
}

Money ScriptEventEnginePreview::GetRunningCost()
{
	if (!this->IsEngineValid()) return -1;
	return ::Engine::Get(this->engine)->GetRunningCost();
}

int32 ScriptEventEnginePreview::GetVehicleType()
{
	if (!this->IsEngineValid()) return ScriptVehicle::VT_INVALID;
	switch (::Engine::Get(this->engine)->type) {
		case VEH_ROAD:     return ScriptVehicle::VT_ROAD;
		case VEH_TRAIN:    return ScriptVehicle::VT_RAIL;
		case VEH_SHIP:     return ScriptVehicle::VT_WATER;
		case VEH_AIRCRAFT: return ScriptVehicle::VT_AIR;
		default: NOT_REACHED();
	}
}

bool ScriptEventEnginePreview::AcceptPreview()
{
	if (!this->IsEngineValid()) return false;
	return ScriptObject::Command<CMD_WANT_ENGINE_PREVIEW>::Do(this->engine);
}

bool ScriptEventCompanyAskMerger::AcceptMerger()
{
	return ScriptObject::Command<CMD_BUY_COMPANY>::Do((::CompanyID)this->owner);
}

ScriptEventAdminPort::ScriptEventAdminPort(const std::string &json) :
		ScriptEvent(ET_ADMIN_PORT),
		json(json)
{
}

#define SKIP_EMPTY(p) while (*(p) == ' ' || *(p) == '\n' || *(p) == '\r') (p)++;
#define RETURN_ERROR(stack) { ScriptLog::Error("Received invalid JSON data from AdminPort."); if (stack != 0) sq_pop(vm, stack); return nullptr; }

SQInteger ScriptEventAdminPort::GetObject(HSQUIRRELVM vm)
{
	const char *p = this->json.c_str();

	if (this->ReadTable(vm, p) == nullptr) {
		sq_pushnull(vm);
		return 1;
	}

	return 1;
}

const char *ScriptEventAdminPort::ReadString(HSQUIRRELVM vm, const char *p)
{
	const char *value = p;

	bool escape = false;
	for (;;) {
		if (*p == '\\') {
			escape = true;
			p++;
			continue;
		}
		if (*p == '"' && escape) {
			escape = false;
			p++;
			continue;
		}
		escape = false;

		if (*p == '"') break;
		if (*p == '\0') RETURN_ERROR(0);

		p++;
	}

	size_t len = p - value;
	sq_pushstring(vm, value, len);
	p++; // Step past the end-of-string marker (")

	return p;
}

const char *ScriptEventAdminPort::ReadTable(HSQUIRRELVM vm, const char *p)
{
	sq_newtable(vm);

	SKIP_EMPTY(p);
	if (*p++ != '{') RETURN_ERROR(1);

	for (;;) {
		SKIP_EMPTY(p);
		if (*p++ != '"') RETURN_ERROR(1);

		p = ReadString(vm, p);
		if (p == nullptr) {
			sq_pop(vm, 1);
			return nullptr;
		}

		SKIP_EMPTY(p);
		if (*p++ != ':') RETURN_ERROR(2);

		p = this->ReadValue(vm, p);
		if (p == nullptr) {
			sq_pop(vm, 2);
			return nullptr;
		}

		sq_rawset(vm, -3);
		/* The key (-2) and value (-1) are popped from the stack by squirrel. */

		SKIP_EMPTY(p);
		if (*p == ',') {
			p++;
			continue;
		}
		break;
	}

	SKIP_EMPTY(p);
	if (*p++ != '}') RETURN_ERROR(1);

	return p;
}

const char *ScriptEventAdminPort::ReadValue(HSQUIRRELVM vm, const char *p)
{
	SKIP_EMPTY(p);

	if (strncmp(p, "false", 5) == 0) {
		sq_pushinteger(vm, 0);
		return p + 5;
	}
	if (strncmp(p, "true", 4) == 0) {
		sq_pushinteger(vm, 1);
		return p + 4;
	}
	if (strncmp(p, "null", 4) == 0) {
		sq_pushnull(vm);
		return p + 4;
	}

	switch (*p) {
		case '"': {
			/* String */
			p = ReadString(vm, ++p);
			if (p == nullptr) return nullptr;

			break;
		}

		case '{': {
			/* Table */
			p = this->ReadTable(vm, p);
			if (p == nullptr) return nullptr;

			break;
		}

		case '[': {
			/* Array */
			sq_newarray(vm, 0);

			/* Empty array? */
			const char *p2 = p + 1;
			SKIP_EMPTY(p2);
			if (*p2 == ']') {
				p = p2 + 1;
				break;
			}

			while (*p++ != ']') {
				p = this->ReadValue(vm, p);
				if (p == nullptr) {
					sq_pop(vm, 1);
					return nullptr;
				}
				sq_arrayappend(vm, -2);

				SKIP_EMPTY(p);
				if (*p == ',') continue;
				if (*p == ']') break;
				RETURN_ERROR(1);
			}

			p++;

			break;
		}

		case '1': case '2': case '3': case '4': case '5':
		case '6': case '7': case '8': case '9': case '0':
		case '-': {
			/* Integer */

			const char *value = p++;
			for (;;) {
				switch (*p++) {
					case '1': case '2': case '3': case '4': case '5':
					case '6': case '7': case '8': case '9': case '0':
						continue;

					default:
						break;
				}

				p--;
				break;
			}

			int res = atoi(value);
			sq_pushinteger(vm, (SQInteger)res);

			break;
		}

		default:
			RETURN_ERROR(0);
	}

	return p;
}

#undef SKIP_EMPTY
#undef RETURN_ERROR
