/*
 * Copyright (C) 2002-2004, 2006-2007 by the Widelands Development Team
 *
 * This program 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 2
 * of the License, or (at your option) any later version.
 *
 * This program 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 this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 */

#include <stdio.h>
#include "battle.h"
#include "editor_game_base.h"
#include "error.h"
#include "game.h"
#include "graphic.h"
#include "militarysite.h"
#include "player.h"
#include "profile.h"
#include "rendertarget.h"
#include "rgbcolor.h"
#include "soldier.h"
#include "transport.h"
#include "tribe.h"
#include "util.h"
#include "warehouse.h"
#include "wexception.h"
#include "attack_controller.h"


/*
==============================================================================

IdleSoldierSupply IMPLEMENTATION

==============================================================================
*/

class IdleSoldierSupply : public Supply {
	public:
		IdleSoldierSupply(Soldier * const);
		~IdleSoldierSupply();

		void set_economy(Economy* e);

	public:
		virtual PlayerImmovable* get_position(Game* g);
	virtual int get_amount(const int ware) const;
	virtual bool is_active() const throw ();

		virtual WareInstance* launch_item(Game* g, int ware) __attribute__ ((noreturn));
		virtual Worker* launch_worker(Game* g, int ware);

		virtual Soldier* launch_soldier(Game* g, int ware, Requeriments* req);
		virtual int get_passing_requeriments(Game* g, int ware, Requeriments* r);
		virtual void mark_as_used (Game* g, int ware, Requeriments* r);
	private:
	Soldier * m_soldier;
	Economy * m_economy;
};


/*
===============
IdleSoldierSupply::IdleSoldierSupply

Automatically register with the soldier's economy.
===============
*/
IdleSoldierSupply::IdleSoldierSupply(Soldier * const s) :
m_soldier(s), m_economy(0)
{set_economy(s->get_economy());}


/*
===============
IdleSoldierSupply::~IdleSoldierSupply

Automatically unregister from economy.
===============
*/
IdleSoldierSupply::~IdleSoldierSupply()
{
	set_economy(0);
}


/*
===============
IdleSoldierSupply::set_economy

Add/remove this supply from the Economy as appropriate.
===============
*/
void IdleSoldierSupply::set_economy(Economy* e)
{
	if (m_economy == e)
		return;

	if (m_economy)
		m_economy->remove_soldier_supply(m_soldier->get_owner()->get_tribe()->get_worker_index(m_soldier->get_name().c_str()), this);

	m_economy = e;

	if (m_economy)
		m_economy->add_soldier_supply(m_soldier->get_owner()->get_tribe()->get_worker_index(m_soldier->get_name().c_str()), this);
}


/*
===============
IdleSoldierSupply::get_position

Return the soldier's position.
===============
*/
PlayerImmovable* IdleSoldierSupply::get_position(Game* g)
{
	return m_soldier->get_location(g);
}


/*
===============
IdleSoldierSupply::get_amount

It's just the one soldier.
===============
*/
int IdleSoldierSupply::get_amount(const int ware) const {
	if (ware == m_soldier->get_owner()->get_tribe()->get_worker_index(m_soldier->get_name().c_str()))
		return 1;

	return 0;
}


/*
===============
IdleSoldierSupply::is_active

Idle soldiers are always active supplies, because they need to get into a
Warehouse ASAP.
===============
*/
bool IdleSoldierSupply::is_active() const throw () {return true;}


/*
===============
IdleSoldierSupply::launch_item
===============
*/
WareInstance* IdleSoldierSupply::launch_item(Game *, int)
{throw wexception("IdleSoldierSupply::launch_item() makes no sense.");}


/*
===============
IdleSodlierSupply::launch_worker
===============
*/
Worker* IdleSoldierSupply::launch_worker(Game* g, int ware)
{
	log ("IdleSoldierSupply::launch_worker() Warning something can go wrong around here.\n");

	return launch_soldier(g, ware, NULL);
}


