/*
 * 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 <iostream>
#include <cstdlib>
#include <list>
#include <vector>
using namespace std;

#include "level.h"
#include "creature.h"
#include "item.h"
#include "monster.h"
#include "term.h"
#include "util.h"


Creature *
Tile::occupied() const
{
	for (std::list<MapObject *>::const_iterator obj = objs.begin();
	     obj != objs.end(); obj++) {
		Creature *m = dynamic_cast<Creature *>(*obj);
		if (m) {
			// dynamic_cast succeeded, this is a creature!
			return m;
		}
	}
	return NULL;
}

void
Tile::draw(Term &term, int base_x, int base_y) const
{
	if (!known()) return;

	term.color(COLOR_GRAY, illum() ? COLOR_BROWN : COLOR_BLACK)
	    .cursor(base_x, base_y)
	    .put((char) type());

	for (std::list<MapObject *>::const_iterator obj = objs.begin();
	     obj != objs.end(); obj++) {
#ifndef GOD
		Creature *m = dynamic_cast<Creature *>(*obj);
		if (m && !illum()) {
			continue;
		}
#endif
		(*obj)->draw(term, base_x, base_y);
	}
}

void
Tile::enter(MapObject *obj)
{
	objs.push_back(obj);
	if (obj->illum_range())
		obj->level()->add_illum(obj);
}

void
Tile::leave(MapObject *obj)
{
	if (obj->illum_range())
		obj->level()->del_illum(obj);
	objs.remove(obj);
}


Level::Level(int depth, int width, int height, int maxmonsters, int maxitems, int danger)
	: depth_(depth), danger_(danger < 0 ? depth_ : danger),
	  maxmonsters_(maxmonsters), maxitems_(maxitems),
	  life(), illum(),
	  map_(width, std::vector<Tile> (height)), map_view_(map_)
{
	char map[22][81] = {
#if 0
		"##################################################2#############################",
		"#.....#############...#####.......###############................###########...#",
		"#.....#############.#######.#.....###############.########.......####..........#",
		"#...........................#######................#######.......####.....##...#",
		"#.########.################.........##############.##################.....######",
		"#.####...#.####.......##################.....#####....................######...#",
		"#.##.....#.####..............................######.########.###############...#",
		"#.##.#####.###############.#############.....######.#######...##############...#",
		"#.##.#####.###############.#############............#######..................#.#",
		"#.##.#####.###############.################################...################.#",
		"#.#.....##........########.#############...........########.######...........#.#",
		"#.#.....#########........................#########..........#....#.........#.#.#",
		"#.#.......#######.#################.####.########..##########....#.........#.#.#",
		"#.#.....#.#######.#######.........#.####....####..###.....######.#.........#.#.#",
		"#.#######.........#######.........#.#######......####.##.........#.........#.#.#",
		"#.#.....#.#########.....#.........#.#################.######################.#.#",
		"#.#.......#########.....###########..........................................#.#",
		"#.#.....###########.................######################...#################.#",
		"#.#.....###########.....##################################...#################.#",
		"#.########################################################.###################.#",
		"#..............................................................................#",
		"################################################################################",
#else
		"################################################################################",
		"#.....###########......##########################...########################..##",
		"#.................................................#.......................##..##",
		"#.....###########......##########################...#####################.##...#",
		"#...###....######......##########.........#############.......###....####.####.#",
		"#..##........#####.##########...............##########...####.#......####......#",
		"#..#....................................................##....#.#...#####.####.#",
		"#..#....................................................##....#.#########.#....#",
		"#.###........################...............##########...######...........#.####",
		"#.#.###....###....###############.........#.###########.......###.........#..###",
		"#.#...######......#########################....##############.###.........#..###",
		"#.#.......................################..##.##############.###.........#...##",
		"#.#...............###.............########.##...###########...###.........#...##",
		"#.#######.###########.....#######..........#.....#####....#....##.........#...##",
		"#..#..###.###########..##########...########.....#####.##...#####.........##..##",
		"#......##.......#####..############.#########...######.##########.........##..##",
		"##....#######...#####..######...###.##################.#####################..##",
		"#.....#######...#####..#####...####.################...#####################.###",
		"#....###############....###...#####........................................#####",
		"#.....##############....##..........##########################.............#####",
		"##..#...................#...##################################.............#####",
		"################################################################################",
#endif
	};

	for (unsigned x = 0; x < map_.size(); x++) {
		for (unsigned y = 0; y < map_[x].size(); y++) {
			map_[x][y].type(Tile::Type(map[y][x]));
#ifdef GOD
			map_[x][y].known(true);
#endif
		}
	}

	total_creat_prob_ = recalc_creat_prob();
	total_item_prob_ = recalc_item_prob();
	spawn_monsters();
	scatter_items();
	recalc_illum();
}

void
Level::draw(Term &term, int base_x, int base_y) const
{
	for (unsigned x = 0; x < map_.size(); x++) {
		for (unsigned y = 0; y < map_[x].size(); y++) {
#ifdef GOD
			map_[x][y].draw(term, base_x + x, base_y + y);
#else
			map_view_[x][y].draw(term, base_x + x, base_y + y);
#endif
		}
	}
}

void
Level::enter(Creature *obj)
{
	life.push_back(obj);
}

void
Level::leave(Creature *obj)
{
	life.remove(obj);
}

void
Level::pretick(Game &game)
{
	if (life.size() < maxmonsters())
		try { spawn_monster(); } catch (X_monsterpack) {}
	recalc_illum();
}

void
Level::posttick(Game &game)
{
	recalc_illum();
}

void
Level::illum_hook(unsigned x, unsigned y, void *)
{
	map_[x][y].illum(true);
}

void
Level::recalc_illum()
{
	for (unsigned x = 0; x < map_.size(); x++) {
		for (unsigned y = 0; y < map_[x].size(); y++) {
			map_[x][y].illum(false);
		}
	}

	for (std::list<MapObject *>::const_iterator obj = illum.begin();
	     obj != illum.end(); obj++) {
		MapObject &i = **obj;
		int i2 = i.illum_range(); i2 *= i2;

		for (unsigned x = max(i.x() - i.illum_range(), 0); x < min(map_.size(), i.x() + i.illum_range()); x++) {
			for (unsigned y = max(i.y() - i.illum_range(), 0); y < min(map_.size(), i.y() + i.illum_range()); y++) {
				int dx = i.x() - x, dy = i.y() - y;
				if (dx * dx + dy * dy <= i2 && trace_visibility(i.x(), i.y(), x, y, &Level::illum_hook, NULL)) {
					map_[x][y].illum(true);
				}
			}
		}
	}
}

unsigned long
Level::recalc_creat_prob()
{
	// idea from qhack
	unsigned long total = 0;
	for (int m = 0; m < cinfos; m++)
		total += creat_prob_in_danger(m, danger());
	return total;
}

unsigned long
Level::recalc_item_prob()
{
	// idea from qhack
	unsigned long total = 0;
	for (int m = 0; m < iinfos; m++)
		total += item_prob_in_danger(m, danger());
	return total;
}

void
Level::in_dir(unsigned &x, unsigned &y, enum Direction dir) const
{
	if (dir > DIR_N && dir < DIR_S)
		x++;
	else if (dir > DIR_S && dir < DIR_N + DIRS)
		x--;

	if (dir > DIR_E && dir < DIR_W)
		y++;
	else if (dir < DIR_E || dir > DIR_W)
		y--;

	if (x < 0 || x >= map_.size() || y < 0 || y >= map_.size())
		throw X_baddir(dir);
}

void
Level::randpos(unsigned &x, unsigned &y) const
{
	do {
		x = rand(80);
		y = rand(22);
	} while (!map_[x][y].opaque());
}

void
Level::percept(unsigned x, unsigned y, void *d)
{
	std::list<Creature *> *creatures = (std::list<Creature *> *) d;
	unleash(x, y);

	for (std::list<MapObject *>::const_iterator obj = map_[x][y].objs.begin();
	     obj != map_[x][y].objs.end(); obj++) {
		Creature *m = dynamic_cast<Creature *>(*obj);
		if (m)
			creatures->push_back(m);
	}
}

bool
Level::trace_visibility(unsigned x1, unsigned y1, unsigned x2, unsigned y2, void (Level::*hook)(unsigned, unsigned, void *), void *d)
{
	unsigned bx = x1, by = y1;
	int dx = x2 - x1, dy = y2 - y1;
	bool steep = abs(dy) > abs(dx);
	if (steep) {
		swap(dx, dy);
		swap(x1, y1);
		swap(x2, y2);
	}
	unsigned adx = abs(dx), ady = abs(dy);
	int sdx = sgn(dx), sdy = sgn(dy);
	int fract = 0;

	while (x1 != x2 || y1 != y2) {
		unsigned mx = steep ? y1 : x1;
		unsigned my = steep ? x1 : y1;
		if (map_[mx][my].illum())
			(this->*hook)(mx, my, d);
		if (!(bx == mx && by == my) && !map_[mx][my].opaque())
			return false;

		if (x1 != x2)
			x1 += sdx;
		fract += ady;
		// Silly heuristic about the ?1:2, 2 is the "normal" one.
		if (/*(map_[x1][y1 + sdy].type() != Tile::ROCK?1:*/(2)*fract >= int(adx)) {
			y1 += sdy;
			fract -= adx;
		}
	}

	return true;
}

