/*==LICENSE==*

CyanWorlds.com Engine - MMOG client, server and tools
Copyright (C) 2011  Cyan Worlds, Inc.

This program 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
(at your option) any later version.

This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.

You can contact Cyan Worlds, Inc. by email legal@cyan.com
 or by snail mail at:
      Cyan Worlds, Inc.
      14617 N Newport Hwy
      Mead, WA   99021

*==LICENSE==*/

#include "hsStream.h"
#include "plAgeDescription.h"
#include "hsUtils.h"
#include "hsStlUtils.h"
#include "plFile/hsFiles.h"
#include "plFile/plInitFileReader.h"
#include "plFile/plEncryptedStream.h"
#include "hsStringTokenizer.h"
#include <functional>
#include <algorithm>


const UInt32    plAgePage::kInvalidSeqSuffix = (UInt32)-1;

plAgePage::plAgePage( const char *name, UInt32 seqSuffix, Byte flags )
{
    fName = name != nil ? hsStrcpy( name ) : nil;
    fSeqSuffix = seqSuffix;
    fFlags = flags;
}

plAgePage::plAgePage( char *stringFrom ) : fName(nil)
{
    SetFromString( stringFrom );
}

plAgePage::plAgePage()
{
    fName = nil;
    fFlags = 0;
    fSeqSuffix = 0;
}

plAgePage::plAgePage( const plAgePage &src ) : fName(nil)
{
    fName = src.fName != nil ? hsStrcpy( src.fName ) : nil;
    fSeqSuffix = src.fSeqSuffix;
    fFlags = src.fFlags;
}

plAgePage::~plAgePage()
{
    delete [] fName;
}

plAgePage &plAgePage::operator=( const plAgePage &src )
{
    delete [] fName;
    fName = src.fName != nil ? hsStrcpy( src.fName ) : nil;
    fSeqSuffix = src.fSeqSuffix;
    fFlags = src.fFlags;
    
    return *this;
}

void plAgePage::SetFlags(Byte f, bool on)
{
    if (on)
        hsSetBits(fFlags, f);
    else
        hsClearBits(fFlags, f);
}

// now preservs original string
hsBool  plAgePage::SetFromString( const char *stringIn )
{
    char    *c, seps[] = ", \n";
    std::string string = stringIn;

    // Parse. Format is going to be "pageName[,seqSuffix[,flags]]"
    c = strtok( (char*)string.c_str(), seps );
    if( c == nil )
        return false;

    delete [] fName;
    fName = hsStrcpy( c );

    // Look for seqSuffix
    c = strtok( nil, seps );
    if( c != nil )
    {
        fSeqSuffix = atoi( c );

        // Look for flags
        c = strtok( nil, seps );
        if( c != nil )
        {
            fFlags = atoi( c );
        }
        else
            fFlags = 0;
    }
    else
    {
        fSeqSuffix = kInvalidSeqSuffix;
        fFlags = 0;
    }

    return true;
}

char    *plAgePage::GetAsString( void ) const
{
    static char str[ 256 ];


    // Format is "pageName[,seqSuffix[,flags]]"
    if( fFlags != 0 )
        sprintf( str, "%s,%d,%d", fName, fSeqSuffix, fFlags );
    else
        sprintf( str, "%s,%d", fName, fSeqSuffix );
    return str;
}


//
//  plAgeDescription
//
//
//

// static
char    plAgeDescription::kAgeDescPath[]={"dat"PATH_SEPARATOR_STR};
char    *plAgeDescription::fCommonPages[] = { "Textures", "BuiltIn" };

// Also gotta init the separators for our helper reading function
plAgeDescription::plAgeDescription() : plInitSectionTokenReader()
{
    IInit();
}


plAgeDescription::~plAgeDescription()
{
    IDeInit();
}

void plAgeDescription::IDeInit()
{
    ClearPageList();
    delete [] fName;    
}

plAgeDescription::plAgeDescription( const char *fileNameToReadFrom ) : plInitSectionTokenReader()
{
    ReadFromFile(fileNameToReadFrom);
}

//
// Reads from a file, returns false if failed.
//
bool plAgeDescription::ReadFromFile( const char *fileNameToReadFrom )
{
    IInit();

    hsStream* stream = plEncryptedStream::OpenEncryptedFile(fileNameToReadFrom);
    if( !stream )
        return false;

    Read( stream );
    stream->Close();
    delete stream;

    SetAgeNameFromPath( fileNameToReadFrom );
    return true;
}

void    plAgeDescription::SetAgeNameFromPath( const char *path )
{
    delete [] fName;

    if( path == nil )
    {
        fName = nil;
        return;
    }

    // Construct our name from the path
    const char *pathSep1 = strrchr( path, '\\' );
    const char *pathSep2 = strrchr( path, '/' );
    if( pathSep2 > pathSep1 )
        pathSep1 = pathSep2;
    if( pathSep1 == nil )
        pathSep1 = (char *)path;
    else
        pathSep1++; // Get past the actual character we found

    char    temp[ 512 ];
    strcpy( temp, pathSep1 );
    char *end = strrchr( temp, '.' );
    if( end != nil )
        *end = 0;

    fName = hsStrcpy( temp );
}