/*
===============
IdleSodlierSupply::launch_soldier

No need to explicitly launch the soldier.
===============
*/
Soldier * IdleSoldierSupply::launch_soldier
(Game *, int ware, Requeriments * req)
{
	assert(ware == m_soldier->get_owner()->get_tribe()->get_worker_index(m_soldier->get_name().c_str()));

	if (req)
	{
		if (req->check (
				  m_soldier->get_level(atrHP),
		m_soldier->get_level(atrAttack),
		m_soldier->get_level(atrDefense),
		m_soldier->get_level(atrEvade)
							))
		{
			// Ensures that this soldier is used now
			m_soldier->mark (false);
			return m_soldier;
		}
		else
			throw wexception ("IdleSoldierSupply::launch_soldier Fails. Requeriments aren't accomplished.");
	}
	else
		return m_soldier;
}

/*
===============
IdleSodlierSupply::mark_as_used
===============
*/
void IdleSoldierSupply::mark_as_used(Game *, int ware, Requeriments * req) {
	assert(ware == m_soldier->get_owner()->get_tribe()->get_worker_index(m_soldier->get_name().c_str()));

	if (req)
	{
		if (req->check (
				  m_soldier->get_level(atrHP),
		m_soldier->get_level(atrAttack),
		m_soldier->get_level(atrDefense),
		m_soldier->get_level(atrEvade)
							))
		{
			// Ensures that this soldier has a request now
			m_soldier->mark (true);
		}
		else
			throw wexception ("IdleSoldierSupply::launch_soldier Fails. Requeriments aren't accomplished.");
	}
	else
		m_soldier->mark (true);
}


/*
===============
IdleSodlierSupply::get_passing_requeriments

No need to explicitly launch the soldier.
===============
*/
int IdleSoldierSupply::get_passing_requeriments
(Game *, int ware, Requeriments * req)
{
	assert(ware == m_soldier->get_owner()->get_tribe()->get_worker_index(m_soldier->get_name().c_str()));

	// Oops we find a marked soldied (in use)
	if (m_soldier->is_marked())
		return 0;

	if (!req)
		return 1;

	if (req->check (
			m_soldier->get_level(atrHP),
	m_soldier->get_level(atrAttack),
	m_soldier->get_level(atrDefense),
	m_soldier->get_level(atrEvade)
						))
		return 1;
	else
		return 0;
}




/*
==============================================================

SOLDIER DESCRIPTION IMPLEMENTATION

==============================================================
*/

/*
===============
Soldier_Descr::Soldier_Descr
Soldier_Descr::~Soldier_Descr
===============
*/
Soldier_Descr::Soldier_Descr
(const Tribe_Descr & tribe_descr, const std::string & soldier_name)
: Worker_Descr(tribe_descr, soldier_name)
{
	add_attribute(Map_Object::SOLDIER);
}

Soldier_Descr::~Soldier_Descr(void)
{
   m_hp_pics_fn.resize(0);
   m_attack_pics_fn.resize(0);
   m_defense_pics_fn.resize(0);
   m_evade_pics_fn.resize(0);
}

