#include <SDL.h>
#include "SDL2_gfxPrimitives.h"
#include <string>

/* Global board size variables */
const int pxsize = 16;
const int fieldsize = 60;
const int SIZE = pxsize*fieldsize;

/* Global colour variables */
//#define DARK_THEME
/* Light theme */
#ifndef DARK_THEME
const Uint32 C_GRID = 0xF0F0F0FF;
const Uint32 C_COLOURED = 0xCCCCCCFF;
const Uint32 C_UNCOLOURED = 0xFFFFFFFF;
const Uint32 C_ANT = 0x94DBFFFF;
#endif
/* Dark theme */
#ifdef DARK_THEME
const Uint32 C_GRID = 0x0F0F0FFF;
const Uint32 C_COLOURED = 0x333333FF;
const Uint32 C_UNCOLOURED = 0x000000FF;
const Uint32 C_ANT = 0x0029A3FF;
#endif

/* Global SDL variables */
SDL_Event ev;
SDL_TimerID sdlTimer;
SDL_Renderer *sdlRenderer;

/**
 * @brief Standard SDL timer function
 * @param ms - delay between events to be generated
 * @param param - unused parameter required by SDL
 * @return ms, used by SDL internally
 */
Uint32 timer(Uint32 ms, void* param) {
    SDL_Event ev;
    ev.type = SDL_USEREVENT;
    SDL_PushEvent(&ev);
    return ms;
}

/**
 * @class Square
 * @brief A square to be used in a grid, has x,y position
 *        and a coloured/uncoloured state
 */
class Square {
    int x, y;
    bool coloured;
    
public:
    /* The drawn_state here is immediately changed
     * by the call to the draw function */
    Square(int y, int x) : x(x), y(y), coloured(false) {}
    
    /**
     * @brief Toggles coloured/uncoloured state for the Square
     */
    void toggle_colour() {
        coloured = !coloured;
    }
    
    /**
     * @brief Displays the Square using the global renderer
     */
    void draw() {
        boxColor(sdlRenderer, x*pxsize+1, y*pxsize+1, (x+1)*pxsize-2, (y+1)*pxsize-2, coloured?C_COLOURED:C_UNCOLOURED);
        rectangleColor(sdlRenderer, x*pxsize, y*pxsize, (x+1)*pxsize, (y+1)*pxsize, C_GRID);
    }
    
    /**
     * @brief Getter function for colour state
     * @return the colour state of the Square
     * @retval true - Square is coloured
     * @retval false - Square is not coloured
     */
    bool get_colour_state() const {
        return coloured;
    }
};

class Ant {
    enum direction {north, east, south, west};
    
    int x, y;
    direction dir;
    
    /**
     * @brief Turns the ant right 90 degrees
     */
    void turn_right() {
        switch(dir) {
            case direction::north:
                dir = east;
                break;
            case direction::east:
                dir = south;
                break;
            case direction::south:
                dir = west;
                break;
            case direction::west:
                dir = north;
                break;
        }
    }
    
    /**
     * @brief Turns the ant left 90 degrees
     */
    void turn_left() {
        switch(dir) {
            case direction::north:
                dir = west;
                break;
            case direction::east:
                dir = north;
                break;
            case direction::south:
                dir = east;
                break;
            case direction::west:
                dir = south;
                break;
        }
    }
    
    /**
     * @brief Moves the ant to the square it's looking at
     * @retval true - step successful
     * @retval false - step cancelled
     */
    bool step_forward() {
        switch(dir) {
            case direction::north:
                if(y == 0) {
                    return false;
                }
                y--;
                break;
            case direction::east:
                if(x == fieldsize-1) {
                    return false;
                }
                x++;
                break;
            case direction::south:
                if(y == fieldsize-1) {
                    return false;
                }
                y++;
                break;
            case direction::west:
                if(x == 0) {
                    return false;
                }
                x--;
                break;
        }
        
        return true;
    }
    
public:

    /**
     * @brief Ant ctor
     * @param y - row number in squares
     * @param x - column number in squares
     * @param dir - direction the ant initially faces
     */
    Ant(int y, int x, direction dir = direction::north)
        : x(x), y(y), dir(dir) {}
        