void    plAgeDescription::IInit( void )
{
    fName = nil;
    fDayLength = 24.0f;
    fMaxCapacity = -1;
    fLingerTime = 180;  // seconds
    fSeqPrefix = 0;
    fReleaseVersion = 0;
    fStart.SetMode( plUnifiedTime::kLocal );

    fPageIterator = -1;
}

struct SzDelete {   void operator()(char * str) { delete [] str;} };
void plAgeDescription::ClearPageList()
{
    fPages.Reset();
}


void    plAgeDescription::AppendPage( const char *name, int seqSuffix, Byte flags )
{
    fPages.Append( plAgePage( name, ( seqSuffix == -1 ) ? fPages.GetCount() : (UInt32)seqSuffix, flags ) );
}

void    plAgeDescription::SeekFirstPage( void )
{
    fPageIterator = 0;
}

plAgePage   *plAgeDescription::GetNextPage( void )
{
    plAgePage   *ret = nil;


    if( fPageIterator >= 0 && fPageIterator < fPages.GetCount() )
    {
        ret = &fPages[ fPageIterator++ ];
        if( fPageIterator >= fPages.GetCount() )
            fPageIterator = -1;
    }

    return ret;
}

void    plAgeDescription::RemovePage( const char *page )
{
    int     i;

    for( i = 0; i < fPages.GetCount(); i++ )
    {
        if( strcmp( page, fPages[ i ].GetName() ) == 0 )
        {
            fPages.Remove( i );
            return;
        }
    }
}

plAgePage   *plAgeDescription::FindPage( const char *name ) const
{
    int     i;


    for( i = 0; i < fPages.GetCount(); i++ )
    {
        if( strcmp( name, fPages[ i ].GetName() ) == 0 )
            return &fPages[ i ];
    }

    return nil;
}

plLocation  plAgeDescription::CalcPageLocation( const char *page ) const
{
    plAgePage *ap = FindPage( page );
    if( ap != nil )
    {
        // Combine our sequence # together
        Int32 combined;
        hsAssert(abs(fSeqPrefix) < 0xFF, "Age sequence prefex is out of range!"); // sequence prefix can NOT be larger or equal to 1-byte max value
        UInt32 suffix = ap->GetSeqSuffix();
        hsAssert(suffix <= 0xFFFF, "Page sequence number is out of range!"); // page sequence number can NOT be larger then 2-byte max value
        if( fSeqPrefix < 0 ) // we are a global age
            combined = -(Int32)( ( ( -fSeqPrefix ) << 16 ) + suffix );
        else
            combined = ( fSeqPrefix << 16 ) + suffix;

        // Now, our 32 bit number looks like the following:
        // 0xRRAAPPPP
        // - RR is FF when reserved, and 00 when normal
        // - AA is the one byte for age sequence prefix (FF not allowed because 0xFFFFFFFFFF is reserved for invalid sequence number)
        // - PPPP is the two bytes for page sequence number

        if( IsGlobalAge() )
            return plLocation::MakeReserved( (UInt32)combined );
        else
        {
            plLocation ret = plLocation::MakeNormal( combined );
            if (page && !stricmp(page, "builtin"))
                ret.SetFlags(plLocation::kBuiltIn);
            return ret;
        }       
    }

    // Just make a blank (invalid) one
    plLocation  loc;
    return loc;
}

//
// Writes the Age Description File
//
void plAgeDescription::Write(hsStream* stream) const
{
    char buf[256];

    // Write the date/time
    sprintf(buf, "StartDateTime=%010u\n", fStart.GetSecs());
    stream->WriteString(buf);

    // Write the day length
    sprintf(buf, "DayLength=%f\n", fDayLength);
    stream->WriteString(buf);

    // Write the max capacity
    sprintf(buf, "MaxCapacity=%d\n", fMaxCapacity);
    stream->WriteString(buf);

    // Write the linger time
    sprintf(buf, "LingerTime=%d\n", fLingerTime);
    stream->WriteString(buf);

    // Write out the sequence prefix
    sprintf( buf, "SequencePrefix=%d\n", fSeqPrefix );
    stream->WriteString( buf );

    // Write out the release version
    sprintf( buf, "ReleaseVersion=%d\n", fReleaseVersion );
    stream->WriteString( buf );

    // Write out the pages
    int i;
    for( i = 0; i < fPages.GetCount(); i++ )
    {
        sprintf(buf, "Page=%s\n", fPages[ i ].GetAsString() );
        stream->WriteString(buf);
    }
}

// Somewhat of an overkill, but I created it, so I better use it.
// The really nifty (or scary, depending on your viewpoint) thing is that, since
// we only have one section, we can safely use ourselves as the section reader.
// Later I might just add section readers with function pointers to avoid this need entirely

const char  *plAgeDescription::GetSectionName( void ) const
{
    return "AgeInfo";
}