/**
===============
Soldier_Descr::parse

Parse carrier-specific configuration data
===============
*/
void Soldier_Descr::parse(const char *directory, Profile *prof, const EncodeData *encdata)
{
	Worker_Descr::parse(directory, prof, encdata);
   Section* sglobal=prof->get_section("global");

   // Parse hitpoints
   std::string hp=sglobal->get_safe_string("hp");
   std::vector<std::string> list;
   split_string(hp, &list, "-");
	if (list.size() != 2) throw wexception
		("Parse error in hp string: \"%s\" (must be \"min-max\")", hp.c_str());
   uint i=0;
   for(i=0; i<list.size(); i++)
      remove_spaces(&list[i]);
   char* endp;
   m_min_hp= strtol(list[0].c_str(),&endp, 0);
	if (endp and *endp)
      throw wexception("Parse error in hp string: %s is a bad value", list[0].c_str());
   m_max_hp = strtol(list[1].c_str(),&endp, 0);
	if (endp and *endp)
      throw wexception("Parse error in hp string: %s is a bad value", list[1].c_str());

   // Parse attack
   std::string attack=sglobal->get_safe_string("attack");
   list.resize(0);
   split_string(attack, &list, "-");
	if (list.size() != 2)
      throw wexception("Parse error in attack string: \"%s\" (must be \"min-max\")", attack.c_str());
   for(i=0; i<list.size(); i++)
      remove_spaces(&list[i]);
   m_min_attack= strtol(list[0].c_str(),&endp, 0);
	if (endp and *endp)
      throw wexception("Parse error in attack string: %s is a bad value", list[0].c_str());
   m_max_attack = strtol(list[1].c_str(),&endp, 0);
	if (endp and *endp)
      throw wexception("Parse error in attack string: %s is a bad value", list[1].c_str());

   // Parse defend
   m_defense=sglobal->get_safe_int("defense");

   // Parse evade
   m_evade=sglobal->get_safe_int("evade");

   // Parse increases per level
   m_hp_incr=sglobal->get_safe_int("hp_incr_per_level");
   m_attack_incr=sglobal->get_safe_int("attack_incr_per_level");
   m_defense_incr=sglobal->get_safe_int("defense_incr_per_level");
   m_evade_incr=sglobal->get_safe_int("evade_incr_per_level");

   // Parse max levels
   m_max_hp_level=sglobal->get_safe_int("max_hp_level");
   m_max_attack_level=sglobal->get_safe_int("max_attack_level");
   m_max_defense_level=sglobal->get_safe_int("max_defense_level");
   m_max_evade_level=sglobal->get_safe_int("max_evade_level");

   // Load the filenames
   m_hp_pics_fn.resize(m_max_hp_level+1);
   m_attack_pics_fn.resize(m_max_attack_level+1);
   m_defense_pics_fn.resize(m_max_defense_level+1);
   m_evade_pics_fn.resize(m_max_evade_level+1);
   char buffer[256];
   std::string dir=directory;
   dir+="/";
   for(i=0; i<=m_max_hp_level; i++) {
      sprintf(buffer, "hp_level_%i_pic", i);
      m_hp_pics_fn[i]=dir;
      m_hp_pics_fn[i]+=sglobal->get_safe_string(buffer);
   }
   for(i=0; i<=m_max_attack_level; i++) {
      sprintf(buffer, "attack_level_%i_pic", i);
      m_attack_pics_fn[i]=dir;
      m_attack_pics_fn[i]+=sglobal->get_safe_string(buffer);
   }
   for(i=0; i<=m_max_defense_level; i++) {
      sprintf(buffer, "defense_level_%i_pic", i);
      m_defense_pics_fn[i]=dir;
      m_defense_pics_fn[i]+=sglobal->get_safe_string(buffer);
   }
   for(i=0; i<=m_max_evade_level; i++) {
      sprintf(buffer, "evade_level_%i_pic", i);
      m_evade_pics_fn[i]=dir;
      m_evade_pics_fn[i]+=sglobal->get_safe_string(buffer);
   }
}

/**
 * Load the graphics
 */
void Soldier_Descr::load_graphics(void) {
   m_hp_pics.resize(m_max_hp_level+1);
   m_attack_pics.resize(m_max_attack_level+1);
   m_defense_pics.resize(m_max_defense_level+1);
   m_evade_pics.resize(m_max_evade_level+1);
   uint i;
   for(i=0; i<=m_max_hp_level; i++) {
      m_hp_pics[i]=g_gr->get_picture( PicMod_Game,  m_hp_pics_fn[i].c_str() );
   }
   for(i=0; i<=m_max_attack_level; i++) {
      m_attack_pics[i]=g_gr->get_picture( PicMod_Game,  m_attack_pics_fn[i].c_str() );
   }
   for(i=0; i<=m_max_defense_level; i++) {
      m_defense_pics[i]=g_gr->get_picture( PicMod_Game,  m_defense_pics_fn[i].c_str() );
   }
   for(i=0; i<=m_max_evade_level; i++) {
      m_evade_pics[i]=g_gr->get_picture( PicMod_Game,  m_evade_pics_fn[i].c_str() );
   }
   Worker_Descr::load_graphics();
}


