/**
 * @file TimerCallback.cpp
 * @author   Ulrich Kemloh <kemlohulrich@gmail.com>
 * @version 0.1
 * Copyright (C) <2009-2010>
 *
 * @section LICENSE
 * This file is part of OpenPedSim.
 *
 * OpenPedSim is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * any later version.
 *
 * OpenPedSim is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with OpenPedSim. If not, see <http://www.gnu.org/licenses/>.
 *
 * @section DESCRIPTION
 * This class is called by the timer and update all elements on the screen
 *
 * \brief Callback class for the visualisation thread. Triggered by the timer
 *
 *
 *
 *  Created on: 11.05.2009
 *
 */


#include <string>
#include <vector>
#include <algorithm>
#include <cstdlib>
#include <iostream>


#ifdef WIN32
#include <vtkAVIWriter.h>
#include <windows.h>
#define TRAVISTO_FFMPEG
#endif


#ifdef __linux__
#ifdef __vtkFFMPEGWriter_h
#include <vtkFFMPEGWriter.h>
#define TRAVISTO_FFMPEG
#endif
#endif

#include <QObject>
#include <QString>
#include <QTime>
#include <QDir>
#include <qwaitcondition.h>

#include <vtkCommand.h>
#include <vtkPolyData.h>
#include <vtkWindowToImageFilter.h>
#include <vtkRenderer.h>
#include <vtkRendererCollection.h>
#include <vtkRenderWindow.h>
#include <vtkRenderWindowInteractor.h>
#include <vtkPNGWriter.h>
#include <vtkPostScriptWriter.h>
#include <vtkActor2DCollection.h>
#include <vtkTextActor.h>
#include <vtkCamera.h>
#include <vtkTextProperty.h>
#include <vtkSphereSource.h>
#include <vtkMapper2D.h>
#include <vtkMapper.h>
#include <vtkSmartPointer.h>
#include <vtkPolyDataMapper.h>
#include <vtkLabeledDataMapper.h>




#include "geometry/FacilityGeometry.h"
#include "geometry/Point.h"

#include "Pedestrian.h"
#include "Frame.h"
#include "FrameElement.h"
#include "TrajectoryPoint.h"
#include "SyncData.h"
#include "SystemSettings.h"
#include "TrailPlotter.h"
#include "geometry/PointPlotter.h"
#include "TimerCallback.h"

#define VTK_CREATE(type, name) \
    vtkSmartPointer<type> name = vtkSmartPointer<type>::New()

using namespace std;

TimerCallback* TimerCallback::New()
{
    TimerCallback *cb = new TimerCallback;
    cb->RenderTimerId = 0;
    cb->windowToImageFilter=NULL;
    cb->runningTime=NULL;
    return cb;
}


