/*
   base/cargo.cc
   This file is part of the Osirion project and is distributed under
   the terms and conditions of the GNU General Public License version 2
*/

#include <assert.h>
#include <cmath>

#include "base/game.h"
#include "base/cargo.h"
#include "base/cargopod.h"
#include "filesystem/inifile.h"
#include "auxiliary/functions.h"
#include "sys/sys.h"

namespace game
{

core::InfoType *Cargo::cargo_infotype = 0;

// loads cargo types from ini file
bool Cargo::init()
{

	// initialize commodities InfoType
	Cargo::cargo_infotype = new core::InfoType("cargo");
	
	filesystem::IniFile cargoini;
	cargoini.open("cargo");
	if (!cargoini.is_open()) {
		con_error << "Could not open " << cargoini.name() << "!" << std::endl;
		return false;
	}

	con_print << "^BLoading cargo..." << std::endl;
	
	size_t count = 0;
	
	Cargo *cargo = 0;
	std::string str;
	long l;
	float f;
	
	while (cargoini.getline()) {
		if (cargoini.got_key()) {
		
			if (cargoini.section().compare("cargo") == 0) {
				if (cargoini.got_key_label("label", str)) {
					cargo->set_label(std::string(str));
					count++;
					continue;
					
				} else if (cargoini.got_key_string("name", str)) {
					cargo->set_name(str);
					continue;

				} else if (cargoini.got_key_string("info", str)) {
					cargo->add_text(str);
					continue;
					
				} else if (cargoini.got_key_string("model", str)) {
					cargo->set_modelname(str);
					continue;
							
				} else if (cargoini.got_key_long("price", l)) {
					cargo->set_price(l);
					continue;
					
				} else if (cargoini.got_key_float("volume", f)) {
					cargo->set_volume(f);
					continue;
					
				} else {
					cargoini.unkown_key();
				}
			}
			
		} else if (cargoini.got_section()) {
		
			if (cargoini.got_section("cargo")) {
				cargo = new Cargo();
				
			} else if (cargoini.got_section()) {
				cargoini.unknown_section();
			}		
		}
	}
	
	// add cargo infos
	con_debug << "  " << cargoini.name() << " " << count << " cargo types" << std::endl;
	
	cargoini.close();
	return true;
}

/* ---- class Cargo -------------------------------------------- */

Cargo::Cargo() : core::Info(cargo_infotype)
{
	set_volume(1);
}

Cargo::~Cargo()
{
}

Cargo *Cargo::find(const std::string & label)
{
	if (!label.size()) {
		return 0;
	}
	
	return (Cargo *) core::Info::find(cargo_infotype, label);
}

// main 'sell cargo' function
void Cargo::sell(core::EntityControlable *seller, core::Entity *buyer, const int amount)
{
	if (!buyer || !seller)
		return;
	
	// can only sell at planets and stations
	if ((buyer->moduletype() != station_enttype) && (buyer->moduletype() != planet_enttype)) {
		seller->owner()->send("^WCan not sell here");
		return;
	}
	
	if (!seller->owner()) 
		return;
	
	if (!buyer->inventory() || !seller->inventory()) {
		seller->owner()->send("^WCan not sell here");
		return;
	}
	
	if (!amount) {
		return;
	}
	
	// seller is the player
	core::Item *seller_item = seller->inventory()->find(this);
	if (!seller_item) {
		if (seller->owner()) {
			seller->owner()->send("^WYou do not own any " + name());
		}
		return;	
	}
	
	// buyer is the station or planer
	core::Item *buyer_item = buyer->inventory()->find(this);
	if (!buyer_item) {
		seller->owner()->send("^W" + buyer->name() + " ^Bdoes not buy " + name());
		return;
	}

	int negotiated_amount = amount;
	if (negotiated_amount < 0) {
		negotiated_amount = seller_item->amount();
	} else if (negotiated_amount > seller_item->amount()) {
		negotiated_amount = seller_item->amount();
	}
		
	int negotiated_price = buyer_item->price();
	
	seller_item->dec_amount(negotiated_amount);
	seller->owner()->set_credits(seller->owner()->credits() + negotiated_price * negotiated_amount);
	seller->owner()->set_dirty();
	seller->inventory()->set_dirty();
	
	if (buyer_item->amount() >= 0) {
		buyer_item->inc_amount(negotiated_amount);
		buyer->inventory()->set_dirty();
	}
	
	// send a cargo purchased message
	std::stringstream msgstr;
	msgstr << "^BSold " << negotiated_amount << " " << aux::plural("unit", negotiated_amount) << " of " << name() << " for " << negotiated_price * negotiated_amount << " credits";
	seller->owner()->send(msgstr.str());
	seller->owner()->sound("game/buy");
	
}

// main 'buy cargo' function
void Cargo::buy(core::EntityControlable *buyer, core::Entity *seller, const int amount)
{
	if (!buyer || !seller)
		return;

	// can only buy at planets and stations
	if ((seller->moduletype() != station_enttype) && (seller->moduletype() != planet_enttype)) {
		buyer->owner()->send("^WCan not buy here");
		return;
	}
	
	if (!buyer->owner()) 
		return;
	
	if (!buyer->inventory() || !seller->inventory()) {
		buyer->owner()->send("^WCan not buy here");
		return;
	}
	
	if (!amount) {
		return;
	}

	// seller is the station or planet
	core::Item *seller_item = seller->inventory()->find(this);	
	if (!seller_item) {
		if (buyer->owner()) {
			buyer->owner()->send("^W" + seller->name() + " ^Bdoes not sell " + name());
		}
		return;	
	} else {
		assert(seller_item->info() == this);
	}
	
	int negotiated_amount = amount;
	int negotiated_price = seller_item->price();
	long cash = buyer->owner()->credits();
	
	// check if the player has enough cash
	if (negotiated_price > 0) {
		
		// negative amount means 'as much as possible'
		if (negotiated_amount < 0) {
			negotiated_amount = cash / negotiated_price;
		}
		
		// maximum amount the player can afford
		if (cash < negotiated_amount * negotiated_price) {
			negotiated_amount = cash / negotiated_price;
		}
		
		if (negotiated_amount < 1) {
			buyer->owner()->send("^WCan not afford transaction!");
			return;
		}
	}
	
	// check cargo size - ignore zero volume cargo
	if (volume() > 0 ) {
		
		// maximum cargo size
		if (negotiated_amount * volume() > buyer->inventory()->capacity_available()) {
			negotiated_amount = (int)floorf(buyer->inventory()->capacity_available() / volume());
		}
		
		if (negotiated_amount < 1) {
			buyer->owner()->send("^WNot enough cargo space available!");
			return;
		}
	}
	
	if (negotiated_amount <= 0) {
		// unlimited amount of zero-cost cargo
		buyer->owner()->send("^WNo unlimited amounts of zero-cost cargo available!");
		return;
	}
	
	// if amount is set to -1. the base has a limitless supply
	
	if (seller_item->amount() == 0) {
		buyer->owner()->send("^WCargo not available!");
		return;
		
	} else if (seller_item->amount() > 0) {
		
		if (negotiated_amount  > seller_item->amount()) {
			negotiated_amount = seller_item->amount();
		}
		
		seller_item->dec_amount(negotiated_amount);
		seller->inventory()->set_dirty();
	}
	
	// buyer is the player
	core::Item *buyer_item = buyer->inventory()->find(this);
	if (!buyer_item) {
		buyer_item = new core::Item(this);
		buyer->inventory()->add(buyer_item);
	} else {
		assert(buyer_item->info() == this);	
	}	
	buyer_item->inc_amount(negotiated_amount);
	buyer->owner()->set_credits(buyer->owner()->credits() - negotiated_price * negotiated_amount);
	buyer->owner()->set_dirty();
	buyer->inventory()->set_dirty();
	
	// send a cargo purchased message
	std::stringstream msgstr;
	msgstr << "^BPurchased " << negotiated_amount << " " << aux::plural("unit", negotiated_amount) << " of " << name() << " for " << negotiated_price * negotiated_amount << " credits";
	buyer->owner()->send(msgstr.str());
	buyer->owner()->sound("game/buy");
}

// main 'eject cargo' function
void Cargo::eject(core::EntityControlable *ejector, const int amount)
{
	if (!ejector->inventory()) {
		return;
	}
	
	if (!amount) {
		return;
	}
	
	if ((ejector->state() == core::Entity::Jump) || (ejector->state() == core::Entity::JumpInitiate)) {
		if (ejector->owner()) {
			ejector->owner()->send("^WCan not eject while hyperspace jump drive is active");
		}
		return;
	}
	
	// find the cargo in the inventory
	core::Item *item = ejector->inventory()->find(this);	
	if (!item || !item->amount()) {
		if (ejector->owner()) {
			ejector->owner()->send("^WYou do not own any " + name());
		}
		return;	
	} else {
		assert(item->info() == this);
	}	
	
	int negotiated_amount = amount;
	if (negotiated_amount < 0) {
		negotiated_amount = item->amount();
	} else if (negotiated_amount > item->amount()) {
		if (ejector->owner()) {
			std::stringstream msgstr;
			msgstr << "^WYou only own " << item->amount() << " " << aux::plural("unit", negotiated_amount) << " of " << name();
			ejector->owner()->send(msgstr.str());
		}
		return;	
	}
	
	item->dec_amount(negotiated_amount);
	ejector->inventory()->set_dirty();
	
	if (ejector->state() == core::Entity::Docked) {
		std::stringstream msgstr;
		if (ejector->owner()) {
			msgstr << "^BDestroyed " << negotiated_amount << " " << aux::plural("unit", negotiated_amount) << " of " << name();
			ejector->owner()->send(msgstr.str());
			ejector->owner()->sound("game/eject");
		}
		return;
	}
	
	// create cargo pod	
	CargoPod *pod = new CargoPod();
	
	pod->set_color(ejector->color());
	pod->set_color_second(ejector->color_second());
	pod->set_zone(ejector->zone());
	pod->set_location(ejector->location() + ejector->axis().up() * ejector->radius());
	pod->set_axis(ejector->axis());
	
	// add loot to inventory
	pod->set_inventory(new core::Inventory());
	pod->inventory()->set_capacity(item->info()->volume() * negotiated_amount);
	
	core::Item *loot = new core::Item(item->info());
	loot->set_amount(negotiated_amount);
	
	pod->inventory()->add(loot);
	pod->inventory()->set_dirty();
	
	if (ejector->owner()) {
		std::stringstream msgstr;
		msgstr << "^BEjected " << negotiated_amount << " " << aux::plural("unit", negotiated_amount) << " of " << name();
		ejector->owner()->send(msgstr.str());
		ejector->owner()->sound("game/eject");
	}
	pod->reset();
}

void Cargo::list()
{
	core::Info::list(cargo_infotype);
}

} // namespace game