/**
 * Get random animation of specified type
 */
uint Soldier_Descr::get_rand_anim(const char * const animation_name) const {
   // Todo: This is thought to get a random animation like attack_1 attack_2 attack_3 ...
   // Randimly trhought this method. By now only gets attack, but isn't very difficult
   // to remake allowing the attack_1 and so.
	return get_animation(animation_name);
}

/**
 * Create a new soldier
 */
Bob * Soldier_Descr::create_object() const {return new Soldier(*this);}

/*
==============================

IMPLEMENTATION

==============================
*/

/*
===============
Soldier::Soldier
Soldier::~Soldier
===============
*/
Soldier::Soldier(const Soldier_Descr & soldier_descr) : Worker(soldier_descr) {}
   // all done through init

Soldier::~Soldier()
{
}

/*
 * Initialize this soldier
 */
void Soldier::init(Editor_Game_Base* gg) {
	m_hp_level=0;
	m_attack_level=0;
	m_defense_level=0;
	m_evade_level=0;

	m_hp_max=0;
	m_min_attack=get_descr()->get_min_attack();
	m_max_attack=get_descr()->get_max_attack();
	m_defense=get_descr()->get_defense();
	m_evade=get_descr()->get_evade();
	Game * const game = dynamic_cast<Game * const>(gg);
	if (game) {
		int range=get_descr()->get_max_hp()-get_descr()->get_min_hp();
		m_hp_max = game->logic_rand() % range;
	}
	m_hp_current=m_hp_max;

	m_marked = false;

	Worker::init(gg);
}

/*
 * Set this soldiers level. Automatically sets the new values
 */
void Soldier::set_level
(const uint hp, const uint attack, const uint defense, const uint evade)
{
   set_hp_level(hp);
   set_attack_level(attack);
   set_defense_level(defense);
   set_evade_level(evade);
}
void Soldier::set_hp_level(const uint hp) {
   assert(hp>=m_hp_level && hp<=get_descr()->get_max_hp_level());

   while(m_hp_level<hp) {
      ++m_hp_level;
      m_hp_max+=get_descr()->get_hp_incr_per_level();
      m_hp_current+=get_descr()->get_hp_incr_per_level();
   }
}
void Soldier::set_attack_level(const uint attack) {
   assert(attack>=m_attack_level && attack<=get_descr()->get_max_attack_level());

   while(m_attack_level<attack) {
      ++m_attack_level;
      m_min_attack+=get_descr()->get_attack_incr_per_level();
      m_max_attack+=get_descr()->get_attack_incr_per_level();
   }
}
void Soldier::set_defense_level(const uint defense) {
   assert(defense>=m_defense_level && defense<=get_descr()->get_max_defense_level());

   while(m_defense_level<defense) {
      ++m_defense_level;
      m_defense+=get_descr()->get_defense_incr_per_level();
   }
}
void Soldier::set_evade_level(const uint evade) {
   assert(evade>=m_evade_level && evade<=get_descr()->get_max_evade_level());

   while(m_evade_level<evade) {
      ++m_evade_level;
      m_evade+=get_descr()->get_evade_incr_per_level();
   }
}

uint Soldier::get_level(const tAttribute at)
{
	switch (at)
	{
		case atrHP:        return m_hp_level;
		case atrAttack:    return m_attack_level;
		case atrDefense:   return m_defense_level;
		case atrEvade:     return m_evade_level;
      case atrTotal: return m_hp_level + m_attack_level + m_defense_level + m_evade_level;
	}
	throw wexception ("Soldier::get_level attribute not identified.)");
}