void TimerCallback::Execute(vtkObject *caller, unsigned long eventId,
                            void *callData){
    if (vtkCommand::TimerEvent == eventId)
    {
        int  frameNumber=0;
        int nPeds=0;
        static bool isRecording =false;
        int tid = * static_cast<int *>(callData);

        if (tid == this->RenderTimerId)
        {
            //dont update anything if the system is actually paused
            //if(extern_is_pause) return;

            vtkRenderWindowInteractor *iren = vtkRenderWindowInteractor::SafeDownCast(caller);
            vtkRenderWindow  *renderWindow = iren->GetRenderWindow();
            vtkRenderer *renderer =renderWindow->GetRenderers()->GetFirstRenderer();

            if (iren && renderWindow && renderer)
            {

                //first pedestrian group
                if(extern_first_dataset_loaded) {
                    Frame * frame=NULL;

                    // return the same frame if the system is paused
                    // in fact you could just return, but in this case no update will be made
                    // e.g showing captions/trails...

                    if(extern_is_pause)
                        frame=extern_trajectories_firstSet.getFrame(extern_trajectories_firstSet.getFrameCursor());
                    else
                        frame = extern_trajectories_firstSet.getNextFrame();

                    if(frame==NULL)
                    {

                    }
                    else
                    {
                        frameNumber=extern_trajectories_firstSet.getFrameCursor();
                        nPeds= frame->getSize();

                        if(SystemSettings::get2D()==true)
                        {
                            vtkPolyData* pData=frame->GetPolyData2D();
#if VTK_MAJOR_VERSION <= 5
                            extern_glyphs_pedestrians->SetInput(pData);
                            ((vtkLabeledDataMapper*)extern_pedestrians_labels->GetMapper())->SetInput(pData);
#else
                            extern_glyphs_pedestrians->SetInputData(pData);
                            extern_pedestrians_labels->GetMapper()->SetInputDataObject(pData);
#endif
                            extern_glyphs_pedestrians->Update();
                        }
                        else
                        {
                            vtkPolyData* pData=frame->GetPolyData3D();
#if VTK_MAJOR_VERSION <= 5
                            extern_glyphs_pedestrians_3D->SetInput(pData);
                            ((vtkLabeledDataMapper*)extern_pedestrians_labels->GetMapper())->SetInput(pData);
#else
                            extern_glyphs_pedestrians_3D->SetInputData(pData);
                            extern_pedestrians_labels->GetMapper()->SetInputDataObject(pData);
#endif
                            extern_glyphs_pedestrians_3D->Update();
                        }

                        if(extern_tracking_enable)
                        {
                            const std::vector<FrameElement *> &elements=frame->GetFrameElements();

                            for(unsigned int i=0;i<elements.size();i++)
                            {
                                FrameElement* el = elements[i];
                                double pos[3];
                                double color;
                                el->GetPos(pos);
                                el->GetColor(&color);
                                extern_trail_plotter->PlotPoint(pos,color);
                            }
                        }
                    }
                }


                int* winSize=renderWindow->GetSize();
                static int  lastWinX=winSize[0]+1; // +1 to trigger a first change
                static int lastWinY=winSize[1];

                sprintf(runningTimeText,"Pedestrians: %d      Time: %ld Sec",nPeds,frameNumber*iren->GetTimerDuration(tid)/1000);
                runningTime->SetInput(runningTimeText);
                runningTime->Modified();

                if((lastWinX!=winSize[0]) || (lastWinY!=winSize[1]) || (frameNumber<10))
                {
                    static std::string winBaseName(renderWindow->GetWindowName());
                    std::string winName=winBaseName;
                    std::string s;
                    winName.append(" [ ");
                    s=QString::number(winSize[0]).toStdString();
                    winName.append(s);
                    winName.append("X");
                    s=QString::number(winSize[1]).toStdString();
                    winName.append(s);
                    winName.append(" ] ");

                    int posY=winSize[1]*(1.0-30.0/536.0);
                    int posX=winSize[0]*(1.0-450.0/720.0);
                    runningTime->SetPosition(posX,posY);
                    renderWindow->SetWindowName(winName.c_str());

                    lastWinX=winSize[0];
                    lastWinY=winSize[1];
                }

                iren->Render();

                if(extern_force_system_update){
                    updateSettings(renderWindow);
                }
                if(extern_take_screenshot){
                    takeScreenshot(renderWindow);
                }
                if(SystemSettings::getRecordPNGsequence()){
                    takeScreenshotSequence(renderWindow);
                }

                if (frameNumber!=0) {
                    int desiredfps=1000.0/iren->GetTimerDuration(tid);
                    int effectivefps=1/(renderer->GetLastRenderTimeInSeconds());

                    effectivefps = (effectivefps>desiredfps)?desiredfps:effectivefps;

                    emit signalFrameNumber(frameNumber);
                    emit signalRunningTime(frameNumber*iren->GetTimerDuration(tid));
                    emit signalRenderingTime(effectivefps);
                }

#ifdef TRAVISTO_FFMPEG

                if(extern_launch_recording){
                    extern_launch_recording=false; //reset

                    windowToImageFilter=vtkWindowToImageFilter::New();
#ifdef WIN32
                    pAVIWriter=vtkAVIWriter::New();
#endif

#ifdef __linux__
                    pAVIWriter=vtkFFMPEGWriter::New();
#endif

                    pAVIWriter->SetQuality(2);
                    pAVIWriter->SetRate(1000.0/iren->GetTimerDuration(tid));

                    QString videoName;
                    SystemSettings::getOutputDirectory(videoName);
                    //create directory if not exits
                    if(!QDir(videoName).exists()){
                        QDir dir;
                        if(!dir.mkpath (videoName )){
                            cerr<<"could not create directory: "<< videoName.toStdString();
                            videoName=""; // current
                        }
                    }

                    videoName += "/tvtvid_"+QDateTime::currentDateTime().toString("yyMMdd_hh_mm_").append(SystemSettings::getFilenamePrefix()).append(".avi");

                    pAVIWriter->SetFileName(videoName.toStdString().c_str());

                    if(windowToImageFilter!=NULL)
                        if(windowToImageFilter->GetInput()==NULL){ //should be the case by first call
                            windowToImageFilter->SetInput(renderWindow);
#if VTK_MAJOR_VERSION <= 5
                            pAVIWriter->SetInput(windowToImageFilter->GetOutput());
#else
                            pAVIWriter->SetInputConnection(windowToImageFilter->GetOutputPort());
#endif
                            pAVIWriter->Start();
                        }
                    extern_recording_enable=true;
                    isRecording=true;
                }

                if(isRecording){
                    windowToImageFilter->Modified();
                    // only write when not paused
                    if(!extern_is_pause) pAVIWriter->Write();

                    if(extern_recording_enable==false){ //stop the recording
                        pAVIWriter->End();
                        windowToImageFilter->Delete();
                        pAVIWriter->Delete();
                        isRecording=false;
                    }
                }
#endif //TRAVISTO_FFMPEG

                if(extern_shutdown_visual_thread){
                    emit signalFrameNumber(0);

                    // this will force an update of the windows
                    lastWinX=0;
                    lastWinY=0;
                    //exit if and only if the recording process is terminated
                    if(isRecording) extern_recording_enable=false;
                    else iren->ExitCallback();
                }
            }
        }
    }
}

