#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>

typedef struct {
    int rows;
    int cols;
    unsigned char *cells;
} Map;

#define RIGHT_HAND 0
#define LEFT_HAND 1

#define LEFT_BORDER 0
#define RIGHT_BORDER 1
#define TOP_BORDER 2

bool isborder(Map *map, int r, int c, int border);
int start_border(Map *map, int r, int c, int leftright);
Map* init_map(int rows, int cols);
void load_map(Map* map, const char* filename);
void free_map(Map* map);
bool validate_map(Map* map);
void find_path(Map* map, int r, int c, int leftright, int sborder);
int load_mapsize(int* mrow, int* mcol, const char* filename);

bool isborder(Map *map, int r, int c, int border) // kontroluje, ci na konkretnej bunke sa nachadza konkretna stena
{
    unsigned char cell = map->cells[r * map->cols + c];
    return (cell & (1 << border)) != 0; // ak sa stena bunky zhoduje s typom kontrolovanej steny, vrati true, inak false
}

int start_border(Map *map, int r, int c, int leftright) //zistenie zaciatocnej steny podla zadania
                                                        // a kontrola, ci zaciatocna bunka moze byt
                                                        // vstupna bunka bludiska
{
    if (leftright == RIGHT_HAND)
    {
        if (c == 0 && r % 2 == 0 && !isborder(map, r, c, LEFT_BORDER)) return RIGHT_BORDER; // r je v nasej matici o 1 mensie, teda parny, obdobne dalsi if
        else if (c == 0 && r % 2 == 1 && !isborder(map, r, c, LEFT_BORDER)) return TOP_BORDER;
        else if (c == map->cols - 1 && ((r+c)%2==0) && !isborder(map, r, c, RIGHT_BORDER)) return TOP_BORDER;
        else if (c == map->cols - 1 && ((r+c)%2==1) && !isborder(map, r, c, RIGHT_BORDER)) return LEFT_BORDER;
        else if (r == 0 && !isborder(map, r, c, TOP_BORDER)) return LEFT_BORDER;
        else if (r == map->rows - 1 && !isborder(map, r, c, TOP_BORDER)) return RIGHT_BORDER;
        else return -1;
    } else if (leftright == LEFT_HAND)
        {
        if (c == 0 && r % 2 == 0 && !isborder(map, r, c, LEFT_BORDER) ) return TOP_BORDER;
        else if (c == 0 && r % 2 == 1 && !isborder(map, r, c, LEFT_BORDER)) return RIGHT_BORDER;
        else if (c == map->cols - 1 && ((r+c)%2==0) && !isborder(map, r, c, RIGHT_BORDER)) return LEFT_BORDER;
        else if (c == map->cols - 1 && ((r+c)%2==1) && !isborder(map, r, c, RIGHT_BORDER)) return TOP_BORDER;
        else if (r == 0 && !isborder(map, r, c, TOP_BORDER)) return RIGHT_BORDER;
        else if (r == map->rows - 1 && !isborder(map, r, c, TOP_BORDER)) return LEFT_BORDER;
        else return -1;
        }
    return -1;
}

Map* init_map(int rows, int cols) {   //inicializacia mapy
    Map* map = malloc(sizeof(Map)); // alokujeme najprv miesto pre strukturu map
    map->rows = rows;
    map->cols = cols;
    map->cells = malloc(rows * cols * sizeof(unsigned char)); // alokujeme miesto pre dany pocet buniek(rows * cols)
    return map;
}

void load_map(Map* map, const char* filename) { //nacitanie mapy zo suboru
    FILE *file = fopen(filename, "r");
    if (file == NULL) {
        fprintf(stderr,"File opening failed\n");
        return;
    }
    fscanf(file, "%d %d", &(map->rows), &(map->cols));
    for (int i = 0; i < map->rows; i++) {
        for (int j = 0; j < map->cols; j++) {
            fscanf(file, "%hhu", &(map->cells[i * map->cols + j]));
        }
    }
    fclose(file);
}

void free_map(Map* map) {      //uvolnenie mapy
    free(map->cells);
    free(map);
}