// Unsignedness ensures that we can only heal, don't hurt throught this method.
void Soldier::heal (const uint hp) {
   molog ("healing (%d+)%d/%d\n", hp, m_hp_current, m_hp_max);
	m_hp_current += hp;

	if (m_hp_current > m_hp_max)
		m_hp_current = m_hp_max;
}

/**
 * This only subs the specified number of hitpoints, don't do anything more.
 */
void Soldier::damage (const uint value)
{
   assert (m_hp_current > 0);

   molog ("damage %d(-%d)/%d\n", m_hp_current, value, m_hp_max);
   if (m_hp_current < value)
      m_hp_current = 0;
   else
      m_hp_current -= value;
}

/*
 * Draw this soldier. This basically draws him as a worker, but add hitpoints
 */
void Soldier::draw
(const Editor_Game_Base & game, RenderTarget & dst, const Point pos) const
{
	if (const uint anim = get_current_anim()) {

	const Point drawpos = calc_drawpos(game, pos);

		uint w, h;
		g_gr->get_animation_size(anim, game.get_gametime() - get_animstart(), w, h);

   // Draw energy bar
   // first: draw white sourrounding
		Rect r(Point(drawpos.x - w, drawpos.y - h - 7), w * 2, 5);
		dst.draw_rect(r, HP_FRAMECOLOR);
	// Draw the actual bar
	const float fraction = static_cast<float>(m_hp_current) / m_hp_max;
	//FIXME:
   //Draw bar in playercolor, should be removed when soldier is correctly painted
   RGBColor color(get_owner()->get_playercolor()->r(),
                  get_owner()->get_playercolor()->g(),
                  get_owner()->get_playercolor()->b()
                 );
   /*if (fraction <= 0.15)
		color = RGBColor(255, 0, 0);
	else if (fraction <= 0.5)
		color = RGBColor(255, 255, 0);
	else
		color = RGBColor(17,192,17);*/
		assert(2 <= r.w);
		assert(2 <= r.h);
	dst.fill_rect
			(Rect
			 (r + Point(1, 1), static_cast<int>(fraction * (r.w - 2)), r.h - 2),
		 color);

   // Draw information fields about levels
   // first, gather informations
	const uint hppic = get_hp_level_pic();
	const uint attackpic = get_attack_level_pic();
	const uint defensepic = get_defense_level_pic();
	const uint evadepic = get_evade_level_pic();
	uint hpw, hph, atw, ath, dew, deh, evw, evh;
	g_gr->get_picture_size(hppic,      hpw, hph);
	g_gr->get_picture_size(attackpic,  atw, ath);
	g_gr->get_picture_size(defensepic, dew, deh);
	g_gr->get_picture_size(evadepic,   evw, evh);

		dst.blit(r + Point      (0, -(hph + ath)), attackpic);
		dst.blit(r + Point(r.w / 2, -(evh + deh)), defensepic);
		dst.blit(r + Point      (0, -        hph), hppic);
		dst.blit(r + Point(r.w / 2, -        evh), evadepic);

	draw_inner(game, dst, drawpos);
	}
}

/**
 *
 *
 */
void Soldier::start_animation
(Editor_Game_Base* gg, const char * const animname, const uint time)
{
	Game * const game = dynamic_cast<Game * const>(gg);
	if (game) start_task_idle (game, get_descr()->get_rand_anim(animname), time);
}

/**
===============
Soldier::start_task_gowarehouse

Get the soldier to move to the nearest warehouse.
The soldier is added to the list of usable wares, so he may be reassigned to
a new task immediately.
===============
*/
void Soldier::start_task_gowarehouse() {
	assert(!m_supply);

	push_task(taskGowarehouse);

	m_supply = (IdleWorkerSupply*) new IdleSoldierSupply(this);
}

