// MediaInfo_MPEG-TS_TimeStamps_Errors - Timestamps for MPEG-TS files, based on MediaInfo
// Copyright (C) 2009-2009 MediaArea.net, info@MediaArea.net
//
// This library is free software: you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// This library 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 Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with this library. If not, see <http://www.gnu.org/licenses/>.
//
//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
//
// This utility displays PCR continuity errors.
// It can display all the PCR of a file with -f
// Compilation:
// g++ MediaInfo_MPEG-TS_TimeStamps_Errors.cpp -o MediaInfo_MPEG-TS_TimeStamps_Errors -lmediainfo -lzen -lz
//
//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

//---------------------------------------------------------------------------
#include "ZenLib/Ztring.h"
#include "ZenLib/File.h"
#include "vector"
#include "iostream"
#include "MediaInfoDLL/MediaInfoDLL.h"
#include "MediaInfo/MediaInfo_Events.h"
using namespace MediaInfoDLL;
using namespace ZenLib;
using namespace std;
//---------------------------------------------------------------------------

//---------------------------------------------------------------------------
struct info
{
    File    TimeStampFile;
    int64u  Value_First;
    float64 Value_Last;
    float64 Value_Current;
    int64u  Offset_Last;
    int64u  Offset_Current;
    bool    IsNormal;

    info()
    {
        Value_First=(int64u)-1;
        Value_Last=(int64u)-1;
        Value_Current=(int64u)-1;
        Offset_Last=(int64u)-1;
        Offset_Current=(int64u)-1;
        IsNormal=false;
    }
};
typedef std::vector<info*> infos;

struct callback_data
{
    bool Full;
    bool Cout;
    Ztring FileName;
    infos Infos;
};
//---------------------------------------------------------------------------

/***************************************************************************/
/* The callback function                                                   */
/***************************************************************************/

