/*==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/>.

Additional permissions under GNU GPL version 3 section 7

If you modify this Program, or any covered work, by linking or
combining it with any of RAD Game Tools Bink SDK, Autodesk 3ds Max SDK,
NVIDIA PhysX SDK, Microsoft DirectX SDK, OpenSSL library, Independent
JPEG Group JPEG library, Microsoft Windows Media SDK, or Apple QuickTime SDK
(or a modified version of those libraries),
containing parts covered by the terms of the Bink SDK EULA, 3ds Max EULA,
PhysX SDK EULA, DirectX SDK EULA, OpenSSL and SSLeay licenses, IJG
JPEG Library README, Windows Media SDK EULA, or QuickTime SDK EULA, the
licensors of this Program grant you additional
permission to convey the resulting work. Corresponding Source for a
non-source form of such a combination shall include the source code for
the parts of OpenSSL and IJG JPEG Library used as well as that of the covered
work.

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 "plKeyFinder.h"

#include "hsTemplates.h"
#include "hsStlUtils.h"

#include "hsResMgr.h"
#include "plResManager.h"

#include "plRegistryHelpers.h"
#include "plRegistryNode.h"
#include "plRegistryKeyList.h"
#include "plPageInfo.h"
#include "pnFactory/plFactory.h"
#include "hsUtils.h"
#include "plCreatableIndex.h"

plResManager* IGetResMgr() { return (plResManager*)hsgResMgr::ResMgr(); }

plKeyFinder& plKeyFinder::Instance()
{
    static plKeyFinder theInstance;
    return theInstance;
}

const char* plKeyFinder::GetLastErrorString() // For Console display
{
    // For Console display
    static const char* KeyFinderErrors[] =
    {
        "Ok",
        "Age not found",
        "Page not found",
        "Invalid class",
        "None of those classes in this page",
        "Object not found"
    };
    return KeyFinderErrors[fLastError];
}

//
// Does name string compare with potentially mangled (ie. [1 0 0]foo) names
//
hsBool NameMatches(const char* obName, const char* pKName, hsBool subString)
{
    if (!obName || !pKName)
        return false;

    const char *o = obName;
    const char *p = pKName;

    // If names are mangled, unmangle
    if (*o != '[' || *p != '[')
    {
        // skip past ']' in both names in case mangled
        while (*o && *o != ']')
            o++;
        o = (*o==']') ? o+1 : obName;

        while (*p && *p != ']')
            p++;
        p = (*p==']') ? p+1 : pKName;
    }

    if (!subString)
    {
        if (!stricmp(o, p))
            return true;            // FOUND IT!!!!!!!!!!!!!!!!!!!
    }
    else
    {
        char oCopy[256], pCopy[256];
        strcpy(oCopy, o);
        strcpy(pCopy, p);
        hsStrLower(oCopy);
        hsStrLower(pCopy);
        if (strstr(pCopy, oCopy))
            return true;
    }

    return false;
}

plKey plKeyFinder::StupidSearch(const char * age, const char * rm, 
                                 const char *className, const char *obName, hsBool subString)
{
    UInt16 ty = plFactory::FindClassIndex(className);
    return StupidSearch(age, rm, ty, obName, subString);
}

class plKeyFinderIter : public plRegistryKeyIterator, public plRegistryPageIterator
{
protected:
    UInt16      fClassType;
    const char  *fObjName;
    hsBool      fSubstr;
    plKey       fFoundKey;
    const char  *fAgeName;

public:
    plKey   GetFoundKey( void ) const { return fFoundKey; }

    plKeyFinderIter( UInt16 classType, const char *obName, hsBool substr ) 
            : fFoundKey( nil ), fClassType( classType ), fObjName( obName ), fSubstr( substr ) { }

    plKeyFinderIter( UInt16 classType, const char *obName, hsBool substr, const char *ageName ) 
        : fFoundKey( nil ), fClassType( classType ), fObjName( obName ), fSubstr( substr ),
            fAgeName( ageName ) {}

    virtual hsBool  EatKey( const plKey& key )
    {
        if( key->GetUoid().GetClassType() == fClassType &&
            NameMatches( fObjName, key->GetUoid().GetObjectName(), fSubstr ) )
        {
            fFoundKey = key;
            return false;
        }

        return true;
    }

    virtual hsBool  EatPage( plRegistryPageNode *pageNode )
    {
#ifndef _DEBUG
        try
        {
#endif
            if( stricmp( pageNode->GetPageInfo().GetAge(), fAgeName ) == 0 )
            {
                // Try loading and searching thru this page
                hsTArray<plKey> keyRefs;

                IGetResMgr()->LoadPageKeys( pageNode );
                plKeyCollector coll( keyRefs );
                pageNode->IterateKeys( &coll );

                if( !pageNode->IterateKeys( this ) )
                    return false;
            }
#ifndef _DEBUG
        } catch (...)
        {
        }
#endif
        return true;
    }
};

plKey plKeyFinder::StupidSearch(const char * age, const char * rm, 
                                 UInt16 classType, const char *obName, hsBool subString)
{
    if (!obName)
        return nil;

    plUoid newOid;

    fLastError = kOk;

    UInt16 maxClasses = plFactory::GetNumClasses();

    UInt16 ty = classType;
    if (ty == maxClasses)   // error
    {   fLastError = kInvalidClass;
        return nil;
    }   

    if( age != nil && rm != nil )
    {
        const plLocation &loc = IGetResMgr()->FindLocation( age, rm );
        if( !loc.IsValid() )
        {
            fLastError = kPageNotFound;
            return nil;
        }

        plKeyFinderIter keyFinder( classType, obName, subString );

        if( !IGetResMgr()->IterateKeys( &keyFinder, loc ) )
            // Return value of false means it stopped somewhere, i.e. found something
            return keyFinder.GetFoundKey();
    }
    else if( age != nil )
    {
        plKeyFinderIter keyFinder(classType, obName, subString, age);

        if( !IGetResMgr()->IterateAllPages( &keyFinder ) )
            return keyFinder.GetFoundKey();
    }
    else
    {
        plKeyFinderIter keyFinder( classType, obName, subString );

        if( !IGetResMgr()->IterateKeys( &keyFinder ) )
            // Return value of false means it stopped somewhere, i.e. found something
            return keyFinder.GetFoundKey();
    }

    fLastError = kObjectNotFound;
    return nil;
}

void plKeyFinder::ReallyStupidResponderSearch(const char *name, std::vector<plKey>& foundKeys, const plLocation &hintLocation )
{
    ReallyStupidSubstringSearch(name, CLASS_INDEX_SCOPED(plResponderModifier), foundKeys, hintLocation);
}

void plKeyFinder::ReallyStupidActivatorSearch(const char *name, std::vector<plKey>& foundKeys, const plLocation &hintLocation)
{
    // use the createable macro so we don't have to pull in all of Python
    ReallyStupidSubstringSearch(name, CLASS_INDEX_SCOPED(plLogicModifier), foundKeys, hintLocation);
    ReallyStupidSubstringSearch(name, CLASS_INDEX_SCOPED(plPythonFileMod), foundKeys, hintLocation);
    ReallyStupidSubstringSearch(name, CLASS_INDEX_SCOPED(plSittingModifier), foundKeys, hintLocation);
}

void plKeyFinder::IGetNames(std::vector<std::string>& names, const char* searchName, int index)
{
    // Not really searching for any particular key, just need all the logic mods
    std::vector<plKey> keys;
    ReallyStupidSubstringSearch(searchName, index, keys);

    for (int i = 0; i < keys.size(); i++)
    {
        // Only allow loaded ones to cut down on the crap
        plKey key = keys[i];
        if (key->ObjectIsLoaded())
            names.push_back(key->GetName());
    }
}

void plKeyFinder::GetResponderNames(std::vector<std::string>& names)
{
    IGetNames(names, "", CLASS_INDEX_SCOPED(plResponderModifier));
}

void plKeyFinder::GetActivatorNames(std::vector<std::string>& names)
{
    IGetNames(names, "", CLASS_INDEX_SCOPED(plLogicModifier));
}

class plKeyFinderIterator : public plRegistryKeyIterator, public plRegistryPageIterator
{
    protected:

        UInt16      fClassType;
        const char  *fObjName;

        std::vector<plKey>  &fFoundKeys;

    public:
    
        plKeyFinderIterator( UInt16 classType, const char *obName, std::vector<plKey>& foundKeys ) 
                : fClassType( classType ), fObjName( obName ), fFoundKeys( foundKeys ) { }

        virtual hsBool  EatKey( const plKey& key )
        {
            if( key->GetUoid().IsValid() && key->GetUoid().GetClassType() == fClassType &&
                strstr( key->GetUoid().GetObjectName(), fObjName ) )
            {
                fFoundKeys.push_back( key );
            }

            return true;
        }

        virtual hsBool EatPage( plRegistryPageNode *page )
        {
            hsBool ret = page->IterateKeys( this );
            return ret;
        }
};

void plKeyFinder::ReallyStupidSubstringSearch(const char *name, UInt16 objType, std::vector<plKey>& foundKeys, const plLocation &hintLocation )
{
    if (!name)
        return;

    plKeyFinderIterator collector( objType, name, foundKeys );
    if( hintLocation.IsValid() )
    {
        plRegistryPageNode *hintPage = IGetResMgr()->FindPage( hintLocation );
        if( hintPage != nil )
        {
            // Try all pages in the same age as that page
            IGetResMgr()->IteratePages( &collector, hintPage->GetPageInfo().GetAge() );
        }

        if (foundKeys.size() > 0)
        {
            return;
        }
    }

    //fpReg->IterateKeys( &collector );
    IGetResMgr()->IterateAllPages( &collector );
}

//// Helper Class for FindSceneNodeKey ///////////////////////////////////////

class plPageFinder : public plRegistryPageIterator
{
    protected:

        plRegistryPageNode  **fPagePtr;
        const char          *fFindString, *fAgeString;

    public:

        plPageFinder( plRegistryPageNode **page, const char *find ) : fPagePtr( page ), fFindString( find ), fAgeString( nil )
        { *fPagePtr = nil; }

        plPageFinder( plRegistryPageNode **page, const char *ageS, const char *pageS ) : fPagePtr( page ), fFindString( pageS ), fAgeString( ageS )
        { *fPagePtr = nil; }

        virtual hsBool  EatPage( plRegistryPageNode *node )
        {
            static char         str[ 512 ];
            const plPageInfo    &info = node->GetPageInfo();

            // Are we searching by age/page?
            if( fAgeString != nil )
            {
                if( stricmp( info.GetAge(), fAgeString ) == 0 && 
                    stricmp( info.GetPage(), fFindString ) == 0 )
                {
                    *fPagePtr = node;
                    return false;
                }
                return true;
            }

            // Try for page only
            if( stricmp( info.GetPage(), fFindString ) == 0 )
            {
                *fPagePtr = node;
                return false;
            }

            // Try for full location
            sprintf( str, "%s_%s_%s", info.GetAge(), info.GetPage() );
            if( stricmp( str, fFindString ) == 0 )
            {
                *fPagePtr = node;
                return false;
            }

            return true;    // Keep searching
        }
};

//// FindSceneNodeKey ////////////////////////////////////////////////////////
//  Given a string for either a page name or a fully qualified location name,
//  finds the page and then returns the key for the sceneNode in that page.
//  Note: in our case, we want to force the page's keys to load if necessary,
//  since the only time we call this function will be to actually load
//  the darned thing.

plKey   plKeyFinder::FindSceneNodeKey( const char *pageOrFullLocName ) const
{
    plRegistryPageNode  *pageNode;
    plPageFinder        pageFinder( &pageNode, pageOrFullLocName );

    
    // Use our own page finder, since we want to do nifty things like partial
    // matches, etc.
    if( IGetResMgr()->IterateAllPages( &pageFinder ) || pageNode == nil )
        return nil;

    return IFindSceneNodeKey( pageNode );
}

//// FindSceneNodeKey ////////////////////////////////////////////////////////
//  Age/page pair version

plKey   plKeyFinder::FindSceneNodeKey( const char *ageName, const char *pageName ) const
{
    plRegistryPageNode  *pageNode;
    plPageFinder        pageFinder( &pageNode, ageName, pageName );

    // Use our own page finder, since we want to do nifty things like partial
    // matches, etc.
    if (IGetResMgr()->IterateAllPages(&pageFinder) || pageNode == nil)
        return nil;

    return IFindSceneNodeKey( pageNode );
}

//// FindSceneNodeKey ////////////////////////////////////////////////////////
//  plLocation version 

plKey plKeyFinder::FindSceneNodeKey(const plLocation& location) const
{
    plRegistryPageNode* pageNode = IGetResMgr()->FindPage(location);
    if (pageNode == nil)
        return nil;

    return IFindSceneNodeKey(pageNode);
}

//// IFindSceneNodeKey ///////////////////////////////////////////////////////


plKey plKeyFinder::IFindSceneNodeKey(plRegistryPageNode* page) const
{
    // Got the pageNode, try a find before loading
    plRegistryKeyList* keyList = page->IGetKeyList(CLASS_INDEX_SCOPED(plSceneNode));
    if (keyList)
    {
        if (keyList->fStaticKeys.size() == 1)
        {
            return plKey::Make((plKeyData*)keyList->fStaticKeys[0]);
        }
        else if (keyList->fDynamicKeys.size() == 1) // happens during export
        {
            plRegistryKeyList::DynSet::const_iterator it = keyList->fDynamicKeys.begin();
            plKeyImp* keyImp = *it;
            return plKey::Make(keyImp);
        }
    }

    // Try loading and see if that helps
    if (page->IsFullyLoaded())
        return nil;

    IGetResMgr()->LoadPageKeys(page);

    // Get the list of all sceneNodes
    plKey retVal(nil);
    keyList = page->IGetKeyList(CLASS_INDEX_SCOPED(plSceneNode));
    if (keyList && keyList->fStaticKeys.size() == 1)
    {
        retVal = plKey::Make((plKeyData*)keyList->fStaticKeys[0]);
    }
    // If we just loaded up all the keys for this page, then we
    // may have a bunch of keys with a refcount of 0. For any of 
    // these keys that nothing else refs (yes, we have unused objects
    // in the data), they don't get deleted because the refcount never
    // rises above zero or falls back to zero. So we'll go ahead and
    // ref and unref all of them. The ones in use stay put, the ones
    // not being used go away. This is less than ideal.
    IGetResMgr()->DumpUnusedKeys(page);

    return retVal;
}

//// FindLocation ////////////////////////////////////////////////////////////

const plLocation    &plKeyFinder::FindLocation( const char *age, const char *page ) const
{
    if (age == nil)
    {
        static plLocation   invalidLoc;
        plRegistryPageNode  *pageNode;
        plPageFinder        pageFinder( &pageNode, page );

        if( IGetResMgr()->IterateAllPages( &pageFinder ) || pageNode == nil )
            return invalidLoc;

        return pageNode->GetPageInfo().GetLocation();
    }

    return IGetResMgr()->FindLocation( age, page );
}

//// GetLocationInfo /////////////////////////////////////////////////////////

const plPageInfo* plKeyFinder::GetLocationInfo( const plLocation &loc ) const
{
    plRegistryPageNode *node = IGetResMgr()->FindPage( loc );
    if (node)
        return &node->GetPageInfo();
    else
        return nil;
}