std::list<Creature *>
Level::explore(unsigned cx, unsigned cy, int range)
{
	std::list<Creature *> creatures;
	int rrange = range * range;

	for (unsigned x = 0; x < map_.size(); x++) {
		for (unsigned y = 0; y < map_[x].size(); y++) {
			map_view_[x][y].illum(false);
		}
	}

	for (int x = max(cx - range, 0); x < min(cx + range, int(map_.size())); x++)
		for (int y = max(cy - range, 0); y < min(cy + range, int(map_[x].size())); y++) {
			int dx = x - cx, dy = y - cy;
			std::cerr << x << " " << y << "\n";
			if (dx * dx + dy * dy < rrange
			    && map_[x][y].illum()
			    && trace_visibility(cx, cy, x, y, &Level::percept, &creatures)) {
				percept(x, y, &creatures);
			}
		}

	for (int x = max(cx - 1, 0); x < min(cx + 2, int(map_.size())); x++)
		for (int y = max(cy - 1, 0); y < min(cy + 2, int(map_[x].size())); y++)
			if (!map_[x][y].opaque()) {
				unleash(x, y);
			}

	return creatures;
}

Monster *
Level::spawn_monster()
{
	int m = 0;
	unsigned long roll = rand(total_creat_prob_ + 1);
	while (roll > creat_prob_in_danger(m, danger())) {
		roll -= creat_prob_in_danger(m, danger());
		m++;
	}

	Monster *monster = Monster::spawn_random(cinfo[m], this);

	if (cinfo[m].flags & CF_IN_PACKS && rand(5) < 3) {
		Dice d = {3,5,3};
		int n = dice(d);
		for (unsigned i = life.size(); i < unsigned(min(n, maxmonsters() + 5)); i++) {
			Monster::spawn_nearby(cinfo[m], *monster, this);
		}
		throw X_monsterpack();
	}
	return monster;
}

void
Level::spawn_monsters()
{
	for (unsigned i = life.size(); i < maxmonsters(); i++) {
		try {
			spawn_monster();
		} catch (X_monsterpack x) {
			i = life.size() - 1;
		}
	}
}

ItemOnMap *
Level::scatter_item()
{
	int m = 0;
	unsigned long roll = rand(total_item_prob_ + 1);
	while (roll > item_prob_in_danger(m, danger())) {
		roll -= item_prob_in_danger(m, danger());
		m++;
	}

	ItemOnMap *iom = ItemOnMap::corporealize_random(iinfo[m], this);

	if (iinfo[m].flags & IF_IN_HEAPS && rand(5) < 3) {
		Dice d = {2,2,3};
		int n = dice(d);
		for (unsigned i = 0; i < unsigned(n); i++) {
			ItemOnMap::corporealize_nearby(iinfo[m], *iom, this);
		}
		throw X_itemheap(n);
	}
	return iom;
}

void
Level::scatter_items()
{
	for (unsigned i = 0; i < maxitems(); i++) {
		try {
			scatter_item();
		} catch (X_itemheap x) {
			i += x.items_created - 1;
		}
	}
}
