#include "gamedata.h"
#include "mathutils.h"
#include "debug.h"
#include <list>
#include <algorithm>
#include <cassert>

using std::list;

int GameData::PLAYER1_COLOUR = 0x4a483f;
int GameData::PLAYER2_COLOUR = 0x090c7a;

int GameData::BASE_BUILD_RADIUS = 75;
int GameData::NODE_RADIUS = 10;


GameVertex::GameVertex(int x, int y, int z, int r, int colour, int score,
		       VertexType type, Player* player)
    : Vertex(x, y, z, r, colour, score)
{
    this->type = type;
    this->player = player;
}


GameData::GameData()
    : Graph(true)
{
    current = NULL;
    mode = MODE_BUILD;
    player1 = Player("player 1", PLAYER1_COLOUR);
    player2 = Player("player 2", PLAYER2_COLOUR);
    turn = &player1;
    build_type = VERTEX_PRODUCER; // first vertex is always a producer
}

GameData::~GameData() { }


Vertex* GameData::get_current_vertex(bool only_mine) const
{
    if (only_mine)
    {
	if (current != NULL &&
	    dynamic_cast<GameVertex*>(current)->player == turn) return current;
	
	return NULL;
    }
    
    return current;
}


void GameData::clear_current_vertex()
{
    mode = MODE_SELECT;
    current = NULL;
}


void GameData::toggle_turn()
{
    if (!turn->has_played()) turn->set_played();

    if (!endgame())
    {
	if (turn == &player1) turn = &player2;
	else if (turn == &player2) turn = &player1;

	if (!turn->has_played())
	{
	    mode = MODE_BUILD;
	    build_type = VERTEX_PRODUCER;
	}
	else
	{
	    mode = MODE_SELECT;
	    build_type = VERTEX_NONE;
	}
    }

    current = NULL;
}


void GameData::handle_click(int x, int y)
{
    int r = 10;

    int colour;
    colour = turn->get_colour();

    Vertex* v = vertex_at(x, y, 0);

    // fixme - energy expenditure should happen in each of these cases except
    // MODE_SELECT
    switch (mode)
    {
    case MODE_SELECT:
	// select_vertex handles making sure a point exists at (x,y)
	select_vertex(x, y);
	break;
    case MODE_BUILD:
	add_vertex(x, y, 0, r, colour);
	break;
    case MODE_ATTACK:
	if (v == NULL || dynamic_cast<GameVertex*>(v)->player == turn) return;
	if (v->colour != colour) attack_vertex(v);
	break;
    case MODE_MOVE:
	if (current == NULL) return;
	if (MathUtils::distance(current->x, current->y, current->z, x, y, 0)
	    <= get_range())
	{
	    current->x = x;
	    current->y = y;
	    toggle_turn();
	}
	break;
    }
}


bool GameData::select_vertex(int x, int y)
{
    Vertex * v = vertex_at(x, y, 0);
    if (v == NULL) return false;

    current = v;
    return true;
}


bool GameData::add_vertex(int x, int y, int z, int r, int colour)
{
    if (build_type == VERTEX_NONE) return false;

    GameVertex* v = new GameVertex(x, y, z, r, colour, 10, build_type, turn);

    if (current == NULL)
    {
	// this is the special case for adding the first vertex for each player
	if (!turn->has_played())
	{
	    if (Graph::add_vertex(v))
	    {
#ifdef DEBUG
		fprintf(stderr, "debug: GameData::add_vertex(): strength=%2.f\n", calculate_strength(*(vertices.rbegin())));
#endif
		toggle_turn();
		return true;
	    }
	}

	// really, we shouldn't be able to get here. return false just in case
	delete v;
	return false;
    }

    // same here - just a logic check
    if (dynamic_cast<GameVertex*>(current)->player != turn)
    {
	delete v;
	return false;
    }

    // This is the range check...
    if (MathUtils::distance(current->x, current->y, 0, v->x, v->y, 0) >
	get_range())
    {
	delete v;
	return false;
    }

    if (Graph::add_vertex(v, current))
    {
#ifdef DEBUG
	fprintf(stderr, "debug: GameData::add_vertex(): strength=%.2f\n",
		calculate_strength(*(vertices.rbegin())));
#endif

	toggle_turn();
	return true;
    }

    delete v;
    return false;
}


