#include <iostream>
#include <cstdlib>
#include <ctime>
#include <set>
#include <fstream>
#include <climits>
#include <bits/stdc++.h>
#include "hlt.hpp"
#include "networking.hpp"

using namespace std;

vector<vector<bool>> wasMoved;

void initWasMoved(hlt::GameMap &presentMap) {
    wasMoved = vector<vector<bool>>(presentMap.width, vector<bool>(presentMap.height, false));
}

void clearWasMoved() {
    for (unsigned short a = 0; a < wasMoved.size(); a++) {
        for (unsigned short b = 0; b < wasMoved[a].size(); b++) {
            wasMoved[a][b] = false;
        }
    }
}

bool isAtBorder(hlt::GameMap &presentMap, hlt::Location location, unsigned char myID) {
    if (presentMap.getSite(location).owner == myID) {
        return false;
    }
    for (unsigned char direction = 1; direction < 5; direction++) {
        hlt::Location neighbor = presentMap.getLocation(location, direction);
        if (presentMap.getSite(neighbor).owner == myID) {
            return true;
        }
    }
    return false;
}

bool compareLocations(hlt::Location a, hlt::Location b) {
    return a.x == b.x && a.y == b.y;
}

unsigned char getDirection(hlt::GameMap& presentMap, hlt::Location& loc1, hlt::Location& loc2) {
    if (loc1.x == loc2.x) {
        if (loc1.y < loc2.y) {
            return SOUTH;
        } else {
            return NORTH;
        }
    } else {
        if (loc1.x < loc2.x) {
            return EAST;
        } else {
            return WEST;
        }
    }
}

double getScore(hlt::GameMap &presentMap, hlt::Location loc) {
    return 5 * presentMap.getSite(loc).production - 0.7 * presentMap.getSite(loc).strength + 50;
}

int calculateStrengthAfter(hlt::GameMap &presentMap, hlt::Location loc, int steps) {
    return presentMap.getSite(loc).strength + steps * presentMap.getSite(loc).production;
}

vector<hlt::Move> getLocsAround(hlt::GameMap &presentMap, 
                                    hlt::Location loc, 
                                    unsigned char myID, 
                                    int steps) {
    vector<hlt::Move> locs;
    hlt::Move moves[] = {
                {presentMap.getLocation(loc, NORTH), SOUTH},
                {presentMap.getLocation(loc, SOUTH), NORTH},
                {presentMap.getLocation(loc, WEST), EAST},
                {presentMap.getLocation(loc, EAST), WEST}
                };

    for (auto move : moves) {
        hlt::Location loc = move.loc;
        if (presentMap.getSite(loc).owner == myID && !wasMoved[loc.x][loc.y]) {
            locs.push_back(move);
        }
    }

    sort(locs.begin(), locs.end(), [&](hlt::Move a, hlt::Move b) {
        return calculateStrengthAfter(presentMap, b.loc, steps) < calculateStrengthAfter(presentMap, a.loc, steps);
    });

    return locs;
}

void setWasMoved(hlt::GameMap &presentMap, vector<hlt::Move> moves) {
    for (auto move : moves) {
        wasMoved[move.loc.x][move.loc.y] = true;
    }
}

vector<hlt::Move> tryToTakeOver(hlt::GameMap &presentMap, 
                                    hlt::Location loc, 
                                    unsigned char myID, 
                                    int steps) {
    vector<hlt::Move> moves;
    vector<hlt::Move> locs = getLocsAround(presentMap, loc, myID, steps);

    if (locs.size() == 0) {
        return moves;
    }

    hlt::Move biggest = locs[0];
    if (calculateStrengthAfter(presentMap, biggest.loc, steps) > presentMap.getSite(loc).strength) {
        // try one move
        if (steps != 0) {
            moves.push_back({biggest.loc, STILL});
        } else {
            moves.push_back(biggest);
        }
    } else {
        // try two moves
        int last = locs.size() - 1;
        while(last > 0) {
            if (calculateStrengthAfter(presentMap, biggest.loc, steps) 
                    + calculateStrengthAfter(presentMap, locs[last].loc, steps) > presentMap.getSite(loc).strength) {
                if (steps != 0) {
                    moves.push_back({biggest.loc, STILL});
                } else {
                    moves.push_back(biggest);
                }
                moves.push_back(locs[last]);
                break;
            }
            last--;
        }
        // try three moves ?
    }
    setWasMoved(presentMap, moves);
    return moves;
}

bool isCloseToEdge(hlt::GameMap &presentMap, hlt::Location loc, unsigned char myID) {
    if (presentMap.getSite(loc).owner != myID) {
        return false;
    }
    for (unsigned char direction = 1; direction < 5; direction++) {
        hlt::Location neighbor = presentMap.getLocation(loc, direction);
        if (presentMap.getSite(neighbor).owner != myID) {
            return true;
        }
    }
    return false;
}