Bob::Task Soldier::taskMoveToBattle = {
   "moveToBattle",
   (Bob::Ptr)&Soldier::moveToBattleUpdate,
   (Bob::Ptr)&Soldier::moveToBattleSignal,
   0,
};

void Soldier::startTaskMoveToBattle(Game * g, Flag *, Coords coords) {
   log ("Soldier::startTaskMoveToBattle\n");
   push_task(taskMoveToBattle);

   State* s = get_state();

   // First step (we must exit from building), and set what is the target
   s->ivar1 = 1;
   s->ivar2 = 0;     // Not requested to attack (used by signal 'combat')
   s->objvar1 = get_location(g);   // objvar1 is the owner flag (where is attached the FRIEND military site)
   s->coords = coords; // Destination
}

void Soldier::moveToBattleUpdate(Game* g, State* state) {
   // See if soldier is at building and drop of it
   if (state->ivar1 == 1) {
      BaseImmovable* position = g->get_map()->get_immovable(get_position());

      if (position && position->get_type() == BUILDING) {
         // We are in our building, try to exit
         if (start_task_waitforcapacity(g, (Flag*)get_location(g)->get_base_flag()))
            return;
         state->ivar1 = 2;
         start_task_leavebuilding (g, 1);
         return;
      }
   }
   else {
      if (get_position() == state->coords) {
         if (state->ivar1 != 3)
            m_attack_ctrl->moveToReached(this);
         state->ivar1 = 3;
         start_task_idle(g,get_descr()->get_animation("idle"),1000);
         return;
      }
      if (!start_task_movepath(g,state->coords,0,get_descr()->get_right_walk_anims(does_carry_ware()))) {
         molog("[moveToBattleUpdate]: Couldn't find path to flag!\n");
         set_signal("fail");
         mark(false);
         pop_task();
         return;
      }
   }
}

void Soldier::moveToBattleSignal(Game * g, State *) {
   std::string signal = get_signal();
   set_signal("");

   log("moveToBattleSignal got signal: %s",signal.c_str());

   if (signal == "won_battle") {
      m_attack_ctrl->soldierWon(this);
      return;
   }
   else if (signal == "die") {
      m_attack_ctrl->soldierDied(this);
      return;
   }
   else if (signal == "return_home") {
      pop_task();
      startTaskMoveHome(g);
      return;
   }
}

Bob::Task Soldier::taskMoveHome = {
   "moveHome",

   (Bob::Ptr)&Soldier::moveHomeUpdate,
   (Bob::Ptr)&Soldier::moveHomeSignal,
   0,
};

void Soldier::startTaskMoveHome(Game* g) {
   log ("Soldier::startTaskMoveHome\n");
   push_task(taskMoveHome);

   State* s = get_state();
   s->ivar1 = 1;
   s->ivar2 = 0;
   s->objvar1 = get_location(g);
}

void Soldier::moveHomeUpdate(Game* g, State* state) {
   // Move home
   if (state->ivar1 == 1) {
      state->ivar1 = 2;
      start_task_return(g,false);
      return;
   }
   else {
      start_task_idle(g, 0, -1); // bind the worker into this house, hide him on the map
      mark (false);              // can be healed now
   }
}

void Soldier::moveHomeSignal(Game *, State *) {
   std::string signal = get_signal();
   set_signal("");

   log("moveToSignal got signal, don't know what to do with it.: %s",signal.c_str());
}

void Soldier::log_general_info(Editor_Game_Base* egbase)
{
   Worker::log_general_info(egbase);
   molog("[Soldier]\n");
   molog("Levels: %d/%d/%d/%d\n", m_hp_level, m_attack_level, m_defense_level, m_evade_level);
   molog ("HitPoints: %d/%d\n", m_hp_current, m_hp_max);
   molog ("Attack :  %d-%d\n", m_min_attack, m_max_attack);
   molog ("Defense : %d%%\n", m_defense);
   molog ("Evade:    %d%%\n", m_evade);
}
