/*
** This file is part of the ViTE project.
**
** This software is governed by the CeCILL-A license under French law
** and abiding by the rules of distribution of free software. You can
** use, modify and/or redistribute the software under the terms of the
** CeCILL-A license as circulated by CEA, CNRS and INRIA at the following
** URL: "http://www.cecill.info".
**
** As a counterpart to the access to the source code and rights to copy,
** modify and redistribute granted by the license, users are provided
** only with a limited warranty and the software's author, the holder of
** the economic rights, and the successive licensors have only limited
** liability.
**
** In this respect, the user's attention is drawn to the risks associated
** with loading, using, modifying and/or developing or reproducing the
** software by the user in light of its specific status of free software,
** that may mean that it is complicated to manipulate, and that also
** therefore means that it is reserved for developers and experienced
** professionals having in-depth computer knowledge. Users are therefore
** encouraged to load and test the software's suitability as regards
** their requirements in conditions enabling the security of their
** systems and/or data to be ensured and, more generally, to use and
** operate it in the same conditions as regards security.
**
** The fact that you are presently reading this means that you have had
** knowledge of the CeCILL-A license and that you accept its terms.
**
**
** ViTE developers are (for version 0.* to 1.0):
**
**        - COULOMB Kevin
**        - FAVERGE Mathieu
**        - JAZEIX Johnny
**        - LAGRASSE Olivier
**        - MARCOUEILLE Jule
**        - NOISETTE Pascal
**        - REDONDY Arthur
**        - VUCHENER Clément
**
*/
#include <stdlib.h>
#include <cstring>
#include <string>
#include <fstream>
#include <iostream>
#ifdef WIN32
#include <windows.h>
#include <limits.h>
#else
#include <sys/mman.h>
#endif
#include <stdio.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <queue>
/* -- */
#include "common/Errors.hpp"
#include "parser/PajeParser/mt_PajeFileManager.hpp"
/* -- */
using namespace std;


#define MAX_SIZE_UNCUT 100*1024*1024

mt_PajeFileManager::mt_PajeFileManager() : _filename(""), _filesize(0), _total_filesize(0), _nbchunks(0), _current_chunk(0), _mapped_size(0), _eoc(false), _lineid(0),_nbtks(0), _old_line(NULL), _global_offset(0) {
    //_tokens = new char*[_PAJE_NBMAXTKS];
    _offset=0;
	_eof = false;
}

mt_PajeFileManager::mt_PajeFileManager( const char * filename) :
  _filename(filename), _filesize(0), _total_filesize(0), _nbchunks(0), _mapped_size(0), _eoc(false), _lineid(0), _nbtks(0), _old_line(NULL), _global_offset(0)
{
    //_tokens = (char**)malloc(sizeof(char*)*_PAJE_NBMAXTKS);
    _offset=0;
    _eof = false;
    open(filename);
}

mt_PajeFileManager::~mt_PajeFileManager() {
#ifdef WIN32
	CloseHandle(_fd);
	UnmapViewOfFile(_line);
#else
    munmap( _line, _mapped_size);
#endif
	
	
	#ifndef WIN32
	fclose(_fd);
    #endif

	close();
    //if(_tokens!=NULL)delete _tokens;
}

