//include std lib if they havent been included
#ifndef STD_LIB_H
#define STD_LIB_H
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<stdbool.h>
#include<math.h>
#include<time.h>
#include<ctype.h>
#endif
#ifndef DNT_H
#define DNT_H
#include "dateNtime.h"
#endif // !DNT_H
//linked list
#ifndef likely
#define likely(x) __builtin_expect(!!(x), 1)
#define unlikely(x) __builtin_expect(!!(x), 0)
#endif
typedef struct linkedlist{
    struct linkedlist* next;
    struct linkedlist* prev;
    void* data;
}linkedlist;
//linked list functions
int sizeofLinkedlist(struct linkedlist* list){
    linkedlist* start = list;
    int size = 0;
    linkedlist* current = list;
    while (current != NULL){
        size++;
        current = current->next;
    }
    current = start;
    while(current->prev != NULL){
        size++;
        current = current->prev;
    }
    return size;
}
struct linkedlist* getLinkedList(struct linkedlist* list,int pos){
    linkedlist* cur = list;
    while(cur->prev != NULL){
        cur = cur->prev;
    }
    struct linkedlist* start = cur;
    for(int i=0;i<pos;i++){
        cur = cur->next;
        if(cur == NULL){
            return start;//return start if pos is out of bounds
        }
    }
    return cur;
}
typedef struct basic_db{
    int row_count;
    bool init_status;//not really used can be useful
    //array of struct of row
}basic_db;

//inventory
#define INVENTORY_DB "data_file_inventory.txt"
typedef struct inventory{
    struct basic_db db;
    struct inventory_row* row;
    
}inventory;
typedef struct inventory_row{
    char category[100];
    char brand[100];
    char product[100];
    double price;
    int stock;
    long barcode;
    bool isdeleted;//common for all rows,default is false
}inventory_row;
typedef enum  {
    category = 1, brand = 2, product = 3, price_inv = 4, stock = 5, barcode_inv = 6
 }INVENTORY;
#define ENDOFINVENTORY 6
//transaction
#define TRANSACTION_DB "data_file_transaction.txt"
typedef struct transaction{
    struct basic_db db;
    struct transaction_row* row;
    
}transaction;
typedef struct transaction_row{
    struct Date date;
    struct Time time;
    long id;
    double price;
    int quantity;
    long barcode;
    bool isdeleted;//common for all rows,default is false
}transaction_row;
typedef enum {
    date = 1, TIME = 2, id_tran = 3, price_tran = 4, quantity = 5, barcode_tran = 6
 }TRANSACTION;
#define ENDOFTRANSACTION 6
//user
#define USER_DB "data_file_user.txt"
typedef struct user{
    struct basic_db db;
    struct user_row* row;
}user;
typedef struct user_row{
    char name[100];
    char role[100];
    long id;
    bool isdeleted;//common for all rows,default is false
}user_row;

typedef enum {
    name = 1, role = 2, id_user = 3
 }USER;
#define ENDOFUSER 3

//admin verify
#define ADMIN_DB "data_file_admin.txt"
//func for admin verify

bool is_admin(char* role){
    FILE* fp = fopen(ADMIN_DB,"r");
    if(fp == NULL){
        printf("Error opening file\n");
        return false;
    }
    char line[100];
    while(fgets(line,100,fp) != NULL){
        if(unlikely(line[0] == '#')) continue;

        while(line[strlen(line)-1] == '\n' || (int)line[strlen(line)-1] == 13)
            line[strlen(line)-1] = '\0';
        
        if(strcmp(line,role) == 0){
            fclose(fp);
            return true;
        }
    }
    fclose(fp);
    return false;
}

void add_admin(char* role){
    FILE* fp = fopen(ADMIN_DB,"a");
    if(fp == NULL){
        printf("Error opening file\n");
        return;
    }
    fprintf(fp,"%s\n",role);
    fclose(fp);
    return;
}

