#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <math.h>
#include <stdbool.h>
#include <unistd.h>
#include <termios.h>
#include <limits.h>
#include <time.h>
#include <sys/time.h>
#include <SDL2/SDL.h>
#include <SDL2/SDL_image.h>

#include "structure.h"
#include "cars.h"
#include "base.h"

int* winners;
int remainingTurns;
int nextRank;

int currentTurn;
int nPlayers;
carData* players;
double* remainingD;

double minRemD;
double maxRemD;

int RED = 0;
int GREEN = 0;
int BLUE = 0;

bool* offTheMap;

color assignColor() {
    color res = (color){.red = RED, .green = GREEN, .blue = BLUE};
    
    // increment color like a binary number (exclude (0,0,0) though)
    if(RED == 255) {
        RED = 0;
        if(GREEN == 255) {
            GREEN = 0;
            BLUE=255-BLUE;
        } else {
            GREEN = 255;
        }
    } else {
        RED = 255;
    }
    if(RED == 0 && GREEN == 0 && BLUE == 0) {RED = 255;}

    return res;
}

void init_all(int nPl) {
    nPlayers = nPl;
    nextRank = 1;
    RED = 255*(rand()%2);
    GREEN = 255*(rand()%2);
    BLUE = 255*(rand()%2);
    if(RED == 0 && GREEN == 0 && BLUE == 0) {RED = 255;}
    currentTurn = 0;
    remainingTurns = -1;
    winners = malloc(sizeof(int)*nPlayers);
    players = malloc(sizeof(carData)*nPlayers);
    remainingD = malloc(sizeof(double)*nPlayers);
    offTheMap = malloc(sizeof(bool)*nPlayers);
    for(int p = 0; p < nPlayers; p++) {
        winners[p] = 0;
        players[p].rgb = assignColor();
        remainingD[p] = 0.0;
        players[p].c = init_car(p, nPlayers);
        offTheMap[p] = false;
    }
    minRemD = 0.0;
    maxRemD = 0.0;
}

void destroy_all() {
    for(int p = 0; p < nPlayers; p++) {
        destroy_car(players[p].c);
    }
    free(players);
    free(offTheMap);
}

// 1 = N
// 2 = E
// 4 = S
// 8 = W
int return_directions(road r) {
    switch (r){
        case NONE:      return 0;
        case START:     return 15;
        case END:       return 15;
        case STR_V:     return 5;
        case STR_H:     return 10;
        case STR_VB:     return 5;
        case STR_HB:     return 10;
        case TURN_NE:   return 3;
        case TURN_SE:   return 6;
        case TURN_SW:   return 12;
        case TURN_NW:   return 9;
        
        default:        return 0;
    }
}

void visit(level* lvl, path* pth, int cx, int cy, int depth, bool* brk) {
    if(!(*brk) && (cx >= 0 && cy >= 0 && cx < lvl->lines && cy < lvl->cols) && pth->nxts[cx][cy].dx == -2) {
        if(cx == FINISH_CHX && cy == FINISH_CHY) {
            pth->nxts[cx][cy] = (next){.dx = -1, .dy = -1, .deep = depth};
            pth->maxDepth = depth-1;
            *brk = true;
        } else {
            int carc = return_directions(lvl->map[cx][cy]);
            if(!(*brk))pth->nxts[cx][cy] = (next){.dx = 2, .dy = 2, .deep = -1};

            if(carc%2==1 && !(*brk)) {
                pth->nxts[cx][cy] = (next){.dx = -1, .dy = 0, .deep = depth};
                visit(lvl, pth, cx-1, cy, depth+1, brk);
            }

            if((carc/2)%2==1 && !(*brk)) {
                pth->nxts[cx][cy] = (next){.dx = 0, .dy =  1, .deep = depth};
                visit(lvl, pth, cx, cy+1, depth+1, brk);
            }

            if((carc/4)%2==1 && !(*brk)) {
                pth->nxts[cx][cy] = (next){.dx =  1, .dy = 0, .deep = depth};
                visit(lvl, pth, cx+1, cy, depth+1, brk);
            }

            if((carc/8)%2==1 && !(*brk)) {
                pth->nxts[cx][cy] = (next){.dx = 0, .dy = -1, .deep = depth};
                visit(lvl, pth, cx, cy-1, depth+1, brk);
            }

            if(!(*brk))pth->nxts[cx][cy] = (next){.dx = 2, .dy = 2, .deep = -1};
        }
    }
}

path* getPath(level* lvl) {
    path* res = malloc(sizeof(path));
    res->cols = lvl->cols;
    res->lines = lvl->lines;
    res->start = (pt){.ix = START_CHX, .iy = START_CHY};
    res->nxts = malloc(sizeof(next*)*lvl->lines);
    for(int l = 0; l < lvl->lines; l++) {
        res->nxts[l] = malloc(sizeof(next)*lvl->cols);
        for(int c = 0; c < lvl->cols; c++) {
            res->nxts[l][c] = (next){.dx = -2, .dy = -2, .deep = -1};
        }
    }

    bool brk = false;
    visit(lvl, res, START_CHX, START_CHY, 0, &brk);
    res->maxD = (1+res->maxDepth)*ROOM_SIZE;
    res->singleDist = (ROOM_SIZE/res->maxD);

    return res;
}