bool validate_map(Map* map) {    //overenie spravnosti bludiska, kontrolujeme, ci je policko definovane validnym cislo(<=7)
    for (int i = 0; i < map->rows; i++) {
        for (int j = 0; j < map->cols; j++) {
            unsigned char cell = map->cells[i * map->cols + j];
            if (cell > 7) {
                return false;
            }
            if (i > 0 && ((cell & (1 << TOP_BORDER)) != (map->cells[(i-1) * map->cols + j] & (1 << TOP_BORDER))) && ((j + i) % 2 == 0)) {
                return false;
            }
            if (j > 0 && ((cell & (1 << LEFT_BORDER)) != ((map->cells[i * map->cols + (j-1)] & (1 << RIGHT_BORDER))>>RIGHT_BORDER))) {  // pravu stenu popisuje druhy najnizsi bit
                                                                                                                                        //a teda ho musime posunut na najnizsi, aby
                                                                                                                                        //sa mohol porovnat s lavou stenou, ktora je
                                                                                                                                        //na najnizsom bite
                return false;
            }
        }
    }
    return true;
}

void find_path(Map* map, int r, int c, int leftright, int sborder) { //hladanie cesty bludiskom
    int x = r, y = c;
    int current_border = sborder;// aktualna stena
    int next_border;                   // nasledujuca stena

    while ((x >= 0) && (x <= (map->rows - 1)) && (y >= 0) && (y <= map->cols - 1))
    {                                                                                   //kym nevyjdeme mimo bludiska
                                                                                       //v if vetvach hladame dalsiu stenu, na ktorej ma byt polozena ruka
        printf("%d,%d\n", x + 1, y + 1);
        if (leftright == RIGHT_HAND) {  //podla pravidla pravej ruky
            if (current_border == TOP_BORDER && ((x + y) % 2 == 1)) {
                if (!isborder(map, x, y, TOP_BORDER)) {
                    next_border = LEFT_BORDER;x++;
                } else if (!isborder(map, x, y, RIGHT_BORDER)) {
                    next_border = RIGHT_BORDER;y++;
                } else {
                    next_border = TOP_BORDER;y--;
                }}
            else if (current_border == TOP_BORDER && ((x + y) % 2 == 0)) {
                if (!isborder(map, x, y, TOP_BORDER)) {
                    next_border = RIGHT_BORDER;x--;
                } else if (!isborder(map, x, y, LEFT_BORDER)) {
                    next_border = LEFT_BORDER;y--;
                } else {
                    next_border = TOP_BORDER;y++;
                }
            } else if (current_border == RIGHT_BORDER && ((x + y) % 2 == 1)) {
                if (!isborder(map, x, y, RIGHT_BORDER)) {
                    next_border = RIGHT_BORDER; y++;
                } else if (!isborder(map, x, y, LEFT_BORDER)) {
                    next_border = TOP_BORDER;y--;
                } else {
                    next_border = LEFT_BORDER;x++;
                }}
            else if (current_border == RIGHT_BORDER && ((x + y) % 2 == 0)) {
                if (!isborder(map, x, y, RIGHT_BORDER)) {
                    next_border = TOP_BORDER;y++;
                } else if (!isborder(map, x, y, TOP_BORDER)) {
                    next_border = RIGHT_BORDER;x--;
                } else {
                    next_border = LEFT_BORDER;y--;
                }

            } else if (current_border == LEFT_BORDER && ((x + y) % 2 == 1)) {
                if (!isborder(map, x, y, LEFT_BORDER)) {
                    next_border = TOP_BORDER;y--;
                } else if (!isborder(map, x, y, TOP_BORDER)) {
                    next_border = LEFT_BORDER;x++;
                } else {
                    next_border = RIGHT_BORDER;y++;
                }
            }
            else if (current_border == LEFT_BORDER && ((x + y) % 2 == 0)) {
                if (!isborder(map, x, y, LEFT_BORDER)) {
                    next_border = LEFT_BORDER;y--;
                } else if (!isborder(map, x, y, RIGHT_BORDER)) {
                    next_border = TOP_BORDER;y++;
                } else {
                    next_border = RIGHT_BORDER;x--;
                }
            }
        }
        else if (leftright == LEFT_HAND) {    //podla pravidla lavej ruky
            if (current_border == TOP_BORDER && ((x + y) % 2 == 0)) {
                if (!isborder(map, x, y, TOP_BORDER)) {
                    next_border = LEFT_BORDER;x--;
                } else if (!isborder(map, x, y, RIGHT_BORDER)) {
                    next_border = RIGHT_BORDER;y++;
                } else {
                    next_border = TOP_BORDER;y--;
                }}
            else if (current_border == TOP_BORDER && ((x + y) % 2 == 1)) {
                if (!isborder(map, x, y, TOP_BORDER)) {
                    next_border = RIGHT_BORDER;x++;
                } else if (!isborder(map, x, y, LEFT_BORDER)) {
                    next_border = LEFT_BORDER;y--;
                } else {
                    next_border = TOP_BORDER;y++;
                }
            } else if (current_border == RIGHT_BORDER && ((x + y) % 2 == 0)) {
                if (!isborder(map, x, y, RIGHT_BORDER)) {
                    next_border = RIGHT_BORDER; y++;
                } else if (!isborder(map, x, y, LEFT_BORDER)) {
                    next_border = TOP_BORDER;y--;
                } else {
                    next_border = LEFT_BORDER;x--;
                }}
            else if (current_border == RIGHT_BORDER && ((x + y) % 2 == 1)) {
                if (!isborder(map, x, y, RIGHT_BORDER)) {
                    next_border = TOP_BORDER;y++;
                } else if (!isborder(map, x, y, TOP_BORDER)) {
                    next_border = RIGHT_BORDER;x++;
                } else {
                    next_border = LEFT_BORDER;y--;
                }

            } else if (current_border == LEFT_BORDER && ((x + y) % 2 == 0)) {
                if (!isborder(map, x, y, LEFT_BORDER)) {
                    next_border = TOP_BORDER;y--;
                } else if (!isborder(map, x, y, TOP_BORDER)) {
                    next_border = LEFT_BORDER;x--;
                } else {
                    next_border = RIGHT_BORDER;y++;
                }
            }
            else if (current_border == LEFT_BORDER && ((x + y) % 2 == 1)) {
                if (!isborder(map, x, y, LEFT_BORDER)) {
                    next_border = LEFT_BORDER;y--;
                } else if (!isborder(map, x, y, RIGHT_BORDER)) {
                    next_border = TOP_BORDER;y++;
                } else {
                    next_border = RIGHT_BORDER;x++;
                }
            }

        }
        current_border = next_border;

    }
}
int load_mapsize(int* mrow, int* mcol, const char* filename) // nacita velkost mapy zo suboru
{
    FILE* file = fopen(filename, "r");
    if (file == NULL)
    {
        fprintf(stderr,"Failed to open file\n");
        return 1;
    }
    fscanf(file, "%d %d", mrow, mcol);
    if(*mrow <= 0 || *mcol <= 0)                    //velkost mapy musi byt kladna, nulova je tiez chybna
    {
        return 2;
    }
    fclose(file);
    return 0;
}