void remove_admin(char* role){
    FILE* fp = fopen(ADMIN_DB,"r");
    if(fp == NULL){
        printf("Error opening file\n");
        return;
    }
    char temp[30] = ADMIN_DB;
    strcat(temp,".temp");
    FILE* fp2 = fopen(temp,"w");
    if(fp2 == NULL){
        printf("Error opening file\n");
        return;
    }
    char line[100];
    while(fgets(line,100,fp) != NULL){
        if(unlikely(line[0] == '#')) {
            fprintf(fp2,"%s",line);
            continue;
        }
        while(line[strlen(line)-1] == '\n' || (int)line[strlen(line)-1] == 13)//remove newline for compare
            line[strlen(line)-1] = '\0';
        if(strcmp(line,role) != 0){
            fprintf(fp2,"%s\n",line);
        }
    }
    fclose(fp);
    fclose(fp2);
    remove(ADMIN_DB);
    rename(temp,ADMIN_DB);
    return;
}
//list of db func
int basic_get_row_count(int end, FILE *fp){
    fseek(fp, 0, SEEK_SET);//prevent pointer on wrong position
    //get row count
    int row_count = 0;
    int colmun_count = 0;
    while(!feof(fp)&&!ferror(fp)){
        char buffer[100];
        fgets(buffer, sizeof(buffer),fp);
        if(unlikely(buffer[0] == '#' || buffer[0] == '\0')){//catch comment line and ignore
            continue;
        }
        colmun_count++;
        if(colmun_count == end){
            row_count++;
            colmun_count = 0;
        }
    }
    return row_count;
}

struct inventory read_db_invt(){//please open file in read mode
    FILE* fp = fopen(INVENTORY_DB, "r");
    struct inventory db;
    
    //gets the number of rows in the file
    int row_count = basic_get_row_count(ENDOFINVENTORY,fp);
    db.row = (struct inventory_row*)malloc(row_count * sizeof(struct inventory_row));
    db.db.row_count = row_count;

    fseek(fp,0,SEEK_SET);//reset fp to the beginning of the file

    //read data
    for(int i=0;i<row_count;i++){
        db.row[i].isdeleted = false;
        for(INVENTORY j=category;j<=ENDOFINVENTORY;j++){
            char buffer[100];
            fgets(buffer, sizeof(buffer),fp);
            if(unlikely(buffer[0] == '#')){//catch comment line and ignore
                j--;//decrement j to prevent skipping next column
            }else{
                buffer[strlen(buffer)] = '\0';
                while(buffer[strlen(buffer)-1] == '\n' || (int)buffer[strlen(buffer)-1] == 13){
                    buffer[strlen(buffer)-1] = '\0';

                }

                switch(j){
                    case category:
                        strcpy(db.row[i].category,buffer);
                        break;
                    case brand:
                        strcpy(db.row[i].brand,buffer);
                        break;
                    case product:
                        strcpy(db.row[i].product,buffer);
                        break;
                    case price_inv:
                        db.row[i].price = atof(buffer);
                        break;
                    case stock:
                        db.row[i].stock = atoi(buffer);
                        break;
                    case barcode_inv:
                        db.row[i].barcode = atol(buffer);
                        break;
                    
                }
            }
            
        }
    }
    if(ferror(fp)){
        printf("Error in reading file\n");
        db.db.init_status = false;
        return db;
    }
    fclose(fp);
    db.db.init_status = true;
    return db;
}
struct transaction read_db_tran(){
    FILE* fp = fopen(TRANSACTION_DB, "r");
    struct transaction db;

    //gets the number of rows in the file
    int row_count = basic_get_row_count(ENDOFTRANSACTION,fp);
    db.row = (struct transaction_row*)malloc(row_count * sizeof(struct transaction_row));
    db.db.row_count = row_count;

    fseek(fp,0,SEEK_SET);//reset fp to the beginning of the file

    //read data
    for(int i=0;i<row_count;i++){
        db.row[i].isdeleted = false;
        for(TRANSACTION j=date;j<=ENDOFTRANSACTION;j++){
            char buffer[100];
            fgets(buffer, sizeof(buffer),fp);
            if(unlikely(buffer[0] == '#')){//catch comment line and ignore
                j--;//decrement j to prevent skipping next column
            }else{
                buffer[strlen(buffer)] = '\0';
                while(buffer[strlen(buffer)-1] == '\n' || (int)buffer[strlen(buffer)-1] == 13){
                    buffer[strlen(buffer)-1] = '\0';

                }

                switch(j){
                    case date:
                        db.row[i].date = convert_to_date(buffer);
                        break;
                    case TIME:
                        db.row[i].time = convert_to_time(buffer);
                        break;
                    case id_tran:
                        db.row[i].id = atol(buffer);
                        break;
                    case price_tran:
                        db.row[i].price = atof(buffer);
                        break;
                    case quantity:
                        db.row[i].quantity = atoi(buffer);
                        break;
                    case barcode_tran:
                        db.row[i].barcode = atol(buffer);
                        break;   
                }
            }
            
        }
    }
    if(ferror(fp)){
        printf("Error in reading file\n");
        db.db.init_status = false;
        return db;
    }
    fclose(fp);
    db.db.init_status = true;
    return db;
}
struct user read_db_user(){
    FILE* fp = fopen(USER_DB,"r");
    struct user db;

