#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_MOVE_RADIUS = 75;

GameData::GameData()
    : Graph(true)
{
    current = NULL;
    player = PLAYER1;
    mode = MODE_MOVE;
}

GameData::~GameData() { }

void GameData::toggle_turn()
{
    mode = MODE_MOVE;
    current = NULL;

    if (!endgame())
    {
	if (player == PLAYER1) player = PLAYER2;
	else if (player == PLAYER2) player = PLAYER1;
    }
}


void GameData::do_vertex(int x, int y, int r)
{
    if (current != NULL &&
	(MathUtils::distance(current->x, current->y, x, y)
	 > get_range()))
    {
	select_vertex(x, y);
	return;
    }

    int colour;
    if (player == PLAYER1) colour = PLAYER1_COLOUR;
    if (player == PLAYER2) colour = PLAYER2_COLOUR;

    if (mode == MODE_MOVE)
    {
	if (point_in_vertex(x, y)) select_vertex(x, y);
	else add_vertex(x, y, r, colour);
    }
    if (mode == MODE_ATTACK)
    {
	Vertex* v = vertex_at(x, y);
	if (v == NULL) return;

	if (v->colour == colour) select_vertex(x, y);
	else attack_vertex(v);
    }
}


void GameData::select_vertex(int x, int y)
{

    for (list<Vertex*>::iterator cursor = vertices.begin();
	 cursor != vertices.end(); cursor++)
    {
	Vertex* v = *cursor;
	if ((MathUtils::distance(v->x, v->y, x, y) <= v->r) &&
	    (v->colour == PLAYER1_COLOUR && player == PLAYER1 ||
	     v->colour == PLAYER2_COLOUR && player == PLAYER2))
	{
	    current = v;
	    return;
	}
    }
}


bool GameData::add_vertex(int x, int y, int r, int colour)
{
    if (mode == MODE_ATTACK) return false;

    if (current == NULL)
    {
	// this is the special case for adding the first vertex for each player
	if ((player == PLAYER1 && !player1_played) ||
	    (player == PLAYER2 && !player2_played))
	{
	    Graph::add_vertex(x, y, r, colour, 10);
#ifdef DEBUG
	    fprintf(stderr, "debug: GameData::add_vertex(): strength=%2.f\n",
		    calculate_strength(*(vertices.rbegin())));
#endif
	    if (player == PLAYER1) player1_played = true;
	    if (player == PLAYER2) player2_played = true;
	    toggle_turn();
	    return true;
	}
	return false;
    }

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

	toggle_turn();
	return true;
    }
    return false;
}


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

    // Special case - a one-node tree just returns its own score!
    list<Vertex*> all_nodes = get_colour(node->colour);
    if (all_nodes.size() == 1) 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;
}


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

    if (node == NULL) return 0;
    else if (mode == MODE_MOVE) return BASE_MOVE_RADIUS;
    else if (mode == MODE_ATTACK)
    {
	int range = BASE_MOVE_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, node->x, node->y)) / 2;
	}
	if (range < 0) range = 0;
	return range;
    }
}


void GameData::attack_vertex(Vertex* target)
{
    float atk_str = calculate_strength(current);
    float def_str = calculate_strength(target);
    float armor = def_str / 10; // how much energy it takes to deal 1 damage

    int damage = (int)(atk_str / armor);

    target->score -= damage;

    if (target->score <= 0) remove_vertex(target);

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

    toggle_turn();
}


bool GameData::endgame()
{
    if (!(player1_played && player2_played)) return false;

    if (get_colour(PLAYER1_COLOUR).empty())
    {
	player = WIN2;
	debug("Gamedata::endgame(): player 2 wins\n");
	return true;
    }
    if (get_colour(PLAYER2_COLOUR).empty())
    {
	player = WIN1;
	debug("Gamedata::endgame(): player 1 wins\n");
	return true;
    }

    return false;
}