void printPath(path* pth) {
    for(int l = 0; l < pth->lines; l++) {
        for(int c = 0; c < pth->cols; c++) {
            if(pth->nxts[l][c].dx == -1 && pth->nxts[l][c].dy == -1) {
                printf("E");
            } else if(pth->nxts[l][c].dx == 1) {
                printf("v");
            } else if(pth->nxts[l][c].dx == -1) {
                printf("^");
            } else if(pth->nxts[l][c].dy == 1) {
                printf(">");
            } else if(pth->nxts[l][c].dy == -1) {
                printf("<");
            } else {
                printf(".");
            }
        }
        printf("\n");
    }
    printf("\n");
}

void printLvl(level* lvl) {
    for(int l = 0; l < lvl->lines; l++) {
        for(int c = 0; c < lvl->cols; c++) {
            printf("%d", (int)lvl->map[l][c]);
        }
        printf("\n");
    }
    printf("\n");
}

void freePath(path* p) {
    for(int l = 0; l < p->lines; l++) {
        free(p->nxts[l]);
    }
    free(p->nxts);
    free(p);
}

// ------------------------------------------------------------------------------------ //

double getRemDistance(level* lvl, path* pth, int nPl) {
    car* c = players[nPl].c;
    switch (lvl->map[c->chy][c->chx]){
        case NONE:
            fprintf(stderr, "ERROR : player in NONE chunk (%d at [%d %d])\n", nPl, c->chx, c->chy);
            return 0.0;

        case START: {
            if(START_DIR == 0) {            // North (-Y)
                return c->pos.fy;
            } else if(START_DIR == 1) {     // East  (+X)
                return (ROOM_SIZE-c->pos.fx);
            } else if(START_DIR == 2) {     // South (+Y)
                return (ROOM_SIZE-c->pos.fy);
            } else {                        // West  (-X)
                return c->pos.fx;
            }
            break;
        }

        case END: return 0.0;

        case STR_V:{
            if(pth->nxts[c->chy][c->chx].dx == -1) {    // UP (North)
                return c->pos.fy;
            } else {                                    // DOWN (South)
                return (ROOM_SIZE-c->pos.fy);
            }
            break;
        }

        case STR_H:{
            if(pth->nxts[c->chy][c->chx].dy == 1) {     // RIGHT (East)
                return (ROOM_SIZE-c->pos.fx);
            } else {                                    // LEFT (West)
                return c->pos.fx;
            }
            break;
        }

        case TURN_NE:{
            if(pth->nxts[c->chy][c->chx].dy == 1) {     // RIGHT (East)
                return (ROOM_SIZE-c->pos.fx);
            } else {                                    // UP (North)
                return c->pos.fy;
            }
            break;
        }

        case TURN_SE:{
            if(pth->nxts[c->chy][c->chx].dy == 1) {     // RIGHT (East)
                return (ROOM_SIZE-c->pos.fx);
            } else {                                    // DOWN (South)
                return (ROOM_SIZE-c->pos.fy);
            }
            break;
        }

        case TURN_SW:{
            if(pth->nxts[c->chy][c->chx].dy == -1) {     // LEFT (West)
                return c->pos.fx;
            } else {                                     // DOWN (South)
                return (ROOM_SIZE-c->pos.fy);
            }
            break;
        }

        case TURN_NW:{
            if(pth->nxts[c->chy][c->chx].dy == -1) {     // LEFT (West)
                return c->pos.fx;
            } else {                                     // UP (North)
                return c->pos.fy;
            }
            break;
        }
        
        default: return 0.0;
    }
    fprintf(stderr, "what ?\n");
    return (0.0);
}

double getTotalRemainingDistance(level* lvl, path* pth, int nPl) {
    car* c = players[nPl].c;
    double localRem = getRemDistance(lvl, pth, nPl);
    return (localRem + 1.0*ROOM_SIZE*(max(0,pth->maxDepth-pth->nxts[c->chy][c->chx].deep)));
}

// ------------------------------------------------------------------------------------ //

int ln_baseN(int n, int b) {
    if(n<b) {
        return 0;
    }
    return 1+ln_baseN(n/b, b);
}
int notLN(int n, int b) {
    int r = 0;
    while(n != 0) {
        n = n/b;
        r++;
    }
    return r;
}
int convex_seg(int x1, int x2, double theta) {
    return (int)(((1.0f - theta) * x1 + theta * x2));
}
double to_double(int n) {
    return (double)n;
}
int pw(int x, int n) {
    if (n<0)
        return 0;
    if (n==0)
        return 1;
    if (n%2==0)
        return pw(x*x, n/2);
    return x*pw(x*x, n/2);
}
double distance_pt(int x1, int x2, int y1, int y2) {
    return sqrt(to_double(pw(x2 - x1, 2) + pw(y2 - y1, 2)));
}
int max(int a, int b) {return (a>b)?(a):(b);}
int min(int a, int b) {return (a<b)?(a):(b);}

int mfax(double a, double b) {return (a>b)?(a):(b);}
int mfin(double a, double b) {return (a<b)?(a):(b);}

double norm(ptf p) {
    return sqrt(p.fx*p.fx + p.fy*p.fy);
}

double distance(ptf p1, ptf p2) {
    return norm((ptf){.fx = p2.fx - p1.fx, .fy = p2.fy - p1.fy});
}

ptf normalize(ptf p) {
    double nr = norm(p);
    return (ptf){.fx = p.fx/nr, .fy = p.fy/nr};
}

double dot(ptf p1, ptf p2) {
    return p1.fx*p2.fx + p1.fy*p2.fy;
}

ptf add(ptf p1, ptf p2) {
    return (ptf){.fx = p1.fx + p2.fx, .fy = p1.fy + p2.fy};
}