    //gets the number of rows in the file
    int row_count = basic_get_row_count(ENDOFUSER,fp);
    db.row = (struct user_row*)malloc(row_count * sizeof(struct user_row));
    db.db.row_count = row_count;

    fseek(fp,0,SEEK_SET);//reset fp to the beginning of the file

    //read data
    for(int i=0;i<row_count;i++){
        db.row[i].isdeleted = false;
        for(USER j=name;j<=ENDOFUSER;j++){
            char buffer[100];
            fgets(buffer, sizeof(buffer),fp);
            if(unlikely(buffer[0] == '#')){//catch comment line and ignore
                j--;//decrement j to prevent skipping next column
            }else{
                buffer[strlen(buffer)] = '\0';//prevent any garbage value
                while(buffer[strlen(buffer)-1] == '\n' || (int)buffer[strlen(buffer)-1] == 13){
                    buffer[strlen(buffer)-1] = '\0';

                }

                switch(j){
                    case name:
                        strcpy(db.row[i].name,buffer);
                        break;
                    case role:
                        strcpy(db.row[i].role,buffer);
                        break;
                    case id_user:
                        db.row[i].id = atol(buffer);
                        break;
                }
            }
            
        }
    }
    if(ferror(fp)){
        printf("Error in reading file\n");
        db.db.init_status = false;
        return db;
    }
    fclose(fp);
    db.db.init_status = true;
    return db;
}

struct linkedlist* role_list_db(){
    struct user db = read_db_user();
    struct linkedlist* list = (struct linkedlist*)malloc(sizeof(struct linkedlist));
    //put unqie user role into linked list in acending order
    struct linkedlist* current = list;
    //initial the first node
    current->data = db.row[0].role;
    current->next = NULL;
    current->prev = NULL;
    for(int i=1;i<db.db.row_count;i++){
        char* role = db.row[i].role;
        int diff = strcmp(current->data,role);
        if(diff == 0){
            continue;
        }else if(diff > 0){
            while(diff > 0){
                if(diff == 0){
                    break;
                }else if(diff < 0){
                    //insert before current
                    struct linkedlist* new = (struct linkedlist*)malloc(sizeof(struct linkedlist));
                    new->data = role;
                    new->next = current;
                    new->prev = current->prev;
                    current->prev->next = new;
                    current->prev = new;
                    break;
                }else{
                    if(current->next == NULL){
                        //insert after current
                        struct linkedlist* new = (struct linkedlist*)malloc(sizeof(struct linkedlist));
                        new->data = role;
                        new->next = NULL;
                        new->prev = current;
                        current->next = new;
                        break;
                    }else{
                        current = current->next;
                        diff = strcmp(current->data,role);  
                    }
                }
            }
        }else{
            while(diff < 0){
                if(diff == 0){
                    break;
                }else if(diff > 0){
                    //insert after current
                    struct linkedlist* new = (struct linkedlist*)malloc(sizeof(struct linkedlist));
                    new->data = role;
                    new->next = current;
                    new->prev = current->prev;
                    current->prev->next = new;
                    current->prev = new;
                    break;
                }else{
                    if(current->prev == NULL){
                        //insert before current
                        struct linkedlist* new = (struct linkedlist*)malloc(sizeof(struct linkedlist));
                        new->data = role;
                        new->next = current;
                        new->prev = NULL;
                        current->prev = new;
                        break;
                    }else{
                        current = current->prev;
                        diff = strcmp(current->data,role);  
                    }
                }
            }
        }
    }
    return list;
}