void mt_PajeFileManager::open ( const char * filename ) {

    _filename = filename;
#ifdef WIN32
    _fd = CreateFile (filename, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
#else
    _fd=fopen(filename, "r");
    if (fail()) 
        throw "Fail to open file";
#endif


#ifndef WIN32
    struct stat buf; // for the file size

    if (-1 == stat(filename, &buf)) // Getq the size
    {
        Error::set(Error::VITE_ERR_FSTAT, 0, Error::VITE_ERRCODE_ERROR);

        if(_fd)fclose(_fd); // Close it if wrong
        throw "Fail to stat file";
    }
    _filesize=(long)buf.st_size;
#else
    LARGE_INTEGER size;
    GetFileSizeEx(_fd, &size);
    _filesize=size.QuadPart;
#endif

#ifdef WIN32
    SYSTEM_INFO si;
    GetSystemInfo(&si);
    _pageSize = (int) si.dwAllocationGranularity;

    if (_filesize>MAX_SIZE_UNCUT){
        _current_chunk=0;
        _mapped_size=MAX_SIZE_UNCUT;
    
    }else{
        _mapped_size=_filesize;
        
    }

    _fm = CreateFileMapping(_fd, NULL, PAGE_READONLY, 0, 0, NULL);
    _line = (char *)MapViewOfFile(_fm, FILE_MAP_COPY, 0, 0, _mapped_size);
#else
    _pageSize=getpagesize();
    if (_filesize>MAX_SIZE_UNCUT){
        _current_chunk=0;
        _mapped_size=MAX_SIZE_UNCUT;
    }else{
        _mapped_size=_filesize;
    }

    if(_mapped_size > 0) {
        _line = (char*)mmap(0,_mapped_size, PROT_READ|PROT_WRITE, MAP_PRIVATE, fileno(_fd),0); // Mapping of the file

        madvise(_line, _mapped_size , MADV_SEQUENTIAL);//advise kernel that we want to read sequentially in the file
    } else {
        // empty file
        _eof=true;
    }
#endif

    if ((char*)-1 == _line)
    {
#ifndef WIN32
        if(_fd)fclose(_fd); // Close it if wrong
#endif
        Error::set(Error::VITE_ERR_OPEN, 0, Error::VITE_ERRCODE_ERROR);
        throw "Fail to open file";
    }

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

void mt_PajeFileManager::close() {
    _filesize = 0;
}

int mt_PajeFileManager::get_filesize() const {
    return _filesize;
}

int mt_PajeFileManager::get_size_loaded() {
    return _global_offset+_offset;
}

float mt_PajeFileManager::get_percent_loaded() {
    if(_filesize != 0) {
	return (float)((double)(_global_offset+_offset) / (double)_filesize);
    }
    else {
	return 0.;
    }
}

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

    _tokens = (char**)calloc(_PAJE_NBMAXTKS,sizeof(char*));
    _lineid++;

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

    //max offset for a single line
    long max_offset=_offset+_pageSize;

    for(i = _offset; (i < max_offset) && (i < _filesize) && ((c != '\n')  && (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 < _filesize) && (i < max_offset-1 ) && (_line[i] != '\'')) {
		    i++;
		}

		// If i is too big, we have an error
		if ( !(i < max_offset -1) ) {
		    throw "overflow";
		    return -1;
		}

        if(i==_filesize){
		    _eof=true;
		    throw "Overflow";
		    return -1;
        }

		// We finish the token by replacing the \' by \0
		_line[i] = '\0';
		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 < _filesize) && (i < max_offset-1 ) && (_line[i] != '"')) {
		    i++;
		}

		// If i is too big, we have an error
		if ( !(i < max_offset)) {
		    throw "Overflow";
		    return -1;
		}

		if(i==_filesize){
		    _eof=true;
		    throw "Overflow";
		    return -1;
        }

		// We finish the token by replacing the " by \0
		_line[i] = '\0';
		itks++;
		_tokens[itks] = &_line[i+1];
		break;
	    }
	case ' '  :
	case '\t' :
	    {
		// Skip all spaces
		while ( (i<_filesize) && (i < max_offset-1 ) &&
			((_line[i] == ' ') || (_line[i] == '\t')) ) {

			  _line[i] = '\0';
		    i++;
		}
		if(i==_filesize){
		    _eof=true;
		    throw "Overflow";
		    return -1;
    }
		// We store a new token if it's a new space
		if (_tokens[itks][0] != '\0')
		    itks++;
		_tokens[itks] = &_line[i];
		i--;
		break;
	    }
	default :
	    break;
	}
    }

    if(itks==0){
        i+=sizeof(char); //we read one char but no token : empty line, advance the offset of one char, in order not to loop
    }
    _offset=i;

    // 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;

   #ifdef WIN32

	 if (_filesize>MAX_SIZE_UNCUT){

        if(_global_offset+_offset>=_filesize)_eof=true;
        else{
            if(_offset+_pageSize>=MAX_SIZE_UNCUT){ //time to mmap another part of the file, else we might miss a line

                _global_offset+=_offset;

				DWORD high_offset=(_global_offset/ULONG_MAX);

				//to align correctly
				DWORD low_offset=(_global_offset%((ULONGLONG)(ULONG_MAX)+1));
				DWORD low_aligned_offset=(low_offset/_pageSize)*_pageSize;
				_offset=low_offset % _pageSize;
				_global_offset=high_offset*((ULONGLONG)(ULONG_MAX)+1) + low_aligned_offset;

                _current_chunk++;
                if (_global_offset+MAX_SIZE_UNCUT>=_filesize) _mapped_size= _filesize-_global_offset;
                else _mapped_size=MAX_SIZE_UNCUT;

                //munmap(_line, MAX_SIZE_UNCUT);
                //printf("mmaping %d bytes for chunk %d\n",_mapped_size, _current_chunk);
                _old_line=_line;
                _eoc=true;// we finished a chunk
				_line = (char *)MapViewOfFile(_fm, FILE_MAP_COPY,high_offset, low_aligned_offset,_mapped_size);
					if (!_line)
			    {
					DWORD d=GetLastError();
                    //printf("mmaping failed %d %d %llu %lu %lu %lu\n",_mapped_size, _current_chunk, _global_offset, d, (DWORD)( _global_offset/ULONG_MAX), (DWORD) (_global_offset%ULONG_MAX));
					Error::set(Error::VITE_ERR_OPEN, 0, Error::VITE_ERRCODE_ERROR);
                    throw "Fail to open file";
                }

            }
        }
    }else if(_offset>=_filesize){
            _eof=true;
    }
   #else

   if (_filesize>MAX_SIZE_UNCUT){

        if(_global_offset+_offset>=_filesize)_eof=true;
        else{
            if(_offset+_pageSize>=MAX_SIZE_UNCUT){ //time to mmap another part of the file, else we might miss a line

                _global_offset+=_offset;
                _offset=_global_offset % getpagesize();
                _global_offset=(_global_offset/getpagesize())*getpagesize();

                _current_chunk++;
                if (_global_offset+MAX_SIZE_UNCUT>=_filesize) _mapped_size= _filesize-_global_offset;
                else _mapped_size=MAX_SIZE_UNCUT;

                //munmap(_line, MAX_SIZE_UNCUT);
                //printf("mmaping %d bytes for chunk %d\n",_mapped_size, _current_chunk);
                _old_line=_line;
                _eoc=true;// we finished a chunk
                _line = (char*)mmap(0, _mapped_size, PROT_READ|PROT_WRITE, MAP_PRIVATE, fileno(_fd),_global_offset); // Mapping of the file
                madvise(_line, _mapped_size , MADV_SEQUENTIAL);//advise kernel that we want to read sequentially in the file

                if ((char*)-1 == _line)
                {
                    //printf("mmaping failed\n",_mapped_size, _current_chunk);
		            if(_fd)fclose(_fd); // Close it if wrong
                    Error::set(Error::VITE_ERR_OPEN, 0, Error::VITE_ERRCODE_ERROR);
                    throw "Fail to open file";
                }

            }
        }
    }else if(_offset>=_filesize){
            _eof=true;
    }

    #endif
    return _lineid;
}

bool mt_PajeFileManager::eoc(){return _eoc;}

void mt_PajeFileManager::close_old_chunk(){

if(_old_line!=NULL){
	#ifndef WIN32
    munmap(_old_line, MAX_SIZE_UNCUT);
	#else
	UnmapViewOfFile(_old_line);
	//static int t=0;
	//printf("unmap %d\n", t);
	#endif
    _eoc=false;
    _old_line=NULL;
}
}

bool mt_PajeFileManager::eof(){
    return _eof;
}

void mt_PajeFileManager::print_line() {
    int i;

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

}