void __stdcall Event_CallBackFunction(unsigned char* Data_Content, size_t Data_Size, void* UserHandler_Void)
{
    /*Retrieving UserHandler*/
    callback_data                      &CallBack=*((callback_data*)UserHandler_Void);
    struct MediaInfo_Event_Generic*     Event_Generic=(struct MediaInfo_Event_Generic*) Data_Content;
    unsigned char                       ParserID;
    unsigned short                      EventID;
    unsigned char                       EventVersion;

    /*integrity tests*/
    if (Data_Size<4)
        return; //There is a problem

    /*Do what you need with the event, here is only an example*/
    /*Retrieving EventID*/
    ParserID    =(unsigned char) ((Event_Generic->EventCode&0xFF000000)>>24);
    EventID     =(unsigned short)((Event_Generic->EventCode&0x00FFFF00)>>8 );
    EventVersion=(unsigned char) ( Event_Generic->EventCode&0x000000FF     );
    switch (ParserID)
    {
        case MediaInfo_Parser_MpegPs :    
                switch (EventID)
                {    
                    case MediaInfo_Event_MpegPs_PES_IFrame_NoTimeStamps                         : break;
                    default                                                                     : return;
                }
                break;
        default : return; //ParserID is unknown
    }

    MediaInfo_Event_MpegPs_PES_IFrame_NoTimeStamps_0 &Data=*(struct MediaInfo_Event_MpegPs_PES_IFrame_NoTimeStamps_0*)Data_Content;
    if (Data.PTS==(int64u)-1)
        return;

    if (CallBack.Infos[Data.PID]==NULL)
    {
        //Creating pointer
        CallBack.Infos[Data.PID]=new info;

        //Saving Delay
        CallBack.Infos[Data.PID]->Value_First=Data.PTS;
        CallBack.Infos[Data.PID]->Value_Current=Data.PTS;

        //Creating the file, depends of the count of programs
        CallBack.Infos[Data.PID]->TimeStampFile.Create(CallBack.FileName+_T(".")+Ztring::ToZtring(Data.PID)+_T(".TimeStampFile.csv"), true);

        //File header
        Ztring Header=Ztring(_T("FileOffset (hexadecimal),FileOffset (decimal),AbsoluteDuration (27 MHz),AbsoluteDuration (ms),AbsoluteDuration (ISO 8601),RelativeDuration (27 MHz),RelativeDuration (ms),RelativeDuration (ISO 6801)"))+ZenLib::EOL;
        if (CallBack.Cout)
            wcout<<Header;
        else
            CallBack.Infos[Data.PID]->TimeStampFile.Write(Header);
    }


    //Saving data from the chunk before
    CallBack.Infos[Data.PID]->Value_Last=CallBack.Infos[Data.PID]->Value_Current;
    CallBack.Infos[Data.PID]->Offset_Last=CallBack.Infos[Data.PID]->Offset_Current;

    //Retrieving the current data
    CallBack.Infos[Data.PID]->Value_Current=Data.PTS;
    CallBack.Infos[Data.PID]->Offset_Current=Data.Stream_Offset;

    //Displaying the offset before if there is a gap
    Ztring Text;
    if (!CallBack.Full && CallBack.Infos[Data.PID]->Offset_Last!=(int64u)-1 && CallBack.Infos[Data.PID]->IsNormal && (CallBack.Infos[Data.PID]->Value_Last+700000000<CallBack.Infos[Data.PID]->Value_Current || CallBack.Infos[Data.PID]->Value_Last>CallBack.Infos[Data.PID]->Value_Current)) //Must be less than 0.7s
    {
        Text+=Ztring::ToZtring(CallBack.Infos[Data.PID]->Offset_Last, 16)+_T(",")
              +Ztring::ToZtring(CallBack.Infos[Data.PID]->Offset_Last)+_T(",")
              +Ztring::ToZtring(float64_int64s(((float64)CallBack.Infos[Data.PID]->Value_Last)*27/1000))+_T(",")
              +Ztring::ToZtring(CallBack.Infos[Data.PID]->Value_Last)+_T(",")
              +Ztring().Duration_From_Milliseconds(float64_int64s(((float64)CallBack.Infos[Data.PID]->Value_Last)/1000000))+_T(",")
              //+Ztring::ToZtring(CallBack.Infos[Data.PID]->Duration_Before, 0)+_T(",")
              //+Ztring::ToZtring(CallBack.Infos[Data.PID]->Duration_Before/27000, 6)+_T(",")
              //+Ztring().Duration_From_Milliseconds(float64_int64s(CallBack.Infos[Data.PID]->Duration_Before/27000))
              +ZenLib::EOL;
    }
    //Displaying the current offset
    if (CallBack.Full)// || CallBack.Infos[Data.PID]->From_Position_Before==(int64u)-1 || (CallBack.Infos[Data.PID]->Duration_Before+700*27000<CallBack.Infos[Data.PID]->Duration_Current || CallBack.Infos[Data.PID]->Duration_Before>CallBack.Infos[Data.PID]->Duration_Current)) //Must be less than 0.7s
    {
        Text+=Ztring::ToZtring(CallBack.Infos[Data.PID]->Offset_Current, 16)+_T(",")
              +Ztring::ToZtring(CallBack.Infos[Data.PID]->Offset_Current)+_T(",")
              +Ztring::ToZtring(float64_int64s(((float64)CallBack.Infos[Data.PID]->Value_Current)*90/1000000))+_T(",")
              +Ztring::ToZtring(((float64)CallBack.Infos[Data.PID]->Value_Current)/1000000, 6)+_T(",")
              +Ztring().Duration_From_Milliseconds(float64_int64s(((float64)CallBack.Infos[Data.PID]->Value_Current)/1000000))+_T(",")
              +Ztring::ToZtring(float64_int64s(((float64)(CallBack.Infos[Data.PID]->Value_Current-CallBack.Infos[Data.PID]->Value_First))*90/1000000))+_T(",")
              +Ztring::ToZtring(((float64)(CallBack.Infos[Data.PID]->Value_Current-CallBack.Infos[Data.PID]->Value_First))/1000000, 6)+_T(",")
              +Ztring().Duration_From_Milliseconds(float64_int64s(((float64)(CallBack.Infos[Data.PID]->Value_Current-CallBack.Infos[Data.PID]->Value_First))/1000000))+_T(",")
              +Ztring::ToZtring(float64_int64s(((float64)(CallBack.Infos[Data.PID]->Value_Current-CallBack.Infos[Data.PID]->Value_Last))*90/1000000))+_T(",")
              +Ztring::ToZtring(((float64)(CallBack.Infos[Data.PID]->Value_Current-CallBack.Infos[Data.PID]->Value_Last))/1000000, 6)+_T(",")
              +Ztring().Duration_From_Milliseconds(float64_int64s(((float64)(CallBack.Infos[Data.PID]->Value_Current-CallBack.Infos[Data.PID]->Value_Last))/1000000))+_T(",")
              +ZenLib::EOL;

/*
        Data+=Ztring::ToZtring(CallBack.Infos[Data.PID]->From_Position_Current, 16)+_T(",")
              +Ztring::ToZtring(CallBack.Infos[Data.PID]->From_Position_Current)+_T(",")
              +Ztring::ToZtring(Duration_Absolute_Current)+_T(",")
              +Ztring::ToZtring(((float64)Duration_Absolute_Current)/27000, 6)+_T(",")
              +Ztring().Duration_From_Milliseconds(float64_int64s(((float64)Duration_Absolute_Current)/27000))+_T(",")
              +Ztring::ToZtring(CallBack.Infos[Data.PID]->Duration_Current, 0)+_T(",")
              +MI.Get(StreamKind, Pos, _T("Duration"))+_T(",")
              +MI.Get(StreamKind, Pos, _T("Duration/String3"))
              +ZenLib::EOL;
*/
        CallBack.Infos[Data.PID]->IsNormal=false;
    }

    //Displaying "Is normal" if useful
    else if (!CallBack.Full && !CallBack.Infos[Data.PID]->IsNormal)
    {
        Text+=Ztring(_T("(Is normal),(Is normal),(Is normal),(Is normal),(Is normal),(Is normal),(Is normal),(Is normal)"))+ZenLib::EOL;
        CallBack.Infos[Data.PID]->IsNormal=true;
    }

    //Output
    if (!Text.empty())
    {
        if (CallBack.Cout)
            wcout<<Text;
        else
            CallBack.Infos[Data.PID]->TimeStampFile.Write(Text);
    }

    cout<<Data.PTS_HR<<std::endl;
}

