/*==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 "HeadSpin.h"
#include "Max.h"
#include "notetrck.h"

#include "hsTypes.h"
#include "hsTemplates.h"

#include "plMaxAnimUtils.h"
#include "MaxExport/plErrorMsg.h"

float TimeValueToGameTime(TimeValue t)
{
    int FR = ::GetFrameRate();
    int TPF = ::GetTicksPerFrame();
    int TPS = TPF*FR;

    return float(t)/float(TPS);
}

bool GetSegMapAnimTime(const char *animName, SegmentMap *segMap, SegmentSpec::SegType type, float& begin, float& end)
{
    if (segMap)
    {
        if (animName && segMap->find(animName) != segMap->end())
        {
            SegmentSpec *spec = (*segMap)[animName];
            if (spec->fType == type)
            {
                if (spec->fStart != -1)
                    begin = spec->fStart;
                if (spec->fEnd != -1)
                    end = spec->fEnd;
                return true;
            }
        }
    }

    return false;
}

SegmentMap *GetSharedAnimSegmentMap(std::vector<Animatable*>& anims, plErrorMsg *pErrorMsg)
{
    if (anims.empty())
        return nil;

    SegmentMap *segMap = GetAnimSegmentMap(anims[0], pErrorMsg);
    if (!segMap)
        return nil;

    int i;
    for (i = 1; i < anims.size(); i++)
    {
        SegmentMap *curSegMap = GetAnimSegmentMap(anims[i], pErrorMsg);
        // This node doesn't have a segmap, so we can't have any anims shared among all the nodes.
        if (!curSegMap)
        {
            DeleteSegmentMap(segMap);
            return nil;
        }

        if (segMap->begin() == segMap->end())
        {
            DeleteSegmentMap(segMap);
            return nil;
        }

        SegmentMap::iterator it = segMap->begin();
        while (it != segMap->end())
        {
            if (curSegMap->find(it->second->fName) == curSegMap->end())
            {
                SegmentMap::iterator del = it;
                it++;
                segMap->erase(del->second->fName);
            }
            else
                it++;
        }

        DeleteSegmentMap(curSegMap);
    }

    return segMap;
}

SegmentSpec::SegmentSpec(float start, float end, char * name, SegType type) :
    fStart(start), fEnd(end), fName(name), fType(type), fInitial(-1)
{
}

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

// constants used for parsing the note tracks

enum NoteType
{
    kNoteStartAnim,
    kNoteEndAnim,
    kNoteStartLoop,
    kNoteEndLoop,
    kNoteMarker,
    kNoteStopPoint,
    kNoteInitial,
    kNoteUnknown,
    kNoteSuppress,
};

SegmentSpec::SegmentSpec()
{
    fStart = -1;
    fEnd = -1;
    fInitial = -1;
    fName = nil;
    fType = kAnim;
}

bool SegmentSpec::Contains(SegmentSpec *spec)
{
    if (!spec)
        return false;

    if (spec->fType == kMarker || spec->fType == kStopPoint)
        return (spec->fStart >= fStart && spec->fStart <= fEnd);

    if (fStart == -1 || fEnd == -1)
        return false;

    if (spec->fStart == -1)
        return (fStart < spec->fEnd);

    if (spec->fEnd == -1)
        return (fEnd > spec->fStart);

    if (fStart <= spec->fStart && fEnd >= spec->fEnd)
        return true;

    return false;
}

bool DoesHaveStopPoints(Animatable *anim)
{
    if (!anim || !anim->HasNoteTracks())
        return false;

    int numTracks = anim->NumNoteTracks();
    for (int i = 0; i < numTracks; i++)
    {
        DefNoteTrack *track = (DefNoteTrack *)anim->GetNoteTrack(i);
        int numKeys = track->keys.Count();

        for (int j = 0; j < numKeys; j++)
        {
            char buf[256];
            strcpy(buf, track->keys[j]->note);
            strlwr(buf);
            if (strstr(buf, "@stoppoint"))
                return true;
        }
    }

    return false;
}

void GetSegment(const char *note, float time, SegmentMap *segMap, plErrorMsg *pErrMsg)
{
    char segName[256];
    char segSuffix[256];

    int matchedFields = sscanf(note, " %[^@] @ %s ", segName, segSuffix);
    
    if (matchedFields == 2)
    {
        NoteType type = kNoteUnknown;

        if (!stricmp(segSuffix, "start") ||
            !stricmp(segSuffix, "begin"))
            type = kNoteStartAnim;
        else if (!stricmp(segSuffix, "end"))
            type = kNoteEndAnim;
        else if (!stricmp(segSuffix, "startloop") ||
                !stricmp(segSuffix, "loopstart") ||
                !stricmp(segSuffix, "beginloop") ||
                !stricmp(segSuffix, "loopbegin"))
            type = kNoteStartLoop;
        else if (!stricmp(segSuffix, "endloop") ||
                !stricmp(segSuffix, "loopend"))
            type = kNoteEndLoop;
        else if (!stricmp(segSuffix, "marker"))
            type = kNoteMarker;
        else if (!stricmp(segSuffix, "stoppoint"))
            type = kNoteStopPoint;
        else if (!stricmp(segSuffix, "initial"))
            type = kNoteInitial;
        else if (!stricmp(segSuffix, "suppress"))
            type = kNoteSuppress;

        if (type == kNoteUnknown)
        {
            if (pErrMsg)
            {
                pErrMsg->Set(true, "NoteTrack Anim Error", "Malformed segment note: %s", segName);
                pErrMsg->Show();
                pErrMsg->Set();
            }
        }
        else
        {
            SegmentMap::iterator existing = segMap->find(segName);
            SegmentSpec *existingSpec = (existing != segMap->end()) ? (*existing).second : nil;
            const char *kErrorTitle = "NoteTrack Anim Error";

            if (existingSpec)
            {
                // an existing spec, but we're processing a start note?
                if (type == kNoteStartAnim && pErrMsg)
                {
                    pErrMsg->Set(true, kErrorTitle, "Got out of order start note.  No Start given for %s", segName).Show();
                    pErrMsg->Set();
                }
                // existing spec, has an end, we're also processing an end?
                else if (type == kNoteEndAnim && existingSpec->fEnd != -1 && pErrMsg)
                {
                    pErrMsg->Set(true, kErrorTitle, "Got two ends for the same segment %s", segName).Show();
                    pErrMsg->Set();
                }
                else if (type == kNoteStartLoop && existingSpec->fStart != -1 && pErrMsg)
                {
                    pErrMsg->Set(true, kErrorTitle, "Got two loop starts for the same segment, %s", segName).Show();
                    pErrMsg->Set();
                }
                else if (type == kNoteEndLoop && existingSpec->fEnd != -1 && pErrMsg)
                {
                    pErrMsg->Set(true, kErrorTitle, "Got two loop ends for the same segment, %s", segName).Show();
                    pErrMsg->Set();
                }
                else if (type == kNoteMarker && pErrMsg)
                {
                    pErrMsg->Set(true, kErrorTitle, "Marker has the same name (%s) as another spec in its notetrack", segName).Show();
                    pErrMsg->Set();
                }
                else if (type == kNoteStopPoint && pErrMsg)
                {
                    pErrMsg->Set(true, kErrorTitle, "Stop point has the same name (%s) as another spec in its notetrack", segName).Show();
                    pErrMsg->Set();
                }

                if (type == kNoteEndAnim || type == kNoteEndLoop)
                    existingSpec->fEnd = time;
                else if (type == kNoteStartLoop)
                    existingSpec->fStart = time;
                else if (type == kNoteInitial)
                    existingSpec->fInitial = time;
            }
            else
            {
                if (type == kNoteEndAnim && pErrMsg)
                {
                    pErrMsg->Set(true, kErrorTitle, "Got an end note without a corresponding start. Ignoring %s", segName).Show();
                    pErrMsg->Set();
                }
                else
                {
                    char *nameCopy = TRACKED_NEW char[strlen(segName)+1];
                    strcpy(nameCopy, segName);

                    switch (type)
                    {
                    case kNoteStartAnim:
                        (*segMap)[nameCopy] = TRACKED_NEW SegmentSpec(time, -1, nameCopy, SegmentSpec::kAnim);
                        break;

                    case kNoteStartLoop:
                        (*segMap)[nameCopy] = TRACKED_NEW SegmentSpec(time, -1, nameCopy, SegmentSpec::kLoop);
                        break;

                    case kNoteEndLoop:
                        (*segMap)[nameCopy] = TRACKED_NEW SegmentSpec(-1, time, nameCopy, SegmentSpec::kLoop);
                        break;

                    case kNoteMarker:
                        (*segMap)[nameCopy] = TRACKED_NEW SegmentSpec(time, -1, nameCopy, SegmentSpec::kMarker);
                        break;

                    case kNoteStopPoint:
                        (*segMap)[nameCopy] = TRACKED_NEW SegmentSpec(time, -1, nameCopy, SegmentSpec::kStopPoint);
                        break;

                    case kNoteSuppress:
                        (*segMap)[nameCopy] = TRACKED_NEW SegmentSpec(-1, -1, nameCopy, SegmentSpec::kSuppress);
                        break;
                        
                    default:
                        delete [] nameCopy;
                        break;
                    }
                }
            }
        }
    }
}

// Read through all the notes in all the note tracks on the given node
// Check the contents of each node for a name like "walk@start", i.e. <string>@[start | end]
// For each match, open a segment specification and p
SegmentMap * GetAnimSegmentMap(Animatable *anim, plErrorMsg *pErrMsg)
{
    if (!anim->HasNoteTracks())
        return nil;
    
    SegmentMap *segMap = TRACKED_NEW SegmentMap();

    int numTracks = anim->NumNoteTracks();

    for (int i = 0; i < numTracks; i++)
    {
        DefNoteTrack * track = (DefNoteTrack *)anim->GetNoteTrack(i);
        int numKeys = track->keys.Count();

        for (int j = 0; j < numKeys; j++)
        {
            char *note = track->keys[j]->note;
            float time = TimeValueToGameTime(track->keys[j]->time);
            GetSegment(note, time, segMap, pErrMsg);
        }
    }

    return segMap;
}

void DeleteSegmentMap(SegmentMap *segMap)
{
    // If we have a segment map, delete the memory associated with it
    if (segMap)
    {
        for (SegmentMap::iterator i = segMap->begin(); i != segMap->end(); i++)
            delete (*i).second;

        delete segMap;
    }
}