void TimerCallback::updateSettings(vtkRenderWindow* renderWindow) {

    static bool fullscreen=false;

    extern_glyphs_pedestrians_actor_2D->SetVisibility(SystemSettings::getShowAgents()&& SystemSettings::get2D());
    extern_glyphs_pedestrians_actor_3D->SetVisibility(SystemSettings::getShowAgents()&& !SystemSettings::get2D());
    extern_trail_plotter->SetVisibility(extern_tracking_enable);

    //agents captions
    extern_pedestrians_labels->SetVisibility(SystemSettings::getShowAgentsCaptions());

    //geometry captions

    //enable / disable full screen
    if(fullscreen!=extern_fullscreen_enable){
        renderWindow->SetFullScreen(extern_fullscreen_enable);
        //renderWindow->GetRenderers()->GetFirstRenderer()->ResetCamera();
        fullscreen=extern_fullscreen_enable;
    }

    // take
    extern_force_system_update=false;
}


void TimerCallback::getTrail(int datasetID, int frameNumber){

    int trailCount=0;
    int trailType=0;
    int trailForm=0;
    int tcMin=0;
    int tcMax=0;

    SystemSettings::getTrailsInfo(&trailCount,&trailType,&trailForm);

    switch(trailType){
    case 0://backward
        tcMin=frameNumber-trailCount;
        tcMax=frameNumber;
        break;

    case 1://symetric
        tcMin=frameNumber-trailCount/2;
        tcMax=frameNumber+trailCount/2;

        break;

    case 2://forward
        tcMin=frameNumber;
        tcMax=frameNumber+trailCount;
        break;

    }


    for (int i=tcMin;i<tcMax;i++){
        Frame* frame = extern_trajectories_firstSet.getFrame(i);
        if(frame==NULL){
            //		cerr<<"Trajectory not available in getTrail(), first data set"<<endl;
        }else {
            FrameElement* point=NULL;
            while(NULL!=(point=frame->getNextElement())){
                //extern_pedestrians_firstSet[point->getIndex()]->plotTrail(point->getX(),point->getY(),point->getZ());
                //extern_pedestrians_firstSet[point->getIndex()]->setTrailGeometry(trailForm);
            }
            frame->resetCursor();
        }
    }
}