    /**
     * @brief Displays the ant using the global renderer
     * @detail Due to budget constraints, the ant is a circle
     */
    void draw() {
        filledCircleColor(sdlRenderer, x*pxsize+pxsize/2, y*pxsize+pxsize/2, pxsize/3, C_ANT);
    }
    
    /**
     * @brief Rotates and moves the ant to the next square
     * @param field_coloured - true if the current square is coloured
     * @retval true - moved succesfully
     * @retval false - end of grid reached
     */
    bool move(bool field_coloured) {
        if(field_coloured) {
            turn_left();
        }
        else {
            turn_right();
        }
        
        return step_forward();
    }
    
    /**
     * @brief Getter function for the ant's x position
     * @return the ant's x position in squares
     */
    int get_x() const {
        return x;
    }
    
    /**
     * @brief Getter function for the ant's y position
     * @return the ant's y position in squares
     */
    int get_y() const {
        return y;
    }
};

/**
 * @brief A field for ants to play on
 */
class Field {
    Square* squares[fieldsize][fieldsize];
    Ant ant;
    
    unsigned cycle_count;
public:
    /**
     * @brief Field ctor
     * @detail Creates squares and initializes ant to
     *         the middle of the field
     */
    Field()
        : ant(fieldsize/2, fieldsize/2)
        , cycle_count(0) {
        for(int i = 0; i < fieldsize; i++) {
            for(int j = 0; j < fieldsize; j++) {
                squares[i][j] = new Square(i, j);
            }
        }
    }
    
    /**
     * @brief Displays the field and its contents
     *        using the global renderer
     * @detail Displays squares first, draws ant on top
     */
    void draw() {
        for(int i = 0; i < fieldsize; i++) {
            for(int j = 0; j < fieldsize; j++) {
                squares[i][j]->draw();
            }
        }
        
        ant.draw();
        
        stringColor(sdlRenderer, 20, 20, std::to_string(cycle_count).c_str(), C_ANT);
        
        SDL_RenderPresent(sdlRenderer);
    }
    
    /**
     * @brief Makes the ant move to the next square
     * @retval true - simulation isn't over yet
     * @retval false - simulation reached an edge
     */
    bool step() {        
        bool retval;
        int x = ant.get_x();
        int y = ant.get_y();
        
        /* Move ant forward, store success flag */
        retval = ant.move(squares[y][x]->get_colour_state());
        /* Colour the square that the ant left */
        squares[y][x]->toggle_colour();
        /* Increments counter */
        cycle_count++;
        
        return retval;
    }
    
    /**
     * @brief Field dtor
     * @detail Frees dinamically allocated memory
     */
    ~Field() {
        for(int i = 0; i < fieldsize; i++) {
            for(int j = 0; j < fieldsize; j++) {
                delete squares[i][j];
            }
        }
    }
};

int main(int argc, char *argv[]) {
    /* Standard SDL initialization */
    SDL_Window *sdlWindow;
    try {
        sdlWindow = SDL_CreateWindow("Langton's Ant",
                                     SDL_WINDOWPOS_UNDEFINED,
                                     SDL_WINDOWPOS_UNDEFINED,
                                     SIZE, SIZE,
                                     0);
        if(sdlWindow == NULL) throw 1;
        
        sdlRenderer = SDL_CreateRenderer(sdlWindow, -1, 0);
        if(sdlRenderer == NULL) throw 1;
    }
    catch(int error) {
        SDL_Quit();
        return error;
    }
    
    /* Create field */
    Field f;
    f.draw();
    
    /* Start generating timer events */
    sdlTimer = SDL_AddTimer(5, timer, nullptr);
    
    bool end_loop = false;
    while(SDL_WaitEvent(&ev) && !end_loop) {
        switch(ev.type) {
            case SDL_USEREVENT:
                /* If step is unsuccessful, wait for keyboard event to quit */
                if(!f.step()) {
                    end_loop = true;
                    while(SDL_WaitEvent(&ev) && ev.type != SDL_QUIT && ev.type != SDL_KEYDOWN);
                }
                f.draw();
                SDL_RenderPresent(sdlRenderer);
                break;
            case SDL_KEYDOWN:
            case SDL_QUIT:
                end_loop = true;
                break;
        }
    }
    
    SDL_RemoveTimer(sdlTimer);
    SDL_Quit();
    return 0;
}
