/*
 * arcane - A rogue-like game engine
 * Copyright (C) 2005  Petr Baudis <pasky@ucw.cz>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of version 2 of the GNU General Public License as
 * published by the Free Software Foundation.
 *
 * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

#include "cinfo.h"
#include "creature.h"
#include "item.h"
#include "game.h"
#include "term.h"


void
Creature::recalc_stats()
{
	hp_ = max(5, to_ * 3 / 2);
	speed_ = cinfo_.speed + (dx_ - 10) / 5;
}

Creature::Creature(CreatureInfo &cinfo, Level *level, unsigned x, unsigned y)
	: MapObject(level, x, y, cinfo.symbol, cinfo.color), cinfo_(cinfo),
	  xpticker_(0)
{
	st_ = dice(cinfo_.st);
	dx_ = dice(cinfo_.dx);
	to_ = dice(cinfo_.to);
	pe_ = dice(cinfo_.pe);
	Dice xpdice = {1,4,22};
	xp_ = dice(xpdice);

	tname_ = "the " + name();

	height_ = cinfo_.height + (st_ - 10) * 2 - (dx_ - 10) / 2;
	weight_ = cinfo_.weight + (to_ - 10) * 2 + (st_ - 10) - (dx_ - 10);

	recalc_stats();
	maxhp_ = hp_;
	healrate_ = max(5, 12 - to() / 5);
	healticker_ = 0;

	if (cinfo.flags & CF_INVISIBLE)
		visible(false);

	this->level()->enter(this);
}

Creature::~Creature()
{
	this->level()->leave(this);
}

void
Creature::tick(Game &game)
{
	if (level() != game.player().level())
		throw;

	if (hp_ < maxhp_) {
		healticker_++;
		if (healticker_ >= healrate_) {
			healticker_ = 0;
			hp_++;
		}
	}

	if (xpticker_ > 0)
		xpticker_--;
}

void
Creature::move(enum Direction dir)
{
	unsigned x = this->x(), y = this->y();
	Creature *c;

	level()->in_dir(x, y, dir);
	if ((c = (*level())(x, y).occupied()))
		throw X_creatdir(dir, c);

	MapObject::move(dir);
}


void
Creature::pick()
{
	Tile &t = (*level())(x(), y());
	for (std::list<MapObject *>::const_iterator obj = t.objs.begin();
	     obj != t.objs.end(); obj++) {
		ItemOnMap *i = dynamic_cast<ItemOnMap *>(*obj);
		if (i) {
			// dynamic_cast succeeded, this is an item!
			Item *item = i->item();
			item->get();
			i->put();
			backpack_push(item);
			item->put();
			return;
		}
	}
}

void
Creature::drop()
{
	Item *item = backpack_pop();
	if (!item) return;
	new ItemOnMap(item, level(), x(), y());
}

void
Creature::backpack_push(Item *item)
{
	item->get();
	backpack_.push(item);
}

Item *
Creature::backpack_pop()
{
	if (backpack_.empty())
		return NULL;
	Item *i = backpack_.top(); backpack_.pop();
	return i;
}


int
Creature::dv() const
{
	return max(1, (dx_ / 5 + st_ / 7) * max(160, logxp()) / 160);
}

int
Creature::pv() const
{
	return max(1, to_ / 5);
}

int
Creature::av() const
{
	return max(1, (st_ / 4 + dx_ / 8) * max(160, logxp()) / 160);
}

long
Creature::logxp() const
{
	// stolen from ADOM
	static const xp_T xps[] = {
		194, 368, 699, 1258, 2264, 3962, 6933, 11439, 17730, 27481,
		42595, 66022, 95731, 138809, 187392, 252979, 341521, 461053,
		622421, 840268, 1134361, 1361233, 1633479, 1960174, 2352208,
		2822649, 3387178, 3871060, 4424068, 5056077, 5778373, 6603854,
		7264239, 7990662, 8789728, 9668700, 10313280, 11000832,
		11734220, 12516501, 13350934, 14240996, 15190395, 15949914,
		16747409, 17584779, 18464017, 19387217, 20356577
	};
	size_t xps_len = sizeof(xps) / sizeof(xps[0]);
	xp_T xp = this->xp();

	unsigned i = 0;
	while (i < xps_len && xp > xps[i])
		i++;
	if (i == 0)
		return xp;
	return i * 200 + (xp - xps[i - 1]) * 200 / ((i == xps_len ? xps[i - 1] * 2 : xps[i]) - xps[i - 1]);
}

xp_T
Creature::xp_worth_attack()
{
	xp_T xp = pe();
	xp += max(dv() * 2 + pv() / 2 + av() - xpticker_ / 200, 1) * (maxhp() + hp() / 2) * speed() / 200;
	if (!this->player()) xpticker_ += 100;
	return xp / 3;
}

xp_T
Creature::xp_worth_defend()
{
	xp_T xp = pe();
	xp += max(dv() + pv() / 2 + av() * 2 - xpticker_ / 200, 1) * (maxhp() + hp() / 4) * speed() / 200;
	if (!this->player()) xpticker_ += 50;
	return xp / 3;
}

void
Creature::xp_gain(xp_T xp)
{
	if (xpticker_ > 0)
		xpticker_ -= xp;
	xp_ += xp;
}

void
Creature::die(std::string &msg)
{
	this->put();
}

bool
Creature::hit(Creature &by, int hit, std::string &msg)
{
	if (hit <= 0) {
		msg += " but do";
		if (by.player()) msg += "es";
		msg += " not manage to harm ";
		msg += this->player() ? "you" : "it";
		return false;
	}
	hp_ -= hit;
	if (hp_ <= 0) {
		if (!this->player()) {
			msg += " and kill";
			if (!by.player()) msg += "s";
			msg += " it";
		}
		this->die(msg);
		return true;
	}
	return false;
}

Creature *
fight(Creature &a, Creature &b, std::string &msg, unsigned long flags)
{
	if (b.karmic() && rand(3) == 0) {
		if (a.player()) {
			msg += "Your ";
		} else {
			msg += a.tname();
			msg += "'s ";
		}
		msg += "hand somehow fuzzily slips down the ";
		msg += b.name();
		msg += "'s blurry skin";
		if (rand(6) != 0) {
			msg += " and does not manage to do any harm.";
		} else {
			msg += ", and suddenly some strange force curls it up! ";
			fight(a, a, msg, FF_BYPASS_DV);
		}
		return &b;
	}

	Dice adice = {1,2,2}, ddice = {1,1,2};
	int anum = a.av() + dice(adice);
	int dnum = flags & FF_BYPASS_DV ? 0 : b.dv() + dice(ddice);
	if (dnum < anum) {
		int pnum = flags & FF_BYPASS_PV ? 0 : b.pv();
		anum -= dnum / 2 + pnum;
		if (a.player()) {
			msg += "You hit ";
		} else {
			msg += ucfirst(a.tname());
#ifdef GOD
			msg += tostr(a.av());
#endif
			msg += " hits ";
		}
		msg += b.player() ? "you" : b.tname();
#ifdef GOD
		msg += tostr(b.dv()); msg += "/"; msg += tostr(b.pv()); msg += ":" + tostr(b.hp());
#endif
		if (anum > 0)
			a.xp_gain(b.xp_worth_attack() * anum / b.maxhp());
		b.hit(a, anum, msg);
		msg += ".";
		return &a;
	} else {
		if (a.player()) {
			msg += "You miss ";
		} else {
			msg += ucfirst(a.tname());
#ifdef GOD
			msg += tostr(a.av());
#endif
			msg += " misses ";
		}
		msg += b.player() ? "you" : b.tname();
#ifdef GOD
		msg += tostr(b.dv()); msg += "/"; msg += tostr(b.pv()); msg += ":" + tostr(b.hp());
#endif
		b.xp_gain(a.xp_worth_defend() / 20);
		msg += ".";
		return &b;
	}
}