hlt::Move getClosestEdge(hlt::GameMap &presentMap, hlt::Location loc, unsigned char myID) {
    hlt::Move moves[] = {
                {loc, NORTH},
                {loc, SOUTH},
                {loc, WEST},
                {loc, EAST}
                };
    int distances[] = {0,0,0,0};
    int margins[] = {presentMap.height/2, presentMap.width/2};

    for (int i = 0; i < 4; i++) {
        while (presentMap.getSite(moves[i].loc).owner == myID
                && distances[i] < margins[i/2]) {
            hlt::Location newLocation = presentMap.getLocation(moves[i].loc, moves[i].dir);
            moves[i].loc = newLocation;
            distances[i]++;
        }
    }

    int distancesToEnemies[] = {0,0,0,0};
    int maxDif = 6;

    for (int i = 0; i < 4; i++) {
        if (distances[i] != margins[i / 2]) {
            if (presentMap.getSite(moves[i].loc).owner == 0) {
                while (distancesToEnemies[i] < maxDif && presentMap.getSite(moves[i].loc).owner == 0) {
                    hlt::Location newLocation;
                    newLocation = presentMap.getLocation(moves[i].loc, moves[i].dir);
                    moves[i].loc = newLocation;
                    distancesToEnemies[i]++;
                }
            }
        } else {
            distancesToEnemies[i] = maxDif;
        }
    }

    int minDistance = distances[0] + distancesToEnemies[0];
    int minIndex = 0;
    for (int i = 1; i < 4; i++) {
        if (minDistance > distances[i] + distancesToEnemies[i]) {
            minDistance = distances[i] + distancesToEnemies[i];
            minIndex = i;
        }
    }
    return moves[minIndex];
}

int main() {
    srand(time(NULL));

    std::cout.sync_with_stdio(0);

    unsigned char myID;
    hlt::GameMap presentMap;

    getInit(myID, presentMap);
    initWasMoved(presentMap);
    sendInit("Dani Mocanu");

    std::set<hlt::Move> moves;
    while(true) {
        moves.clear();

        getFrame(presentMap);

        priority_queue<hlt::Location, vector<hlt::Location>, function<bool(hlt::Location, hlt::Location)>> scores([&](hlt::Location a, hlt::Location b) {
            return getScore(presentMap, a) < getScore(presentMap, b);
        });
        
        for(unsigned short a = 0; a < presentMap.height; a++) {
            for(unsigned short b = 0; b < presentMap.width; b++) {
                hlt::Location loc = { b, a };
                if (isAtBorder(presentMap, loc, myID)) {
                    scores.push(loc);
                }
            }
        }

        while(!scores.empty()) {
            hlt::Location loc = scores.top();
            scores.pop();

            vector<hlt::Move> movesForEdge = tryToTakeOver(presentMap, loc, myID, 0);
            if (movesForEdge.size() != 0) {
                for (auto move : movesForEdge) {
                    moves.insert(move);
                }
            } else {
                movesForEdge = tryToTakeOver(presentMap, loc, myID, 1);
                if (movesForEdge.size() != 0) {
                    for (auto move : movesForEdge) {
                        moves.insert(move);
                    }
                } else {
                    vector<hlt::Move> bestMoves = getLocsAround(presentMap, loc, myID, 1);
                    if (bestMoves.size() == 0) {
                        continue;
                    }
                    hlt::Move bestMove = bestMoves[0];
                    vector<hlt::Move> bestNeighbors = getLocsAround(presentMap, bestMove.loc, myID, 0);
                    if (bestNeighbors.size() == 0) {
                        continue;
                    }
                    hlt::Move bestNeighbor = bestNeighbors[0];
                    if (calculateStrengthAfter(presentMap, bestMove.loc, 1) 
                            + presentMap.getSite(bestNeighbor.loc).strength > presentMap.getSite(loc).strength) {
                        moves.insert({bestMove.loc, STILL});
                        moves.insert(bestNeighbor);
                        setWasMoved(presentMap, {bestMove, bestNeighbor});
                    }
                }
            }
        }

        // map traverse
        for(unsigned short a = 0; a < presentMap.height; a++) {
            for(unsigned short b = 0; b < presentMap.width; b++) {
                hlt::Location loc = { b, a };
                if (presentMap.getSite(loc).owner != myID) {
                    continue;
                }
                // site is mine
                if (isCloseToEdge(presentMap, loc, myID)) {
                    if (!wasMoved[loc.x][loc.y]) {
                        moves.insert({loc, STILL});
                    }
                } else {
                    if (presentMap.getSite(loc).strength < presentMap.getSite(loc).production * 5) {    
                        moves.insert({loc, STILL});
                    } else {
                        moves.insert({loc, getClosestEdge(presentMap, loc, myID).dir});
                    }
                }

                wasMoved[loc.x][loc.y] = true;
            }
        }

        sendFrame(moves);
        
        clearWasMoved();
    }

    return 0;
}

poti sa calculezi getClosestEdge in alt fel?