int main(int argc, char *argv[]) {
    if (argc < 2)
    {
        fprintf(stderr,"Missing arguments\n");
        return 1;
    }

    if (strcmp(argv[1], "--help") == 0)
    {
        printf(" Start program with these arguments:\n"
               "\n"
               "./maze --help\n"
               "shows this guide\n"
               "or\n"
               "./maze --test soubor.txt\n"
               "checks the validity of maze given in a file in second argument\n"
               "nebo\n"
               "./maze --rpath R C soubor.txt\n"
               "finds path from input R-row,C-column in the maze to exit with right-hand rule,\n"
               "nebo\n"
               "./maze --lpath R C soubor.txt\n"
               "finds path from input R-row,C-column in the maze to exit with left-hand rule,\n");
    }
    else if (strcmp(argv[1], "--test") == 0)
    {
        if (argc < 3)
        {
            fprintf(stderr,"missing name of file\n");
            return 1;
        }
        // nacitanie velkosti mapy
        int* mrows;
        int* mcols;
        mrows = malloc(sizeof(int));
        mcols = malloc(sizeof(int));

        int err = load_mapsize(mrows,mcols, argv[2]);
        if(err == 1)
        {
            free(mrows);
            free(mcols);
            return 1;
        }
        else if(err == 2)
        {
            printf("Invalid\n");
            free(mrows);
            free(mcols);
            return 0;
        }
        //koniec nacitania velkosti mapy

        Map* map = init_map(*mrows, *mcols);
        free(mrows);
        free(mcols);
        load_map(map, argv[2]);
        bool valid = validate_map(map);
        if (valid)
        {
            printf("Valid\n");
        }
        else
        {
            printf("Invalid\n");
        }
        free_map(map);

    }
    else if (strcmp(argv[1], "--rpath") == 0)
    {
        if (argc < 4) {
            fprintf(stderr,"Missing arguments\n");
            return 1;
        }
        int r = atoi(argv[2]);
        int c = atoi(argv[3]);

        //nacitannie velkosti mapy
        int* mrows;
        int* mcols;
        mrows = malloc(sizeof(int));
        mcols = malloc(sizeof(int));

        int err = load_mapsize(mrows,mcols, argv[4]);
        if(err == 1)
        {
            free(mrows);
            free(mcols);
            return 1;
        }
        else if(err == 2)
        {
            printf("Invalid\n");
            free(mrows);
            free(mcols);
            return 0;
        }
        //koniec nacitania veloksti mapy

        Map* map = init_map(*mrows, *mcols);
        free(mrows);
        free(mcols);

        load_map(map, argv[4]);
        if (r < 1 || r > map->rows || c < 1 || c > map->cols)
        {
            fprintf(stderr,"Wrong input coordinates\n");
            free_map(map);
            return 1;
        }

        bool valid = validate_map(map);
        if (!valid)
        {
            printf("Invalid\n");
            free_map(map);
            return 1;
        }
        int sborder = start_border(map, r-1, c-1, RIGHT_HAND);
        if (sborder == -1)
        {
            fprintf(stderr, "Invalid start border\n");
            free_map(map);
            return 1;
        }
        else
        {
            find_path(map, r - 1, c - 1, RIGHT_HAND, sborder);
        }

        free_map(map);

    }
    else if (strcmp(argv[1], "--lpath") == 0)
    {

        if (argc < 4)
        {
            fprintf(stderr,"Missing arguments\n");
            return 1;
        }
        int r =atoi(argv[2]);
        int c =atoi(argv[3]);

        //nacitanie velksoti mapy
        int* mrows;
        int* mcols;
        mrows = malloc(sizeof(int));
        mcols = malloc(sizeof(int));

        int err = load_mapsize(mrows,mcols, argv[4]);
        if(err == 1)
        {
            free(mrows);
            free(mcols);
            return 1;
        }
        else if(err == 2)
        {
            printf("Invalid\n");
            free(mrows);
            free(mcols);
            return 0;
        }
        //koniec nacitania velkosti mapy

        Map* map = init_map(*mrows, *mcols);
        free(mrows);
        free(mcols);

        load_map(map, argv[4]);
        if (r < 1 || r > map->rows || c < 1 || c > map->cols)
        {
            fprintf(stderr,"Wrong input coordinates\n");
            free_map(map);
            return 1;
        }

        bool valid = validate_map(map);
        if (!valid)
        {
            printf("Invalid\n");
            free_map(map);
            return 1;
        }

        int sborder = start_border(map, r-1, c-1, LEFT_HAND);
        if (sborder == -1)
        {
            fprintf(stderr, "Invalid start border\n");
            free_map(map);
            return 1;
        }
        else
        {
            find_path(map, r - 1, c - 1, LEFT_HAND, sborder);
        }
        free_map(map);
    }
    return 0;
}
