#include <iostream>
#include <algorithm> // std::sort
#include <sys/types.h>
#include <dirent.h>
#include <sys/stat.h>
#include <unistd.h>

#include "SequenceInput.h"

SequenceInput::SequenceInput(int x, int y, double temporal_step, string input_file_path):module(x,y,temporal_step){
    // Default input parameters
    InputFilePath = input_file_path;
    SkipNInitFrames = 0; // No frame skipped by default
    RepeatLastFrame = false; // Sim. is terminted after end of input
    InputFramePeriod = 1; // by default one new frame is used each simulation millisecond
    CurrentInFrameInd = 0; // First frame to load is number 0
    verbose = true;
    // Allocate image buffers buffer
    outputImage = new CImg<double> (sizeY, sizeX, 1, 1, 0);

    // Init. internal vars
    NextFrameTime = 0; // First frame must be received at time 0
    endOfInput = false;
}

// This method will probably not be used
SequenceInput::SequenceInput(const SequenceInput &copy):module(copy){
    InputFilePath = copy.InputFilePath;
    SkipNInitFrames = copy.SkipNInitFrames;
    RepeatLastFrame = copy.RepeatLastFrame;
    InputFramePeriod = copy.InputFramePeriod;
    NextFrameTime = copy.NextFrameTime;
    CurrentInFrameInd = copy.CurrentInFrameInd;
    inputFileList = copy.inputFileList;
    inputMovie = copy.inputMovie;
    endOfInput = copy.endOfInput;

    outputImage=new CImg<double>(*copy.outputImage);
}

SequenceInput::~SequenceInput(){
    closeInput();
    if(outputImage != NULL)
        delete outputImage;
}

//------------------------------------------------------------------------------//

bool SequenceInput::openInput(){
    bool ret_correct;
    struct stat input_seq_path_stat;

    ret_correct = false; // default return value

    // Check if the specified input-sequence path is a directory or a movie file
    if(stat(InputFilePath.c_str(), &input_seq_path_stat) == 0){
        if(verbose)
            cout << "Opening image sequence: " << InputFilePath << "..." << endl;
        if(S_ISDIR(input_seq_path_stat.st_mode)){ // The user has specified a directory as input sequence: load all the directory files
            DIR *dp; // Pointer to the opened input-directiry stream 
            dp = opendir(InputFilePath.c_str());
            if(dp != NULL){
                dirent* de;
                do{ // For each directory entry:
                    de = readdir(dp);
                    if(de != NULL){ // We got a valid entry
                        string curr_input_file_path(InputFilePath + de->d_name); // Compose the entire current dir entry path
                    
                        if(stat(curr_input_file_path.c_str(), &input_seq_path_stat) == 0){ // Try to get information about the de->d_name
                            if(!S_ISDIR(input_seq_path_stat.st_mode)){ // Subdirectories (including . and ..) are not included in the input file sequence
                                inputFileList.push_back(curr_input_file_path);
                                ret_correct = true; // At least one image was found, proceed
                            }
                        }else{
                            perror("Error accessing the specified input sequence directory: ");
                            break;
                        }
                    }
                }while (de != NULL); // Continue while we get valid directory entries
                closedir(dp);
                if(ret_correct){
                    sort(inputFileList.begin(), inputFileList.end()); // File will be load in alphabetical order
                    if(verbose)
                        cout << inputFileList.size() << " files in directory" << endl;
                }
            }else
                cout << "Error reading retina script: Cannot open input sequence directory " << InputFilePath << endl;

        } else { // The user has specified a file as input sequence: load the whole movie file
            inputMovie.load_inr(InputFilePath.c_str()); // We assume that the specified file is a movie (sequence of images)
            ret_correct = true;

            if(verbose)
                cout << inputMovie.depth() << " frames in movie file" << endl;
        }
    } else
        perror("Error accessing the specified input sequence: ");

    return(ret_correct);
}

void SequenceInput::closeInput(){
    // Nothing to do for the current implementation
}

void SequenceInput::skipFrame(){
    CurrentInFrameInd++;
}

//------------------------------------------------------------------------------//

