/*  Copyright (c) MediaArea.net SARL. All Rights Reserved.
 *
 *  Use of this source code is governed by a BSD-style license that can
 *  be found in the License.html file in the root of the source tree.
 */

//---------------------------------------------------------------------------
// Pre-compilation
#include "MediaInfo/PreComp.h"
#ifdef __BORLANDC__
    #pragma hdrstop
#endif
//---------------------------------------------------------------------------

//---------------------------------------------------------------------------
#include "MediaInfo/Setup.h"
//---------------------------------------------------------------------------

//---------------------------------------------------------------------------
#include "MediaInfo/TimeCode.h"
//---------------------------------------------------------------------------

//---------------------------------------------------------------------------
using namespace std;
//---------------------------------------------------------------------------

namespace MediaInfoLib
{

//***************************************************************************
// Constructor/Destructor
//***************************************************************************

//---------------------------------------------------------------------------
TimeCode::TimeCode ()
:   Hours((int8u)-1),
    Minutes((int8u)-1),
    Seconds((int8u)-1),
    Frames((int8u)-1),
    MoreSamples(0),
    MoreSamples_Frequency(0),
    FramesPerSecond_Is1001(false),
    FramesPerSecond(0),
    DropFrame(false),
    MustUseSecondField(false),
    IsSecondField(false),
    IsNegative(false)
{
}

//---------------------------------------------------------------------------
TimeCode::TimeCode (int8u Hours_, int8u Minutes_, int8u Seconds_, int8u Frames_, int8u FramesPerSecond_, bool DropFrame_, bool MustUseSecondField_, bool IsSecondField_)
:   Hours(Hours_),
    Minutes(Minutes_),
    Seconds(Seconds_),
    Frames(Frames_),
    MoreSamples(0),
    MoreSamples_Frequency(0),
    FramesPerSecond_Is1001(false),
    FramesPerSecond(FramesPerSecond_),
    DropFrame(DropFrame_),
    MustUseSecondField(MustUseSecondField_),
    IsSecondField(IsSecondField_),
    IsNegative(false)
{
}

//---------------------------------------------------------------------------
TimeCode::TimeCode (int64s Frames_, int8u FramesPerSecond_, bool DropFrame_, bool MustUseSecondField_, bool IsSecondField_)
:   FramesPerSecond(FramesPerSecond_),
    MoreSamples(0),
    MoreSamples_Frequency(0),
    FramesPerSecond_Is1001(false),
    DropFrame(DropFrame_),
    MustUseSecondField(MustUseSecondField_),
    IsSecondField(IsSecondField_)
{
    FromFrames(Frames_);
}

bool TimeCode::FromFrames(int64s Frames_)
{
    if (!FramesPerSecond)
    {
        Frames  = 0;
        Seconds = 0;
        Minutes = 0;
        Hours   = 0;
        IsNegative = false;
        return true;
    }

    if (Frames_<0)
    {
        IsNegative=true;
        Frames_=-Frames_;
    }
    else
        IsNegative=false;

    int8u Dropped=0;
    if (DropFrame)
    {
        Dropped=2;
        if (FramesPerSecond>30)
            Dropped+=2;
        if (FramesPerSecond>60)
            Dropped+=2;
        if (FramesPerSecond>90)
            Dropped+=2;
        if (FramesPerSecond>120)
            Dropped+=2;
    }

    int64u Minutes_Tens = Frames_/(600*FramesPerSecond-Dropped*9); //Count of 10 minutes
    int64u Minutes_Units = (Frames_-Minutes_Tens*(600*FramesPerSecond-Dropped*9))/(60*FramesPerSecond-Dropped);

    Frames_ += 9*Dropped*Minutes_Tens+Dropped*Minutes_Units;
    if (Minutes_Units && ((Frames_/FramesPerSecond)%60)==0 && (Frames_%FramesPerSecond)<Dropped) // If Minutes_Tens is not 0 (drop) but count of remaining seconds is 0 and count of remaining frames is less than 2, 1 additional drop was actually counted, removing it
        Frames_-=Dropped;

    Frames  =    Frames_ % FramesPerSecond;
    Seconds =   (Frames_ / FramesPerSecond) % 60;
    Minutes =  ((Frames_ / FramesPerSecond) / 60) % 60;
    int64s Temp = (((Frames_ / FramesPerSecond) / 60) / 60);
    Hours = (Temp>99 || Temp<-99)?(Temp%24):Temp;

    return false;
}

//---------------------------------------------------------------------------
TimeCode::TimeCode (const char* Value, size_t Length)
:   Hours((int8u)-1),
    Minutes((int8u)-1),
    Seconds((int8u)-1),
    Frames((int8u)-1),
    MoreSamples(0),
    MoreSamples_Frequency(0),
    FramesPerSecond_Is1001(false),
    FramesPerSecond(0),
    DropFrame(false),
    MustUseSecondField(false),
    IsSecondField(false),
    IsNegative(false)
{
    FromString(Value, Length);
}

//***************************************************************************
// Helpers
//***************************************************************************

//---------------------------------------------------------------------------
void TimeCode::PlusOne()
{
    //TODO: negative values

    if (FramesPerSecond==0)
        return;
    if (MustUseSecondField)
    {
        if (IsSecondField)
        {
            Frames++;
            IsSecondField=false;
        }
        else
            IsSecondField=true;
    }
    else
        Frames++;
    if (Frames>=FramesPerSecond)
    {
        Seconds++;
        Frames=0;
        if (Seconds>=60)
        {
            Seconds=0;
            Minutes++;

            if (DropFrame && Minutes%10)
                Frames=2; //frames 0 and 1 are dropped for every minutes except 00 10 20 30 40 50

            if (Minutes>=60)
            {
                Minutes=0;
                Hours++;
                if (Hours>=24)
                {
                    Hours=0;
                }
            }
        }
    }
}

//---------------------------------------------------------------------------
void TimeCode::MinusOne()
{
    //TODO: negative values

    if (FramesPerSecond==0)
        return;
    if (MustUseSecondField && IsSecondField)
        IsSecondField=false;
    else
    {
        if (Frames==0 || (DropFrame && Minutes%10 && Frames<=2))
        {
            Frames=FramesPerSecond;
            if (Seconds==0)
            {
                Seconds=60;
                if (Minutes==0)
                {
                    Minutes=60;
                    if (Hours==0)
                        Hours=24;
                    Hours--;
                }
                Minutes--;
            }
            Seconds--;
        }
        Frames--;

        if (MustUseSecondField)
            IsSecondField=true;
    }
}


//---------------------------------------------------------------------------
static const int32s PowersOf10[]=
{
    10,
    100,
    1000,
    10000,
    100000,
    1000000,
    10000000,
    100000000,
    1000000000,
};
static const int PowersOf10_Size=sizeof(PowersOf10)/sizeof(int32s);

//---------------------------------------------------------------------------
bool TimeCode::FromString(const char* Value, size_t Length)
{
    IsSecondField=false;
    IsNegative=false;

    //hh:mm:ss;ff or hh:mm:ss.zzzzzzzzzSfffffffff formats
    if (Length>7
     && Value[0]>='0' && Value[0]<='9'
     && Value[1]>='0' && Value[1]<='9'
     && Value[2]==':'
     && Value[3]>='0' && Value[3]<='9'
     && Value[4]>='0' && Value[4]<='9'
     && Value[5]==':'
     && Value[6]>='0' && Value[6]<='9'
     && Value[7]>='0' && Value[7]<='9')
    {
        if (Length>8)
        {
            //hh:mm:ss.zzzzzzzzzSfffffffff format
            unsigned char c=(unsigned char)Value[8];
            if (c=='.' || c==',')
            {
                if (Length==9)
                    return true;
                int i=9;
                int32s S=0;
                int TheoriticalMax=i+PowersOf10_Size;
                int MaxLength=Length>TheoriticalMax?TheoriticalMax:Length;
                while (i<MaxLength)
                {
                    c=(unsigned char)Value[i];
                    c-='0';
                    if (c>9)
                        break;
                    S*=10;
                    S+=c;
                    i++;
                }
                if (i==Length)
                    MoreSamples_Frequency=PowersOf10[i-10];
                else
                {
                    c=(unsigned char)Value[i];
                    if (c!='S' && c!='/')
                        return true;
                    i++;
                    TheoriticalMax=i+PowersOf10_Size;
                    MaxLength=Length>TheoriticalMax?TheoriticalMax:Length;
                    int32s Multiplier=0;
                    while (i<Length)
                    {
                        c=(unsigned char)Value[i];
                        c-='0';
                        if (c>9)
                            break;
                        Multiplier*=10;
                        Multiplier+=c;
                    }
                    if (i==MaxLength && i<Length && Value[i]=='0' && Multiplier==100000000)
                    {
                        Multiplier=1000000000;
                        i++;
                    }
                    if (i<Length)
                        return true;
                    MoreSamples_Frequency=Multiplier;
                }
                Frames=(int8u)-1;
                MoreSamples=S;
                DropFrame=false;
            }
            //hh:mm:ss;ff format
            else if (Length==11
             && (Value[8]==':' || Value[8]==';')
             && Value[9]>='0' && Value[9]<='9'
             && Value[10]>='0' && Value[10]<='9')
            {
                DropFrame=Value[8]==';';
                Frames=((Value[9]-'0')*10)+(Value[10]-'0');
                MoreSamples=0;
            }
            else
                return true;
        }
        else
        {
            MoreSamples=0;
        }
        Hours=((Value[0]-'0')*10)+(Value[1]-'0');
        Minutes=((Value[3]-'0')*10)+(Value[4]-'0');
        Seconds=((Value[6]-'0')*10)+(Value[7]-'0');
        return false;
    }
    //X.Xs format
    if (Length>=2
     && Value[Length-1]=='s')
    {
        Length--; //Remove the "s" from the string
        unsigned char c;
        int i=0;
        int32s S=0;
        int TheoriticalMax=i+PowersOf10_Size;
        int MaxLength=Length>TheoriticalMax?TheoriticalMax:Length;
        while (i<MaxLength)
        {
            c=(unsigned char)Value[i];
            c-='0';
            if (c>9)
                break;
            S*=10;
            S+=c;
            i++;
        }
        Hours=S/3600;
        Minutes=(S%3600)/60;
        Seconds=S%60;
        Frames=(int8u)-1;
        c=(unsigned char)Value[i];
        if (c=='.' || c==',')
        {
            i++;
            if (i==Length)
                return true;
            size_t i_Start=i;
            S=0;
            TheoriticalMax=i+PowersOf10_Size;
            MaxLength=Length>TheoriticalMax?TheoriticalMax:Length;
            while (i<MaxLength)
            {
                c=(unsigned char)Value[i];
                c-='0';
                if (c>9)
                    break;
                S*=10;
                S+=c;
                i++;
            }
            if (i!=Length)
                return true;
            MoreSamples_Frequency=PowersOf10[i-1-i_Start];
            MoreSamples=S;
        }
        else
        {
            MoreSamples=0;
            MoreSamples_Frequency=1; // Indicates that it comes from a timestamp
        }
        return false;
    }
    //X.Xf format
    if (Length>=2
     && Value[Length-1]=='f')
    {
        Length--; //Remove the "f" from the string
        unsigned char c;
        int i=0;
        int32s S=0;
        int TheoriticalMax=i+PowersOf10_Size;
        int MaxLength=Length>TheoriticalMax?TheoriticalMax:Length;
        while (i<MaxLength)
        {
            c=(unsigned char)Value[i];
            c-='0';
            if (c>9)
                break;
            S*=10;
            S+=c;
            i++;
        }
        if (i!=Length)
            return true;
        if (!FramesPerSecond)
            FramesPerSecond=1; // Arbitrary choosen
        int32s OneHourInFrames=3600*FramesPerSecond;
        int32s OneMinuteInFrames=60*FramesPerSecond;
        Hours=S/OneHourInFrames;
        Minutes=(S%OneHourInFrames)/OneMinuteInFrames;
        Seconds=(S%OneMinuteInFrames)/FramesPerSecond;
        Frames=S%FramesPerSecond;
        MoreSamples=0;
        return false;
    }
    //Xt format
    if (Length>=2
     && Value[Length-1]=='t')
    {
        Length--; //Remove the "t" from the string
        unsigned char c;
        int i=0;
        int64s S=0;
        int TheoriticalMax=i+PowersOf10_Size;
        int MaxLength=Length>TheoriticalMax?TheoriticalMax:Length;
        while (i<MaxLength)
        {
            c=(unsigned char)Value[i];
            c-='0';
            if (c>9)
                break;
            S*=10;
            S+=c;
            i++;
        }
        if (i!=Length)
            return true;
        if (!MoreSamples_Frequency)
            MoreSamples_Frequency=1; // Arbitrary choosen
        int64s OneHourInFrequency=3600*MoreSamples_Frequency;
        int64s OneMinuteInFrequency=60*MoreSamples_Frequency;
        Hours=S/OneHourInFrequency;
        Minutes=(S%OneHourInFrequency)/OneMinuteInFrequency;
        Seconds=(S%OneMinuteInFrequency)/MoreSamples_Frequency;
        Frames=0; // (int8u)-1;
        MoreSamples=S%MoreSamples_Frequency;
        return false;
    }

    return true;
}

//---------------------------------------------------------------------------
string TimeCode::ToString() const
{
    string TC;
    if (IsNegative)
        TC+='-';
    TC+=('0'+Hours/10);
    TC+=('0'+Hours%10);
    TC+=':';
    TC+=('0'+Minutes/10);
    TC+=('0'+Minutes%10);
    TC+=':';
    TC+=('0'+Seconds/10);
    TC+=('0'+Seconds%10);
    if (Frames!=(int8u)-1)
    {
        TC+=DropFrame?';':':';
        TC+=('0'+(Frames*(MustUseSecondField?2:1)+(IsSecondField?1:0))/10);
        TC+=('0'+(Frames*(MustUseSecondField?2:1)+(IsSecondField?1:0))%10);
    }
    if (MoreSamples_Frequency)
    {
        int AfterCommaMinus1;
        int32s MoreSamples_Temp;
        if (MoreSamples<0)
        {
            AfterCommaMinus1=-1;
            TC+='-';
            MoreSamples_Temp=-MoreSamples;
        }
        else
        {
            AfterCommaMinus1=PowersOf10_Size;
            while ((--AfterCommaMinus1)>=0 && PowersOf10[AfterCommaMinus1]!=MoreSamples_Frequency);
            TC+=(Frames!=(int8u)-1 || AfterCommaMinus1<0)?'+':'.';
            MoreSamples_Temp=MoreSamples;
        }
        if ((Frames!=(int8u)-1 || AfterCommaMinus1<0))
        {
            stringstream s;
            s<<MoreSamples_Temp;
            TC+=s.str();
            TC+='S';
            s.str(string());
            s<<MoreSamples_Frequency;
            TC+=s.str();
        }
        else
        {
            for (int i=0; i<=AfterCommaMinus1;i++)
                TC+='0'+(MoreSamples_Temp/(i==AfterCommaMinus1?1:PowersOf10[AfterCommaMinus1-i-1])%10);
        }
    }

    return TC;
}

//---------------------------------------------------------------------------
int64s TimeCode::ToFrames() const
{
    if (!FramesPerSecond)
        return 0;

    int64s TC=(int64s(Hours)     *3600
             + int64s(Minutes)   *  60
             + int64s(Seconds)        )*int64s(FramesPerSecond)
             + int64s(Frames);

    if (DropFrame)
    {
        int Dropped=0;
        int FramesPerSecond2=FramesPerSecond-1;
        for (;;)
        {
            Dropped++;
            FramesPerSecond2-=30;
            if (FramesPerSecond2<0)
                break;
        }

        TC-= int64s(Hours)      *108*Dropped
          + (int64s(Minutes)/10)*18*Dropped
          + (int64s(Minutes)%10)* 2*Dropped;
    }

    TC*=(MustUseSecondField?2:1);
    TC+=(IsSecondField?1:0);

    return IsNegative?-TC:TC;
}

//---------------------------------------------------------------------------
int64s TimeCode::ToMilliseconds() const
{
    if (!FramesPerSecond || Frames==(int8u)-1)
    {
        int64s TC=(int64s(Hours)     *3600
                 + int64s(Minutes)   *  60
                 + int64s(Seconds)        )*1000;
        if (Frames!=(int8u)-1)
        {
            if (MoreSamples_Frequency)
            {
                int32s MoreSamples_Frequency_Duplicate=MoreSamples_Frequency;
                if (!(MoreSamples_Frequency_Duplicate%1000))
                    MoreSamples_Frequency_Duplicate/=1000;
                if (MoreSamples_Frequency_Duplicate<=0xFF)
                {
                    TimeCode Duplicate(*this);
                    Duplicate.FramesPerSecond=(int8u)MoreSamples_Frequency_Duplicate;
                    return Duplicate.ToMilliseconds();
                }
            }
            TC+=(((int64u)Frames)*1000+0xFF/2)/0xFF;
        }
        if (MoreSamples_Frequency)
            TC+=(((int64s)MoreSamples)*1000+MoreSamples_Frequency/2)/MoreSamples_Frequency;
        return TC;
    }

    int64s MS=float64_int64s(ToFrames()*1000*((DropFrame || FramesPerSecond_Is1001)?1.001:1.000)/(FramesPerSecond*(MustUseSecondField?2:1)));
    if (MoreSamples_Frequency)
        MS+=(((int64s)MoreSamples)*1000+MoreSamples_Frequency/2)/MoreSamples_Frequency;

    return IsNegative?-MS:MS;
}

//***************************************************************************
//
//***************************************************************************

//---------------------------------------------------------------------------
//Modified Julian Date
Ztring Date_MJD(int16u Date_)
{
    //Calculating
    float64 Date=Date_;
    int Y2=(int)((Date-15078.2)/365.25);
    int M2=(int)(((Date-14956.1) - ((int)(Y2*365.25))) /30.6001);
    int D =(int)(Date-14956 - ((int)(Y2*365.25)) - ((int)(M2*30.6001)));
    int K=0;
    if (M2==14 || M2==15)
        K=1;
    int Y =Y2+K;
    int M =M2-1-K*12;

    //Formating
    return                       Ztring::ToZtring(1900+Y)+__T("-")
         + (M<10?__T("0"):__T(""))+Ztring::ToZtring(     M)+__T("-")
         + (D<10?__T("0"):__T(""))+Ztring::ToZtring(     D);
}

//---------------------------------------------------------------------------
//Form: HHMMSS, BCD
Ztring Time_BCD(int32u Time)
{
    return (((Time>>16)&0xFF)<10?__T("0"):__T("")) + Ztring::ToZtring((Time>>16)&0xFF, 16)+__T(":") //BCD
         + (((Time>> 8)&0xFF)<10?__T("0"):__T("")) + Ztring::ToZtring((Time>> 8)&0xFF, 16)+__T(":") //BCD
         + (((Time    )&0xFF)<10?__T("0"):__T("")) + Ztring::ToZtring((Time    )&0xFF, 16);        //BCD
}


} //NameSpace