void TimerCallback::takeScreenshot(vtkRenderWindow *renderWindow){
    static int imageID=0;
    vtkWindowToImageFilter * winToImFilter  = vtkWindowToImageFilter::New();
    winToImFilter->SetInput( renderWindow );
    //winToImFilter->SetMagnification(4);
    //renderWindow->Delete();
    //vtkPostScriptWriter * image  = vtkPostScriptWriter::New();
    vtkPNGWriter * image  = vtkPNGWriter::New();
    image->SetInputConnection(winToImFilter->GetOutputPort());
    winToImFilter->Delete();

    QString screenshots;
    SystemSettings::getOutputDirectory(screenshots);
    //create directory if not exits
    if(!QDir(screenshots).exists()){
        QDir dir;
        if(!dir.mkpath (screenshots )){
            //Debug::Error("could not create directory: %s",screenshots.toStdString().c_str());
            //try with the current directory
            screenshots="";
        }
    }


    char filename[256]={0};
    //	sprintf(filename,"travisto_video_%d.png",imageID++);
    std::string date= QString(QDateTime::currentDateTime().toString("yyMMdd_hh")).toStdString();

    sprintf(filename,"travisto_snap_%sh_%d.png",date.c_str(),imageID++);

    //append the prefix
    screenshots+=SystemSettings::getFilenamePrefix();
    screenshots+=QString(filename);
    image->SetFileName(screenshots.toStdString().c_str());
    winToImFilter->Modified();

    image->Write ();
    image->Delete();
    extern_take_screenshot=false;
}

/// take png screenshot sequence
void TimerCallback::takeScreenshotSequence(vtkRenderWindow* renderWindow){
    static int imageID=0;
    vtkWindowToImageFilter * winToImFilter  = vtkWindowToImageFilter::New();
    winToImFilter->SetInput( renderWindow );
    //renderWindow->Delete();
    vtkPNGWriter * image  = vtkPNGWriter::New();
    //vtkPostScriptWriter * image  = vtkPostScriptWriter::New();
    image->SetInputConnection( winToImFilter->GetOutputPort());
    winToImFilter->Delete();

    QString screenshots;
    SystemSettings::getOutputDirectory(screenshots);

    screenshots.append("./png_seq_"+QDateTime::currentDateTime().toString("yyMMddhh")+"_"+SystemSettings::getFilenamePrefix());
    screenshots.truncate(screenshots.size()-1);

    //create directory if not exits
    if(!QDir(screenshots).exists()){
        QDir dir;
        if(!dir.mkpath (screenshots )){
            cerr<<"could not create directory: "<< screenshots.toStdString();
            //try with the current directory
            screenshots="./png_seq_"+QDateTime::currentDateTime().toString("yyMMdd")+"_"+SystemSettings::getFilenamePrefix();
            screenshots.truncate(screenshots.size()-1);
        }
    }


    char filename[30]={0};
    sprintf(filename,"/tmp_%07d.png",imageID++);
    screenshots.append(filename);
    image->SetFileName(screenshots.toStdString().c_str());
    winToImFilter->Modified();

    image->Write ();
    image->Delete();
}


void TimerCallback::SetRenderTimerId(int tid)
{
    this->RenderTimerId = tid;
}

void TimerCallback::setTextActor(vtkTextActor* ra){
    runningTime=ra;
}