bool SequenceInput::allocateValues(){
    bool ret_correct;
    module::allocateValues(); // Call the allocateValues() method of the base class
    
    ret_correct = openInput(); // Load INR file or open directory
    if(ret_correct){
        if(SkipNInitFrames>0)
            cout << "Skipping " << SkipNInitFrames << " input frames" << endl;
        for(int n_skipped_frames=0;n_skipped_frames<SkipNInitFrames;n_skipped_frames++)
            skipFrame(); // Skip frame
            
        // Use the first frame to find out the new dimensions of retina image size
        get_new_frame(); // Get first valid frame
        sizeY=outputImage->width();
        sizeX=outputImage->height();
        NextFrameTime=InputFramePeriod; // Next frame must be read at this time
        // output image should have been automatically resized after first frame load
    }

    return(ret_correct);
}

//------------------------------------------------------------------------------//

bool SequenceInput::set_SkipNInitFrames(int n_frames){
    bool ret_correct;
    if (n_frames>=0) {
        SkipNInitFrames = n_frames;
        ret_correct=true;
    } else
        ret_correct=false;
    return(ret_correct);
}

bool SequenceInput::set_RepeatLastFrame(bool repeat_flag){
    RepeatLastFrame = repeat_flag;
    return(true);
}

bool SequenceInput::set_InputFramePeriod(double sim_time_period){
    bool ret_correct;
    if (sim_time_period>0) {
        InputFramePeriod = sim_time_period;
        ret_correct=true;
    } else
        ret_correct=false;
    return(ret_correct);
}

//------------------------------------------------------------------------------//

bool SequenceInput::setParameters(vector<double> params, vector<string> paramID){

    bool correct = true;

    for (vector<double>::size_type i = 0;i < params.size() && correct;i++){
        const char * s = paramID[i].c_str();

        if (strcmp(s,"SkipNInitFrames")==0){
            correct = set_SkipNInitFrames((int)(params[i]));
        } else if (strcmp(s,"RepeatLastFrame")==0){
            correct = set_RepeatLastFrame(params[i] != 0.0);
        } else if (strcmp(s,"InputFramePeriod")==0){
            correct = set_InputFramePeriod(params[i]);
        } else{
              correct = false;
        }
    }
    return correct;
}

//------------------------------------------------------------------------------//

// This method can only be used to set the simulation time
void SequenceInput::feedInput(double sim_time, const CImg<double> &new_input, bool isCurrent, int port){
    // Update the current simulation time (although it is currently not used)
    simTime = sim_time;
}


//------------------------------------------------------------------------------//

void SequenceInput::get_new_frame(){    
    if(inputFileList.size() == 0){ // filename list is empty, so input was a movie file
        if(CurrentInFrameInd < (unsigned long)inputMovie.depth()) // Some frames still availables to be read
            *outputImage = inputMovie.get_slice(CurrentInFrameInd++);
        else
            if(!endOfInput && !RepeatLastFrame){
                if(verbose)
                    cout << "\rNo more input frames: terminating simulation" << endl;
                endOfInput=true; // Indicate an end of input and simulation
            }
    
    } else { // Input was a directory
        if(CurrentInFrameInd < inputFileList.size()) // Some files still availables to be read
            outputImage->load(inputFileList.at(CurrentInFrameInd++).c_str());
        else {
            if(!endOfInput && !RepeatLastFrame){
                if(verbose)
                    cout << "\rNo more input images: terminating simulation" << endl;
                endOfInput=true; // Indicate an end of input and simulation
            }
        }
    }
}

void SequenceInput::update(){
    while(simTime >= NextFrameTime){ // It is time to get a new frame?:
        get_new_frame();
        NextFrameTime += InputFramePeriod; // Update start time of the next frame
    }
}

//------------------------------------------------------------------------------//

// This method returns the last received image which is stored in the output buffer
CImg<double>* SequenceInput::getOutput(){
    if(endOfInput)
        return NULL;
    else
        return outputImage;
}

//------------------------------------------------------------------------------//

bool SequenceInput::isDummy() {
    return false;
    };