#include <stdio.h>
#include <stdlib.h>
#include <sys\types.h> 
#include <sys\stat.h>
#include <windows.h>

#include "History.h"

typedef void (*_history_on_line_read)(char*);

int _historyIsInitialized = 0;

const char* _historyFilePath = NULL;

#define _HISTORY_ENTRIES_CAPACITY 17
char* _historyEntries[_HISTORY_ENTRIES_CAPACITY];
int _historyEntriesCount = 0;

void _history_read_lines(const char* filePath, _history_on_line_read onLineReadCallback)
{
    _historyEntriesCount = 0;

    struct stat info;
    if (stat(filePath, &info) != 0) {
        return;
    }

    char* file = (char*)malloc(info.st_size);
    if (file == NULL) {
        return;
    }
    FILE* fp;
    errno_t result = fopen_s(&fp, filePath, "rb");
    if (result) {
        free(file);
        return;
    }
    // try to read a single block of info.st_size bytes
    size_t blocks_read = fread(file, info.st_size, 1, fp);
    if (blocks_read != 1) {
        fclose(fp);
        free(file);
        return;
    }
    fclose(fp);

    char* line = (char*)malloc((MAX_PATH+1) * sizeof(char));
    if (line == NULL) {
        free(file);
        return;
    }

    char* curCharInFile = file;
    char* curCharInLine = line;
    int position = 0;
    while (position < info.st_size) {
        if (*curCharInFile == '\r' || *curCharInFile == '\n') {
            // we've reached end of line
            *curCharInLine = 0; // terminate line string
            if (strlen(line) != 0) {
                onLineReadCallback(line);
                // reset line
                curCharInLine = line;
            }
        }
        else {
            // regular character, so accumulate it
            *curCharInLine++ = *curCharInFile;
        }

        // next character
        curCharInFile++;
        position++;
    }
    // if we're in the middle of a line and the file contents ended, we have to
    // count this last line as well
    if (curCharInLine != line) {
        // line contains at least one character
        *curCharInLine = 0; // terminate line string
        onLineReadCallback(line);
    }
    free(line);
    free(file);
}

void _history_process_line(char* line) {
    if (line == NULL || strlen(line) == 0) {
        return;
    }

    if (_historyEntriesCount >= _HISTORY_ENTRIES_CAPACITY) {
        return;
    }

    // allocate memory for line into storage
    size_t lineLength = strlen(line);
    _historyEntries[_historyEntriesCount] = (char*)malloc((lineLength + 1) * sizeof(char));
    if (_historyEntries[_historyEntriesCount] == NULL) {
        return;
    }

    // copy line into storage
    strcpy_s(_historyEntries[_historyEntriesCount], lineLength+1, line);

    _historyEntriesCount++;
}

void _history_delete_entries() {
    for (int i = 0; i < _historyEntriesCount; i++) {
        if (_historyEntries[i] != NULL) {
            free(_historyEntries[i]);
            _historyEntries[i] = NULL;
        }
    }

    _historyEntriesCount = 0;
}

void _history_read_entries_from_disk() {
    _history_delete_entries();
    _history_read_lines(_historyFilePath, _history_process_line);
}

void _history_save_entries_to_disk() {
    FILE* file = fopen(_historyFilePath, "w");
    if (file == NULL) {
        return;
    }

    for (int i = 0; i < _historyEntriesCount; i++) {
        fputs(_historyEntries[i], file);
        fputc('\n', file);
    }

    fclose(file);
}

void history_start(const char* filePath) {
    history_destroy();

    _historyFilePath = filePath;
    _history_read_entries_from_disk();

    _historyIsInitialized = 1;
}

void history_destroy() {
    if (!_historyIsInitialized) {
        return;
    }

    _history_delete_entries();
    _historyIsInitialized = 0;
}

const char** history_get_entries(int* count)
{
    *count = _historyEntriesCount;
    return _historyEntries;
}

void history_add(const char* filePath)
{
    // a new entry may be added now
    
    // refresh from disk
    _history_read_entries_from_disk();

    // compare incoming entry, to see if it's already in history
    for (int i = 0; i < _historyEntriesCount; i++) {
        if (!strcmp(_historyEntries[i], filePath)) {
            // it's already in history, so we bring it to front

            char* matched = _historyEntries[i];

            // shift all entries before matched index to the right once
            for (int j = i; j >= 1; j--) {
                _historyEntries[j] = _historyEntries[j - 1];
            }

            _historyEntries[0] = matched;
            _history_save_entries_to_disk();
            return;
        }
    }

    // incoming is not already in history

    // allocate memory for incoming entry
    size_t lineLength = strlen(filePath);
    char* incomingEntry = (char*)malloc((lineLength + 1) * sizeof(char));
    if (incomingEntry == NULL) {
        return;
    }

    // save last entry in case we need to free
    char* lastEntry = _historyEntries[_HISTORY_ENTRIES_CAPACITY - 1];

    // first shift once all entries to the right
    // (last entry is preserved above)
    for (int i = _HISTORY_ENTRIES_CAPACITY - 1; i >= 1; i--) {
        _historyEntries[i] = _historyEntries[i - 1];
    }

    // copy incoming into entry and set as first entry
    strcpy_s(incomingEntry, lineLength + 1, filePath);
    _historyEntries[0] = incomingEntry;

    if (lastEntry != NULL) {
        // history was full, so we have to free previous last entry's memory
        free(lastEntry);
    }
    else {
        // history was not full, so count is now higher
        _historyEntriesCount++;
    }

    _history_save_entries_to_disk();
}