bool update_db_invt(struct inventory invt){
    FILE* fpR = fopen(INVENTORY_DB,"r");
    char temp[30] = INVENTORY_DB;
    strcat(temp,".temp");
    FILE* fpW = fopen(temp,"w");
    if(fpR == NULL || fpW == NULL){
        printf("Error in opening file\n");
        return false;
    }
    for(int i=0;i<invt.db.row_count;i++){
        if(invt.row[i].barcode == -1024 || invt.row[i].isdeleted == true)//skip create new row
            continue;
        for(INVENTORY j=category;j<=ENDOFINVENTORY;j++){
            char buffer[100];
            do{
                if(unlikely(feof(fpR))){
                    break;
                }
                fgets(buffer, sizeof(buffer),fpR);
                if(buffer[0] == '#'){//catch comment line and print back to new file
                    fputs(buffer,fpW);
                }
            }while(buffer[0] == '#');
            switch(j){
                case category:
                    fprintf(fpW,"%s",invt.row[i].category);
                    break;
                case brand:
                    fprintf(fpW,"%s",invt.row[i].brand);
                    break;
                case product:
                    fprintf(fpW,"%s",invt.row[i].product);
                    break;
                case price_inv:
                    fprintf(fpW,"%.1f",invt.row[i].price);
                    break;
                case stock:
                    fprintf(fpW,"%d",invt.row[i].stock);
                    break;
                case barcode_inv:
                    fprintf(fpW,"%ld",invt.row[i].barcode);
                    break;
            }
            fprintf(fpW,"\n");
        }
    }
    //close file and replace old file with new file
    fclose(fpR);
    fclose(fpW);
    remove(INVENTORY_DB);
    rename(temp,INVENTORY_DB);
    return true;
    
}

bool update_db_tran(struct transaction tran){
    FILE* fpR = fopen(TRANSACTION_DB,"r");
    char temp[30] = TRANSACTION_DB;
    strcat(temp,".temp");
    FILE* fpW = fopen(temp,"w");
    if(fpR == NULL || fpW == NULL){
        printf("Error in opening file\n");
        return false;
    }
    for(int i=0;i<tran.db.row_count;i++){
        if(tran.row[i].isdeleted == true){
            continue;
        }
        for(TRANSACTION j=date;j<=ENDOFTRANSACTION;j++){
            char buffer[100];
            do{
                if(unlikely(feof(fpR))){
                    break;
                }
                fgets(buffer, sizeof(buffer),fpR);
                if(buffer[0] == '#'){//catch comment line and print back to new file
                    fputs(buffer,fpW);
                }
            }while(buffer[0] == '#');
            switch(j){
                case date:
                    fprintf(fpW,"%d-%02d-%02d",tran.row[i].date.year,tran.row[i].date.month,tran.row[i].date.day);
                    break;
                case TIME:
                    fprintf(fpW,"%02d:%02d:%02d",tran.row[i].time.hour,tran.row[i].time.minute,tran.row[i].time.second);
                    break;
                case id_tran:
                    fprintf(fpW,"%ld",tran.row[i].id);
                    break;
                case price_tran:
                    fprintf(fpW,"%.1f",tran.row[i].price);
                    break;
                case quantity:
                    fprintf(fpW,"%d",tran.row[i].quantity);
                    break;
                case barcode_tran:
                    fprintf(fpW,"%ld",tran.row[i].barcode);
                    break;
            }
            fprintf(fpW,"\n");
        }
    }
    //close file and replace old file with new file
    fclose(fpR);
    fclose(fpW);
    remove(TRANSACTION_DB);
    rename(temp,TRANSACTION_DB);
    return true;
}

bool update_db_user(struct user user){
    FILE* fpR = fopen(USER_DB,"r");
    char temp[30] = USER_DB;
    strcat(temp,".temp");
    FILE* fpW = fopen(temp,"w");
    if(fpR == NULL || fpW == NULL){
        printf("Error in opening file\n");
        return false;
    }
    for(int i=0;i<user.db.row_count;i++){
        if(user.row[i].isdeleted == true){
            continue;
        }
        for(USER j=name;j<=ENDOFUSER;j++){
            char buffer[100];
            do{
                if(unlikely(feof(fpR))){
                    break;
                }
                fgets(buffer, sizeof(buffer),fpR);
                if(buffer[0] == '#'){//catch comment line and print back to new file
                    fputs(buffer,fpW);
                }
            }while(buffer[0] == '#');
            switch(j){
                case name:
                    fprintf(fpW,"%s",user.row[i].name);
                    break;
                case role:
                    fprintf(fpW,"%s",user.row[i].role);
                    break;
                case id_user:
                    fprintf(fpW,"%ld",user.row[i].id);
                    break;
            }
            fprintf(fpW,"\n");
        }
    }
    //close file and replace old file with new file
    fclose(fpR);
    fclose(fpW);
    remove(USER_DB);
    rename(temp,USER_DB);
    return true;
}

bool append_inventory(struct inventory_row* row){
    FILE* fp = fopen(INVENTORY_DB,"a");
    if(fp == NULL){
        printf("Error in opening file\n");
        return false;
    }
    fprintf(fp,"%s\n",row->category);
    fprintf(fp,"%s\n",row->brand);
    fprintf(fp,"%s\n",row->product);
    fprintf(fp,"%.1f\n",row->price);
    fprintf(fp,"%d\n",row->stock);
    fprintf(fp,"%ld\n",row->barcode);
    fclose(fp);
    return true;
}