//***************************************************************************
// Main entry
//***************************************************************************

int main(int argc, char* argv[])
{
    //Input
    callback_data CallBack_Data;
    CallBack_Data.Full=false;
    CallBack_Data.Cout=false;
    for (int Pos=1; Pos<argc; Pos++)
    {
        if (string(argv[Pos])=="-f" || string(argv[Pos])=="--full")
            CallBack_Data.Full=true;
        else if (string(argv[Pos])=="-o" || string(argv[Pos])=="--cout")
            CallBack_Data.Cout=true;
        else if (string(argv[Pos])=="-h" || string(argv[Pos])=="--help")
            ; //Help
        else if (string(argv[Pos])=="-v" || string(argv[Pos])=="--version")
            ; //Version
        else if (!CallBack_Data.FileName.empty())
        {
            cout<<"Only 1 file per call"<<endl;
            return 1;
        }
        else
            CallBack_Data.FileName.From_UTF8(argv[Pos]);
    }

    if (CallBack_Data.FileName.empty())
    {
        cout<<"MediaInfo_MPEG-TS_TimeStamps_Errors v.20091228"<<endl;
        cout<<endl;
        cout<<"Options:"<<endl;
        cout<<"-h, --help           : display this help message"<<endl;
        cout<<"-v, --version        : display this help message"<<endl;
        cout<<"-f, --full           : display all timestamps"<<endl;
        cout<<"-o, --cout           : display to standard output instead of file"<<endl;
        cout<<endl;
        return 2;
    }

    //Initiating
    CallBack_Data.Infos.resize(0x2000);
    stream_t StreamKind=Stream_General; //General or Menu, depends of the count of programs in the TS file

    //Initializing MediaInfo
    MediaInfo MI;

    MI.Option(_T("File_Event_CallBackFunction"), _T("CallBack=memory://")+Ztring::ToZtring((size_t)Event_CallBackFunction)+_T(";UserHandler=memory://")+Ztring::ToZtring((size_t)&CallBack_Data));
    MI.Option(_T("ParseSpeed"), _T("1"));
    
    MI.Open(CallBack_Data.FileName);

    return 0;

    /*
    //From: preparing for reading
    ZenLib::File From; From.Open(CallBack_Data.FileName, ZenLib::File::Access_Read);
    ZenLib::int8u* From_Buffer=new ZenLib::int8u[188]; //Note: you can do your own buffer
    size_t From_Buffer_Size; //The size of the read file buffer

    //Preparing to fill MediaInfo with a buffer
    MI.Option(_T("File_ForceParser"), _T("MpegTs")); //We forcing the parser for speed improvement
    MI.Option(_T("UserBits"), _T("Duration")); //Not useful now because Duration bit is set in all cases, but for future
    MI.Open_Buffer_Init(From.Size_Get());
    bool CanWrite_OnlyIfParsingIsOk=false;

    //The parsing loop
    do
    {
        //Reading data
        From_Buffer_Size=From.Read(From_Buffer, 188);

        //Sending the buffer to MediaInfo
        size_t Result=MI.Open_Buffer_Continue(From_Buffer, From_Buffer_Size);

        //Managing result (if streams are detected)
        if (CanWrite_OnlyIfParsingIsOk && Result&(1<<16)) //Bit 16 is temporary bit for Duration modification in MPEG-TS (first UserBit)
        {
            for(size_t Pos=0; Pos<Infos.size(); Pos++)
            {
                const Ztring &Duration=MI.Get(StreamKind, Pos, _T("Duration"));
                if (!Duration.empty())
                {
                    //Saving data from the chunk before
                    Infos[Pos]->Duration_Before=Infos[Pos]->Duration_Current;
                    Infos[Pos]->From_Position_Before=Infos[Pos]->From_Position_Current;
                    int64u Duration_Absolute_Before=Infos[Pos]->Delay+float64_int64s(Infos[Pos]->Duration_Before);

                    //Retrieving the current data
                    Infos[Pos]->Duration_Current=Ztring(Duration).To_float64()*27000;
                    int64u Duration_Absolute_Current=Infos[Pos]->Delay+float64_int64s(Infos[Pos]->Duration_Current);
                    Infos[Pos]->From_Position_Current=From.Position_Get()-188;

                    //Displaying the offset before if there is a gap
                    Ztring Data;
                    if (!CallBack_Data.Full && Infos[Pos]->From_Position_Before!=(int64u)-1 && Infos[Pos]->IsNormal && (Infos[Pos]->Duration_Before+700*27000<Infos[Pos]->Duration_Current || Infos[Pos]->Duration_Before>Infos[Pos]->Duration_Current)) //Must be less than 0.7s
                    {
                        Data+=Ztring::ToZtring(Infos[Pos]->From_Position_Before, 16)+_T(",")
                              +Ztring::ToZtring(Infos[Pos]->From_Position_Before)+_T(",")
                              +Ztring::ToZtring(Duration_Absolute_Before)+_T(",")
                              +Ztring::ToZtring(((float64)Duration_Absolute_Before)/27000, 6)+_T(",")
                              +Ztring().Duration_From_Milliseconds(float64_int64s(((float64)Duration_Absolute_Before)/27000))+_T(",")
                              +Ztring::ToZtring(Infos[Pos]->Duration_Before, 0)+_T(",")
                              +Ztring::ToZtring(Infos[Pos]->Duration_Before/27000, 6)+_T(",")
                              +Ztring().Duration_From_Milliseconds(float64_int64s(Infos[Pos]->Duration_Before/27000))
                              +ZenLib::EOL;
                    }
                    //Displaying the current offset
                    if (CallBack_Data.Full || Infos[Pos]->From_Position_Before==(int64u)-1 || (Infos[Pos]->Duration_Before+700*27000<Infos[Pos]->Duration_Current || Infos[Pos]->Duration_Before>Infos[Pos]->Duration_Current)) //Must be less than 0.7s
                    {
                        Data+=Ztring::ToZtring(Infos[Pos]->From_Position_Current, 16)+_T(",")
                              +Ztring::ToZtring(Infos[Pos]->From_Position_Current)+_T(",")
                              +Ztring::ToZtring(Duration_Absolute_Current)+_T(",")
                              +Ztring::ToZtring(((float64)Duration_Absolute_Current)/27000, 6)+_T(",")
                              +Ztring().Duration_From_Milliseconds(float64_int64s(((float64)Duration_Absolute_Current)/27000))+_T(",")
                              +Ztring::ToZtring(Infos[Pos]->Duration_Current, 0)+_T(",")
                              +MI.Get(StreamKind, Pos, _T("Duration"))+_T(",")
                              +MI.Get(StreamKind, Pos, _T("Duration/String3"))
                              +ZenLib::EOL;
                        Infos[Pos]->IsNormal=false;
                    }

                    //Displaying "Is normal" if useful
                    else if (!CallBack_Data.Full && !Infos[Pos]->IsNormal)
                    {
                        Data+=Ztring(_T("(Is normal),(Is normal),(Is normal),(Is normal),(Is normal),(Is normal),(Is normal),(Is normal)"))+ZenLib::EOL;
                        Infos[Pos]->IsNormal=true;
                    }

                    //Output
                    if (!Data.empty())
                    {
                        if (CallBack_Data.Cout)
                            wcout<<Data;
                        else
                            Infos[Pos]->TimeStampFile.Write(Data);
                    }
                }
            }
        }

        //Configuring the library after the detection
        if (!CanWrite_OnlyIfParsingIsOk && Result&0xFE)
        {
            if (MI.Get(Stream_General, 0, _T("Format"))!=_T("MPEG-TS"))
            {
                wcout<<CallBack_Data.FileName<<_T(" is not a MPEG-TS file")<<endl;
                return 2;
            }
            if (CallBack_Data.Cout && MI.Count_Get(Stream_Menu)>2)
            {
                cout<<"Outputing to cout is not possible with multiple programs in a MPEG-TS, option disabled"<<endl;
                CallBack_Data.Cout=false;
            }

            //Stream is detected
            CanWrite_OnlyIfParsingIsOk=true;

            //Detecting the count of programs
            if (MI.Count_Get(Stream_Menu))
                StreamKind=Stream_Menu;
            else
                StreamKind=Stream_General;
            Infos.resize(MI.Count_Get(StreamKind));

            for(size_t Pos=0; Pos<MI.Count_Get(StreamKind); Pos++)
            {
                //Creating pointer
                Infos[Pos]=new info;

                //Saving Delay
                Infos[Pos]->Delay=float64_int64s(Ztring(MI.Get(StreamKind, Pos, _T("Delay"))).To_float64()*27000);

                //Creating the file, depends of the count of programs
                Infos[Pos]->TimeStampFile.Create(CallBack_Data.FileName+(StreamKind==Stream_Menu?(_T(".")+MI.Get(Stream_Menu, Pos, _T("MenuID"))):Ztring())+_T(".TimeStampFile.csv"), true);

                //File header
                Ztring Data=Ztring(_T("FileOffset (hexadecimal),FileOffset (decimal),AbsoluteDuration (27 MHz),AbsoluteDuration (ms),AbsoluteDuration (ISO 8601),RelativeDuration (27 MHz),RelativeDuration (ms),RelativeDuration (ISO 6801)"))+ZenLib::EOL;
                if (CallBack_Data.Cout)
                    wcout<<Data;
                else
                    Infos[Pos]->TimeStampFile.Write(Data);
            }

            //Re-opening the file: we want data from the begining
            From.Close();
            From.Open(CallBack_Data.FileName, ZenLib::File::Access_Read);
            MI.Open_Buffer_Init(MI.Open_Buffer_Continue_GoTo_Get()+From.Size_Get(), MI.Open_Buffer_Continue_GoTo_Get());
            MI.Option(_T("File_IsSeekable"), _T("0"));  //We say we want all now
            //MI.Option(_T("ParseSpeed"), _T("1"));
        }
    }
    while (From_Buffer_Size>0);
    for(size_t Pos=0; Pos<Infos.size(); Pos++)
    {
        Ztring Data;
        if (!CallBack_Data.Full && Infos[Pos]->IsNormal)
        {
            int64u Duration_Absolute_Current=Infos[Pos]->Delay+float64_int64s(Infos[Pos]->Duration_Current);
            Data=Ztring::ToZtring(Infos[Pos]->From_Position_Current, 16)+_T(",")
                +Ztring::ToZtring(Infos[Pos]->From_Position_Current)+_T(",")
                +Ztring::ToZtring(Duration_Absolute_Current)+_T(",")
                +Ztring::ToZtring(((float64)Duration_Absolute_Current)/27000, 6)+_T(",")
                +Ztring().Duration_From_Milliseconds(float64_int64s(((float64)Duration_Absolute_Current)/27000))+_T(",")
                +Ztring::ToZtring(Infos[Pos]->Duration_Current, 0)+_T(",")
                +MI.Get(StreamKind, Pos, _T("Duration"))+_T(",")
                +MI.Get(StreamKind, Pos, _T("Duration/String3"))
                +ZenLib::EOL;
        }

        if (CallBack_Data.Cout)
            wcout<<Data;
        else
        {
            Infos[Pos]->TimeStampFile.Write(Data);

            //User information
            if (StreamKind==Stream_Menu)
                wcout<<_T("Data for menu ")<<MI.Get(Stream_Menu, Pos, _T("MenuID"))<<_T(" is in ")<<CallBack_Data.FileName+(StreamKind==Stream_Menu?(_T(".")+MI.Get(Stream_Menu, Pos, _T("MenuID"))):Ztring())+_T(".TimeStampFile.csv");
            else
                wcout<<_T("Data is in ")<<CallBack_Data.FileName+(StreamKind==Stream_Menu?(_T(".")+MI.Get(Stream_Menu, Pos, _T("MenuID"))):Ztring())+_T(".TimeStampFile.csv")<<endl;
        }
    }

    //Closing all
    for (size_t Pos=0; Pos<Infos.size(); Pos++)
        delete Infos[Pos]; //Infos[Pos]=NULL;
    */

    return 0;
}