hsBool      plAgeDescription::IParseToken( const char *token, hsStringTokenizer *tokenizer, UInt32 userData )
{
    char *tok;

    if( !stricmp( token, "StartDateTime" ) )
    {
        if( ( tok = tokenizer->next() ) != nil )
        {
            char buf[11];
            strncpy(buf, tok, 10); buf[10] = '\0';
            fStart.SetSecs(atoi(buf));
        }
    }
    else if (!stricmp(token, "DayLength"))
    {
        if( ( tok = tokenizer->next() ) != nil )
            fDayLength = (float)atof(tok);
    }
    else if (!stricmp(token, "Page"))
    {
        fPages.Append( plAgePage( tokenizer->GetRestOfString() ) );
        if( fPages[ fPages.GetCount() - 1 ].GetSeqSuffix() == plAgePage::kInvalidSeqSuffix )
            fPages[ fPages.GetCount() - 1 ].SetSeqSuffix( fPages.GetCount() );
    }
    else if (!stricmp(token, "MaxCapacity"))
    {
        if( ( tok = tokenizer->next() ) != nil )
            fMaxCapacity = atoi(tok);
    }
    else if (!stricmp(token, "LingerTime"))
    {
        if( ( tok = tokenizer->next() ) != nil )
            fLingerTime = atoi(tok);
    }
    else if( !stricmp(token, "SequencePrefix"))
    {
        if( ( tok = tokenizer->next() ) != nil )
            fSeqPrefix = atoi(tok);
    }
    else if( !stricmp(token, "ReleaseVersion"))
    {
        if( ( tok = tokenizer->next() ) != nil )
            fReleaseVersion = atoi(tok);
    }

    return true;
}

//
// Reads the Age Description File
//
void plAgeDescription::Read(hsStream* stream)
{
    plInitSectionReader *sections[] = { (plInitSectionReader *)this, nil };

    plInitFileReader    reader( stream, sections );

    if( !reader.IsOpen() )
    {
        hsAssert( false, "Unable to open age description file for reading" );
        return;
    }
    
    reader.Parse();
    reader.Close();
}

//
// What is the current time in the age (in secs), based on dayLength.
//
int plAgeDescription::GetAgeTimeOfDaySecs(const plUnifiedTime& earthCurrentTime) const
{
    double elapsedSecs = GetAgeElapsedSeconds(earthCurrentTime);
    int secsInADay = (int)(fDayLength * 60 * 60);
    int ageTime = (int)elapsedSecs % secsInADay;
    return ageTime;
}

//
// What is the current time in the age (from 0-1), based on dayLength.
//
float plAgeDescription::GetAgeTimeOfDayPercent(const plUnifiedTime& earthCurrentTime) const
{
    double elapsedSecs = GetAgeElapsedSeconds(earthCurrentTime);
    int secsInADay = (int)(fDayLength * 60 * 60);
    double ageTime = fmod(elapsedSecs, secsInADay);
    float percent=(float)(ageTime/secsInADay);
    if (percent<0.f)
        percent=0.f;
    if (percent>1.f)
        percent=1.f;
    return percent;
}

//
// How old is the age in days.
//
double plAgeDescription::GetAgeElapsedDays(plUnifiedTime earthCurrentTime) const
{
    earthCurrentTime.SetMicros(0);  // Ignore micros for this calculation

    double elapsedSecs = GetAgeElapsedSeconds(earthCurrentTime);
    return elapsedSecs / 60.0 / 60.0 / fDayLength;  // days and fractions
}

//
// How many seconds have elapsed since the age was born.  or how old is the age (in secs)
//
double plAgeDescription::GetAgeElapsedSeconds(const plUnifiedTime & earthCurrentTime) const
{
    plUnifiedTime elapsed = earthCurrentTime - fStart;
    return elapsed.GetSecsDouble();
}

    // Static functions for the available common pages
    enum CommonPages
    {
        kTextures,
        kGlobal
    };

const char *plAgeDescription::GetCommonPage( int pageType )
{
    hsAssert( pageType < kNumCommonPages, "Invalid page type in GetCommonPage()" );
    return fCommonPages[ pageType ];
}

void    plAgeDescription::AppendCommonPages( void )
{
    UInt32 startSuffix = 0xffff, i;


    if( IsGlobalAge() )
        return;

    for( i = 0; i < kNumCommonPages; i++ )
        fPages.Append( plAgePage( fCommonPages[ i ], startSuffix - i, 0 ) );
}

void    plAgeDescription::CopyFrom(const plAgeDescription& other)
{
    IDeInit();
    fName = hsStrcpy(other.GetAgeName());
    int i;
    for(i=0;i<other.fPages.GetCount(); i++)
        fPages.Append( other.fPages[ i ] );

    fStart = other.fStart;
    fDayLength = other.fDayLength;
    fMaxCapacity = other.fMaxCapacity;
    fLingerTime = other.fLingerTime;

    fSeqPrefix = other.fSeqPrefix;
    fReleaseVersion = other.fReleaseVersion;
}

bool plAgeDescription::FindLocation(const plLocation& loc) const
{
    int i;
    for( i = 0; i < fPages.GetCount(); i++ )
    {
        plLocation pageLoc = CalcPageLocation(fPages[i].GetName());
        if (pageLoc == loc)
            return true;
    }
    return false;
}