bool append_transaction_db(struct transaction_row* row){
    FILE* fp = fopen(TRANSACTION_DB,"a");
    if(fp == NULL){
        printf("Error in opening file\n");
        return false;
    }
    fprintf(fp,"%04d-%02d-%02d\n",row->date.year,row->date.month,row->date.day);
    fprintf(fp,"%02d:%02d:%02d\n",row->time.hour,row->time.minute,row->time.second);
    fprintf(fp,"%ld\n",row->id);
    fprintf(fp,"%.1f\n",row->price);
    fprintf(fp,"%d\n",row->quantity);
    fprintf(fp,"%ld\n",row->barcode);
    fclose(fp);
    return true;
}

bool append_user(struct user_row* row){
    FILE* fp = fopen(USER_DB,"a");
    if(fp == NULL){
        printf("Error in opening file\n");
        return false;
    }
    fprintf(fp,"%s\n",row->name);
    fprintf(fp,"%s\n",row->role);
    fprintf(fp,"%ld\n",row->id);
    fclose(fp);
    return true;
}

//checkout db support,

typedef struct cart{//linked list
    struct inventory_row* row;//pointer to the row
    int quantity;//quantity of the item
    struct cart* next;
}cart;


bool append_transaction(struct cart* cart,struct user_row* user){
    FILE* fp = fopen(TRANSACTION_DB,"a");
    if(fp == NULL){
        printf("Error in opening file\n");
        return false;
    }

    struct Date date = get_date();
    struct Time time = get_time();
    struct cart* temp = cart;
    if(temp == NULL)
        return false;
    
    do{

        fprintf(fp,"%d-%02d-%02d\n",date.year,date.month,date.day);
        fprintf(fp,"%02d:%02d:%02d\n",time.hour,time.minute,time.second);
        fprintf(fp,"%ld\n",user->id);
        fprintf(fp,"%f\n",temp->row->price);
        fprintf(fp,"%d\n",temp->quantity);
        fprintf(fp,"%ld\n",temp->row->barcode);
        if(temp->next == NULL)
            break;
        temp = temp->next;
    }while(temp->next != NULL);//this while condition just for safety,as it should already break in the if statement
    fclose(fp);
    return true;
}

bool update_stock_N_checkout(struct cart* cart,struct user_row* user){
    struct inventory invt = read_db_invt();
    struct cart* temp = cart;
    if(temp == NULL)
        return false;
    do{
        for(int i=0;i<invt.db.row_count;i++){

            if(invt.row[i].barcode == temp->row->barcode){
                invt.row[i].stock -= temp->quantity;
                break;
            }
        }
        if(temp->next == NULL)
            break;
        temp = temp->next;
    }while(temp->next != NULL);//this while condition just for safety,as it should already break in the if statement
    if(!append_transaction(cart,user)){
        return false;
    }
    if(!update_db_invt(invt)){
        return false;
    }
    return true;
}

//user
struct user_row* get_user(long userid){
    struct user_row* user = (struct user_row*)malloc(sizeof(struct user_row));
    FILE* fp = fopen(USER_DB,"r");
    if(fp == NULL){
        printf("Error in opening file\n");
        return NULL;
    }
    char buffer[100];
    
    //gets the number of rows in the file
    int row_count = basic_get_row_count(ENDOFUSER,fp);

    fseek(fp,0,SEEK_SET);//reset fp to the beginning of the file
    
    //read data
    for(int i=0;i<row_count;i++){
        for(USER j=name;j<=ENDOFUSER;j++){
            char buffer[100];
            fgets(buffer, sizeof(buffer),fp);
            if(buffer[0] == '#'){//catch comment line and ignore
                j--;//decrement j to prevent skipping next column
            }else{
                buffer[strlen(buffer)] = '\0';
                while(buffer[strlen(buffer)-1] == '\n' || (int)buffer[strlen(buffer)-1] == 13){
                    buffer[strlen(buffer)-1] = '\0';
                }

                switch(j){
                    case name:
                        strcpy(user->name,buffer);
                        break;
                    case role:
                        strcpy(user->role,buffer);
                        break;
                    case id_user:
                        user->id = atol(buffer);
                        if(user->id == userid){
                            fclose(fp);
                            return user;
                        }
                        break;
                }
            }
            
        }
    }
    fclose(fp);
    return NULL;
}