/**
 *
 * @file src/parser/PajeParser/PajeFileManager.cpp
 *
 * @copyright 2008-2024 Bordeaux INP, CNRS (LaBRI UMR 5800), Inria,
 *                      Univ. Bordeaux. All rights reserved.
 *
 * @author Camille Ordronneau
 * @author Johnny Jazeix
 * @author Mathieu Faverge
 * @author Jean-Alexandre Collin
 * @author Francois Trahay
 * @author Augustin Degomme
 *
 * @date 2024-07-17
 */

#include <cstring>
#include <string>
#include <fstream>
#include <iostream>
/* -- */
#include "parser/PajeParser/PajeFileManager.hpp"
/* -- */
using namespace std;

PajeFileManager::PajeFileManager() :
    _filename("") {
    _tokens = new char *[_PAJE_NBMAXTKS];
}

PajeFileManager::PajeFileManager(const char *filename, ios_base::openmode mode) :
    _filename(filename), _filesize(-1), _lineid(0), _nbtks(0) {
    _tokens = new char *[_PAJE_NBMAXTKS];
    open(filename, mode);
}

PajeFileManager::~PajeFileManager() {
    close();
    delete[] _tokens;
}

void PajeFileManager::open(const char *filename, ios_base::openmode mode) {

    _filename = filename;
    ifstream::open(filename, mode);

    if (fail())
        throw "Fail to open file";

    // get length of file:
    seekg(0, ios::end);
    _filesize = tellg();
    seekg(0, ios::beg);

    // For windows, clear the eof byte for ept files after reading the first file
    clear();
}

void PajeFileManager::close() {
    ifstream::close();
    _filesize = -1;
}

long long PajeFileManager::get_filesize() const {
    return _filesize;
}

long long PajeFileManager::get_size_loaded() {
    return tellg();
}

float PajeFileManager::get_percent_loaded() {
    if (_filesize != -1) {
        return (float)tellg() / (float)_filesize;
    }
    else {
        return 0.;
    }
}

int PajeFileManager::get_line(PajeLine *lineptr) {
    unsigned int i = 0;
    int itks = 0;
    char c;

    // We set all the tokens to NULL
    for (i = 0; i < _PAJE_NBMAXTKS; i++) {
        _tokens[i] = nullptr;
    }

    std::getline(*this, _line);
    if (rdstate() != ifstream::goodbit) {
        setstate(ifstream::eofbit); // We stop the reading if something happens to the state of the file.
        lineptr->_nbtks = 0;
        return _lineid;
    }
    _lineid++;

    // Set the adress of the first token
    c = _line[0];
    _tokens[itks] = &_line[0];

    for (i = 0; i < _line.size() /*&& ((c != '\n') &&(c != '\r\n')&&(c != '\r') && (c != '\0'))*/ && (itks < _PAJE_NBMAXTKS); i++) {
        c = _line[i];

        switch (c) {
            // It's the end of the line, we just add the end caractere
            // for the last token and increase the counter
        case '\0':
        case '\n': {
            _line[i] = '\0';
            itks++;
            break;
        }
        case '%': {
            // In these case, we just have two tokens, one with the % and one without
            itks++;
            _tokens[itks] = &_line[i + 1];
            break;
        }
        case '\'': {
            // Check if we are at the beginnning of a new one or not
            if (_tokens[itks][0] == '\'') {
                _line[i] = '\0';
                _tokens[itks]++;
            }
            else {
                _line[i] = '\0';
                itks++;
                _tokens[itks] = &_line[i + 1];
            }

            // Start a long token
            while ((i < _line.size()) && (_line[i] != '\'')) {
                i++;
            }
            if (i == _line.size()) {
                setstate(ifstream::eofbit);
                throw "Overflow";
                return -1;
            }
            // We finish the token by replacing the \' by \0
            _line[i] = '\0';
            if (i + 1 < _line.size()) { // begin a new token if we are not at the end of the line
                itks++;
                _tokens[itks] = &_line[i + 1];
            }
            break;
        }
        case '"': {
            // Check if we are at the beginnning of a new one or not
            if (_tokens[itks][0] == '"') {
                _line[i] = '\0';
                _tokens[itks]++;
            }
            else {
                _line[i] = '\0';
                itks++;
                _tokens[itks] = &_line[i + 1];
            }

            // Start a long token (-1 to have place or the null caractere)
            while ((i < _line.size()) && (_line[i] != '"')) {
                i++;
            }

            // We finish the token by replacing the " by \0
            _line[i] = '\0';
            if (i + 1 < _line.size()) { // begin a new token if we are not at the end of the line
                itks++;
                _tokens[itks] = &_line[i + 1];
            }
            if (i == _line.size()) {
                setstate(ifstream::eofbit);
                throw "Overflow";
                return -1;
            }
            break;
        }
        case ' ':
        case '\t': {
            // Skip all spaces
            while ((i < _filesize) && (i < _line.size()) && ((_line[i] == ' ') || (_line[i] == '\t'))) {
                _line[i] = '\0';
                i++;
            }
            if (i == _filesize) {
                setstate(ifstream::eofbit);
                throw "Overflow";
                return -1;
            }
            // We store a new token if it's a new space
            if (i != _line.size()) {
                if (_tokens[itks] != nullptr && _tokens[itks][0] != '\0')
                    itks++;
                _tokens[itks] = &_line[i];
                i--;
            }
            break;
        }
        case '#': {
            // Skip the comment until new line or end of text
            while ((i < _filesize) && (i < _line.size()) && ((_line[i] != '\n') && (_line[i] != '\0'))) {
                _line[i] = '\0';
                i++;
            }

            if (i == _filesize) {
                setstate(ifstream::eofbit);
                throw "Overflow";
                return -1;
            }

            // Let the parser handle properly new line or end of text
            if (((_line[i] == '\n') || (_line[i] == '\0'))) {
                i--;
            }

            break;
        }
        default:
            break;
        }
    }
    itks++; // add a token to avoid bugs, because we don't meet \n with std strings
    // We remove the last token if it is empty
    if ((itks > 0) && (_tokens[itks - 1][0] == '\0'))
        itks--;

    _nbtks = itks;
    lineptr->_id = _lineid;
    lineptr->_nbtks = _nbtks;
    lineptr->_tokens = _tokens;

    return _lineid;
}

void PajeFileManager::print_line() {
    int i;

    cout << "==================" << _lineid << "=====================" << endl;
    for (i = 0; i < _nbtks; i++) {
        cout << i << " : " << _tokens[i] << endl;
    }
    cout << "===========================================" << endl;
}
