#include <ctype.h>
#include <limits.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include "ramjet.h"

char pc_2_char[16]  = {' ', 'P', 'N', 'B', 'R', 'Q', 'K', ' ',
                       ' ', 'p', 'n', 'b', 'r', 'q', 'k', ' '};

const char* const sq_2_str[] = {"a8", "b8", "c8", "d8", "e8", "f8", "g8", "h8",
                                "a7", "b7", "c7", "d7", "e7", "f7", "g7", "h7",
                                "a6", "b6", "c6", "d6", "e6", "f6", "g6", "h6",
                                "a5", "b5", "c5", "d5", "e5", "f5", "g5", "h5",
                                "a4", "b4", "c4", "d4", "e4", "f4", "g4", "h4",
                                "a3", "b3", "c3", "d3", "e3", "f3", "g3", "h3",
                                "a2", "b2", "c2", "d2", "e2", "f2", "g2", "h2",
                                "a1", "b1", "c1", "d1", "e1", "f1", "g1", "h1"};

int char_2_pc[UCHAR_MAX + 1] = {0};

// initialise the char->pc conversion table
static void char_2_pc_init()
{
    char_2_pc['P'] = PT_2_PC(PAWN, WHITE);
    char_2_pc['N'] = PT_2_PC(KNIGHT, WHITE);
    char_2_pc['B'] = PT_2_PC(BISHOP, WHITE);
    char_2_pc['R'] = PT_2_PC(ROOK, WHITE);
    char_2_pc['Q'] = PT_2_PC(QUEEN, WHITE);
    char_2_pc['K'] = PT_2_PC(KING, WHITE);

    char_2_pc['p'] = PT_2_PC(PAWN, BLACK);
    char_2_pc['n'] = PT_2_PC(KNIGHT, BLACK);
    char_2_pc['b'] = PT_2_PC(BISHOP, BLACK);
    char_2_pc['r'] = PT_2_PC(ROOK, BLACK);
    char_2_pc['q'] = PT_2_PC(QUEEN, BLACK);
    char_2_pc['k'] = PT_2_PC(KING, BLACK);
}

// initialise the constants above
void rj_init()
{
    char_2_pc_init();
}

// return a pointer to the first non-white character,
// force a terminator at the end of the first token,
// and set the original pointer after it
char* grab_token(char** str)
{
    // find the beginning of the first token
    char* tk = *str;
    while(!isgraph(tk[0]) && tk[0] != '\0')
        tk = &tk[1];

    // find the end of the first token
    *str = tk;
    while(isgraph((*str)[0]))
        *str = &(*str)[1];

    // add a terminator if there is any character left
    if((*str)[0] != '\0') {
        (*str)[0] = '\0';
        *str = &(*str)[1];
    }

    return tk;
}

// return a pointer to the first non-white character after the specified token
char* after_token(char **str, const char *match)
{
    char *token;

    for(token = grab_token(str); token[0] != '\0'; token = grab_token(str))
        if(strcmp(token, match) == 0)
            break;

    return *str;
}

// return a pointer to the end of the string,
// after appending a space character to it
char* append_space(char* str)
{
    // find the end of the string
    while(str[0] != '\0')
        str = &str[1];

    // append a space character
    str[0] = ' ';
    str[1] ='\0';

    return &str[1];
}

// print the string both on the file and stdout adding a newline at the end
void print_line(FILE* fp, const char* str)
{
    printf("%s\n", str);
    if(fp != NULL)
        fprintf(fp, "ENG: %s\n", str);
}

// bundles a function/argument pair so it can cross the pthreads trampoline
typedef struct {
    void (*func)(void*);
    void* arg;
} THREAD_ARGS;

// pthreads expects a "void* f(void*)" entry point; unwrap and call the
// original "void f(void*)" callback, then let the thread clean itself up
static void* thread_trampoline(void* args_)
{
    THREAD_ARGS* args = (THREAD_ARGS*) args_;
    void (*func)(void*) = args->func;
    void* arg = args->arg;

    free(args);
    func(arg);

    return NULL;
}

// run a single-argument function in a new, detached thread
void new_thread(void (*func)(void*), void* arg)
{
    THREAD_ARGS* args = (THREAD_ARGS*) malloc(sizeof(THREAD_ARGS));
    pthread_t tid;

    args->func = func;
    args->arg = arg;

    pthread_create(&tid, NULL, thread_trampoline, args);
    pthread_detach(tid);
}

// get a time-stamp from the operating system
// (masked to 32 bits to match the wraparound behaviour time_diff() expects)
MILLISEC time_now()
{
    struct timespec ts;
    uint64_t ms;

    clock_gettime(CLOCK_MONOTONIC, &ts);
    ms = (uint64_t) ts.tv_sec * 1000 + (uint64_t) ts.tv_nsec / 1000000;

    return (MILLISEC) (ms & 0xFFFFFFFFu);
}

// return the time elapsed in milliseconds
MILLISEC time_diff(MILLISEC start)
{
    MILLISEC ms = time_now();
    return (ms >= start)? ms - start : (ms + MS_MAX) - start;
}

// test the functions above
int rj_test()
{
    // piece-type and piece-code mapping
    if(PC_2_PT(PT_2_PC(KNIGHT, WHITE)) != KNIGHT ||
       PC_2_PT(PT_2_PC(KNIGHT, BLACK)) != KNIGHT ||
       PC_2_PL(PT_2_PC(KNIGHT, WHITE)) != WHITE ||
       PC_2_PL(PT_2_PC(KNIGHT, BLACK)) != BLACK)
        return 1;

    // piece-character mapping
    PLAYER pl; PIECETYPE pt;
    for(pl = WHITE; pl <= BLACK; ++pl)
        for(pt = PAWN; pt <= KING; ++pt)
            if(char_2_pc[(int) pc_2_char[PT_2_PC(pt, pl)]] != PT_2_PC(pt, pl))
                return 2;

    // square range
    if(IS_SQ_BAD(A8) || IS_SQ_BAD(H1) || !IS_SQ_BAD(SQ_NULL) || !IS_SQ_BAD(-1))
        return 3;

    // square transformations
    if(SQ_MIRROR(D3, WHITE) != D3 || SQ_MIRROR(D3, BLACK) != D6 ||
       SQ_FRONT(F2, WHITE) != F3 || SQ_FRONT(F2, BLACK) != F1 ||
       SQ_DIA_W(C5, WHITE) != B6 || SQ_DIA_W(C5, BLACK) != B4 ||
       SQ_DIA_E(G4, WHITE) != H5 || SQ_DIA_E(G4, BLACK) != H3)
        return 4;

    // castle targets
    if(SQ_K_CASTLE(1, WHITE) != G1 || SQ_K_CASTLE(1, BLACK) != G8 ||
       SQ_R_CASTLE(1, WHITE) != F1 || SQ_R_CASTLE(1, BLACK) != F8)
        return 5;

    // square-string mapping
    SQUARE sq; char buffer[4];
    for(sq = 0; sq < 64; ++sq) {
        strcpy(buffer, sq_2_str[sq]);
        if(STR_2_SQ(buffer) != sq)
            return 6;
    }

    // append space
    char example[32] = "\t Alas! My\tlove, you\r\n";
    char* str = example;
    char* ptr = append_space(str);
    if(strcmp(str, "\t Alas! My\tlove, you\r\n ") || ptr != &str[23])
        return 7;

    // grab token
    ptr = grab_token(&str);
    if(ptr[0] != 'A' || example[7] != '\0' || str != &example[8])
        return 8;

    return 0;
}