float GameData::calculate_armor(Vertex* node)
{
    float str = calculate_strength(node);
    float armor;

    switch(dynamic_cast<GameVertex*>(node)->type)
    {
    case VERTEX_ATTACKER:
	armor = str / 10;
	break;
    case VERTEX_DEFENDER:
	armor = str / 5;
	break;
    case VERTEX_PRODUCER:
	armor = str / 40;
	break;
    }

    if (armor < 1) armor = 1;
    return armor;
}


float GameData::calculate_attack(Vertex* node)
{
    float attack = calculate_strength(node);

    switch (dynamic_cast<GameVertex*>(node)->type)
    {
    case VERTEX_ATTACKER:
	attack *= 1.5;
	break;
    case VERTEX_DEFENDER:
	attack /= 0.75;
	break;
    case VERTEX_PRODUCER:
	attack = 0;
	break;
    }
}


float GameData::calculate_strength(Vertex* node)
{
    list<Vertex*> visited;

    // Special case - a one-node tree just returns its own score!
    if (node->neighbors.empty()) return (float)node->score;

    return calculate_strength_r(node, 0, visited);
}


// Oh the recursive recursion!
float GameData::calculate_strength_r(Vertex* node, unsigned int depth, list<Vertex*>& visited)
{
    // Find which vertices we need to visit from here
    list<Vertex*> neighbors = node->neighbors;
    list<Vertex*> to_visit;

    visited.push_back(node);

    for (list<Vertex*>::iterator cursor = neighbors.begin();
	 cursor != neighbors.end(); cursor++)
    {
	Vertex* v = *cursor;
	// if this is true, we haven't visited the vertex on the other end of
	// this edge yet
	if (find(visited.begin(), visited.end(), v) == visited.end())
	{
	    to_visit.push_back(v);
	}
    }

    // This is the base case - this node has no unvisited neighbors
    if (to_visit.empty())
    {
	assert(depth > 0);
	return (float)(node->score) / depth;
    }

    // Else, iterate through to_visit and visit them all, summing their
    // effective strengths adjusted for depth.
    // Since our trees are acyclic, this can't loop.
    float modscore = (float)node->score;
    if (depth > 0) modscore /= depth;

    for (list<Vertex*>::iterator cursor = to_visit.begin();
	 cursor != to_visit.end(); cursor++)
    {
	Vertex* v = *cursor;
	modscore += calculate_strength_r(v, depth+1, visited);
    }

    return modscore;
}


// This class contains logic checks to keep the mode aligned with
// what is reasonable. Special cases inside the GameData class should just
// do mode = MODE_<whatever>
void GameData::set_mode(Mode m)
{
    // Stay in MODE_SELECT (or maybe MODE_BUILD) when current is null
    if (current == NULL) return;

    // If we're leaving MODE_BUILD, we should set the build_type back to none
    if (mode == MODE_ATTACK && m != mode) build_type = VERTEX_NONE;

    // The other modes all require current to match the player
    if (dynamic_cast<GameVertex*>(current)->player == turn) mode = m;
}


int GameData::get_range(Vertex* node)
{
    if (node == NULL) node = current;

    if (node == NULL) return 0;
    else if (mode == MODE_MOVE) return turn->get_energy();
    else if (mode == MODE_BUILD) return BASE_BUILD_RADIUS;
    else if (mode == MODE_ATTACK)
    {
	int range = BASE_BUILD_RADIUS;
	list<Vertex*> neighbors = node->neighbors;

	for(list<Vertex*>::iterator cursor = neighbors.begin();
	    cursor != neighbors.end(); cursor++)
	{
	    Vertex* v = *cursor;
	    range -= (100 - MathUtils::distance(v->x, v->y, v->z,
						node->x, node->y, node->z))
		/ 2;
	}
	if (range < 0) range = 0;
	return range;
    }
}


void GameData::attack_vertex(Vertex* target)
{
    float atk = calculate_attack(current);
    float armor = calculate_armor(target);
    int damage = (int)(atk / armor);
    target->score -= damage;
    if (target->score <= 0) remove_vertex(target);

#ifdef DEBUG
    fprintf(stderr, "debug: GameData::attack_vertex(): atk=%.2f, armor=%.2f, damage=%d\n", atk, armor, damage);
#endif

    toggle_turn();
}


bool GameData::endgame()
{
    if (!(player1.has_played() && player2.has_played())) return false;

    if (get_colour(player1.get_colour()).empty())
    {
	debug("Gamedata::endgame(): player 2 wins\n");
	return true;
    }
    if (get_colour(player2.get_colour()).empty())
    {
	debug("Gamedata::endgame(): player 1 wins\n");
	return true;
    }

    return false;
}