/*==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 "plRegistryNode.h"
#include "plRegistryKeyList.h"
#include "plRegistryHelpers.h"

#include "pnKeyedObject/plKeyImp.h"
#include "plStatusLog/plStatusLog.h"
#include "pnFactory/plFactory.h"
#include "plFile/plFileUtils.h"
#include "hsStlUtils.h"

#include "plVersion.h"

plRegistryPageNode::plRegistryPageNode(const char* path)
    : fValid(kPageCorrupt)
    , fPath(nil)
    , fDynLoadedTypes(0)
    , fStaticLoadedTypes(0)
    , fOpenRequests(0)
    , fIsNewPage(false)
{
    fPath = hsStrcpy(path);

    hsStream* stream = OpenStream();
    if (stream)
    {
        fPageInfo.Read(&fStream);
        fValid = IVerify();
        CloseStream();
    }
}

plRegistryPageNode::plRegistryPageNode(const plLocation& location, const char* age, const char* page, const char* dataPath)
    : fValid(kPageOk)
    , fPath(nil)
    , fPageInfo(location)
    , fDynLoadedTypes(0)
    , fStaticLoadedTypes(0)
    , fOpenRequests(0)
    , fIsNewPage(true)
{
    fPageInfo.SetStrings(age, page);

    char filePath[512];

    // Copy the path over
    strncpy(filePath, dataPath, sizeof(filePath));
    plFileUtils::AddSlash(filePath);

    // Time to construct our actual file name. For now, we'll use the same old format
    // of age_page.extension
    strncat(filePath, fPageInfo.GetAge(), sizeof(filePath));
    strncat(filePath, "_District_", sizeof(filePath));
    strncat(filePath, fPageInfo.GetPage(), sizeof(filePath));
    strncat(filePath, ".prp", sizeof(filePath));

    fPath = hsStrcpy(filePath);
}

plRegistryPageNode::~plRegistryPageNode()
{
    delete [] fPath;
    UnloadKeys();
}

PageCond plRegistryPageNode::IVerify()
{
    // Check the checksum values first, to make sure the files aren't corrupt
    UInt32 ourChecksum = 0;
    hsStream* stream = OpenStream();
    if (stream)
    {
        ourChecksum = stream->GetEOF() - fPageInfo.GetDataStart();
        CloseStream();
    }
    if (ourChecksum != fPageInfo.GetChecksum())
        return kPageCorrupt;

    // If major version out-of-date, entire location is screwed
    if (fPageInfo.GetMajorVersion() > plVersion::GetMajorVersion())
        return kPageTooNew;
    else if (fPageInfo.GetMajorVersion() < plVersion::GetMajorVersion())
        return kPageOutOfDate;

    // Check the minor versions
    const plPageInfo::ClassVerVec& classVersions = fPageInfo.GetClassVersions();
    for (int i = 0; i < classVersions.size(); i++)
    {
        const plPageInfo::ClassVersion& cv = classVersions[i];
        UInt16 curVersion = plVersion::GetCreatableVersion(cv.Class);

        if (curVersion > cv.Version)
            return kPageOutOfDate;
        else if (curVersion < cv.Version)
            return kPageTooNew;
    }

    return kPageOk;
}

hsStream* plRegistryPageNode::OpenStream()
{
    if (fOpenRequests == 0)
    {
        if (!fStream.Open(fPath, "rb"))
            return nil;
    }
    fOpenRequests++;
    return &fStream;
}

void plRegistryPageNode::CloseStream()
{
    if (fOpenRequests > 0)
        fOpenRequests--;

    if (fOpenRequests == 0)
        fStream.Close();
}

void plRegistryPageNode::LoadKeys()
{
    hsAssert(IsValid(), "Trying to load keys for invalid page");
    hsAssert(!fIsNewPage, "Trying to read a new page");
    if (IsFullyLoaded())
        return;

    hsStream* stream = OpenStream();
    if (!stream)
    {
        hsAssert(0, xtl::format("plRegistryPageNode::LoadKeysFromSource - bad stream %s,%s", 
            GetPageInfo().GetAge(), GetPageInfo().GetPage()).c_str());
        return;
    }

    // If we're loading keys in the middle of a read because FindKey() failed, we'd better
    // make note of our stream position and restore it when we're done.
    UInt32 oldPos = stream->GetPosition();
    stream->SetPosition(GetPageInfo().GetIndexStart());

    // Read in the number of key types
    UInt32 numTypes = stream->ReadSwap32();
    for (UInt32 i = 0; i < numTypes; i++)
    {
        UInt16 classType = stream->ReadSwap16();
        plRegistryKeyList* keyList = IGetKeyList(classType);
        if (!keyList)
        {
            keyList = TRACKED_NEW plRegistryKeyList(classType);
            fKeyLists[classType] = keyList;
        }
        keyList->Read(stream);
    }

    stream->SetPosition(oldPos);
    CloseStream();
    fStaticLoadedTypes = fKeyLists.size();
}

void plRegistryPageNode::UnloadKeys()
{
    KeyMap::iterator it = fKeyLists.begin();
    for (; it != fKeyLists.end(); it++)
    {
        plRegistryKeyList* keyList = it->second;
        delete keyList;
    }
    fKeyLists.clear();

    fDynLoadedTypes = 0;
    fStaticLoadedTypes = 0;
}

//// plWriteIterator /////////////////////////////////////////////////////////
//  Key iterator for writing objects
class plWriteIterator : public plRegistryKeyIterator
{
protected:
    hsStream* fStream;

public:
    plWriteIterator(hsStream* s) : fStream(s) {}

    virtual hsBool EatKey(const plKey& key)
    {
        plKeyImp* imp = (plKeyImp*)key;
        imp->WriteObject(fStream);
        return true;
    }
};

void plRegistryPageNode::Write()
{
    hsAssert(fOpenRequests == 0, "Trying to write while the page is open for reading");

    if (!fStream.Open(fPath, "wb"))
    {
        hsAssert(0, "Couldn't open file for writing");
        return;
    }

    // Some prep stuff.  Assign object IDs for every key in this page, and put the
    // versions of all our creatable types in the pageinfo.
    fPageInfo.ClearClassVersions();

    KeyMap::const_iterator it;
    for (it = fKeyLists.begin(); it != fKeyLists.end(); it++)
    {
        plRegistryKeyList* keyList = it->second;
        keyList->PrepForWrite();

        int ver = plVersion::GetCreatableVersion(keyList->GetClassType());
        fPageInfo.AddClassVersion(keyList->GetClassType(), ver);
    }

    // First thing we write is the pageinfo.  Later we'll rewind and overwrite this with the final values
    fPageInfo.Write(&fStream);

    fPageInfo.SetDataStart(fStream.GetPosition());

    // Write all our objects
    plWriteIterator writer(&fStream);
    IterateKeys(&writer);

    fPageInfo.SetIndexStart(fStream.GetPosition());

    // Write our keys
    fStream.WriteSwap32(fKeyLists.size());
    for (it = fKeyLists.begin(); it != fKeyLists.end(); it++)
    {
        plRegistryKeyList* keyList = it->second;
        fStream.WriteSwap16(keyList->GetClassType());
        keyList->Write(&fStream);
    }

    // Rewind and write the pageinfo with the correct data and index offsets
    fStream.Rewind();
    fPageInfo.SetChecksum(fStream.GetEOF() - fPageInfo.GetDataStart());
    fPageInfo.Write(&fStream);

    fStream.Close();
}

//// IterateKeys /////////////////////////////////////////////////////////////

hsBool plRegistryPageNode::IterateKeys(plRegistryKeyIterator* iterator) const
{
    KeyMap::const_iterator it = fKeyLists.begin();
    for (; it != fKeyLists.end(); it++)
    {
        plRegistryKeyList* keyList = it->second;
        if (!keyList->IterateKeys(iterator))
            return false;
    }

    return true;
}

//// IterateKeys /////////////////////////////////////////////////////////////
//  Restricted version that only iterates through the keys of a given class
//  type.

hsBool plRegistryPageNode::IterateKeys(plRegistryKeyIterator* iterator, UInt16 classToRestrictTo) const
{
    plRegistryKeyList* keyList = IGetKeyList(classToRestrictTo);
    if (keyList != nil)
        return keyList->IterateKeys(iterator);

    return true;
}

plKeyImp* plRegistryPageNode::FindKey(UInt16 classType, const char* name) const
{
    plRegistryKeyList* keys = IGetKeyList(classType);
    if (keys == nil)
        return nil;

    return keys->FindKey(name);
}

plKeyImp* plRegistryPageNode::FindKey(const plUoid& uoid) const
{
    plRegistryKeyList* keys = IGetKeyList(uoid.GetClassType());
    if (keys == nil)
        return nil;

    return keys->FindKey(uoid);
}

void plRegistryPageNode::AddKey(plKeyImp* key)
{
    UInt16 classType = key->GetUoid().GetClassType();
    plRegistryKeyList* keys = fKeyLists[classType];
    if (keys == nil)
    {
        keys = TRACKED_NEW plRegistryKeyList(classType);
        fKeyLists[classType] = keys;
    }

    // Error check
    if (keys->FindKey(key->GetUoid().GetObjectName()) != nil)
    {
        //char str[512], tempStr[128];
        //sprintf(str, "Attempting to add a key with a duplicate name. Not allowed."
        //          "\n\n(Key name: %s, Class: %s, Loc: %s)", key->GetUoid().GetObjectName(), 
        //          plFactory::GetNameOfClass(classType), key->GetUoid().GetLocation().StringIze(tempStr));
        //hsStatusMessage(str);
        hsBool recovered = false;

        // Attempt recovery
        for (int i = 0; i < 500; i++)
        {
            char tempName[512];
            sprintf(tempName, "%s%d", key->GetUoid().GetObjectName(), i);
            if (keys->FindKey(tempName) == nil)
            {
                plUoid uoid(key->GetUoid().GetLocation(), key->GetUoid().GetClassType(), tempName, key->GetUoid().GetLoadMask());
                key->SetUoid(uoid);
                recovered = true;
                break;
            }
        }

        if (!recovered)
        {
            hsAssert(0, "Couldn't allocate a unique key");
            return;
        }
    }

    plRegistryKeyList::LoadStatus loadStatusChange;
    keys->AddKey(key, loadStatusChange);

    if (loadStatusChange == plRegistryKeyList::kDynLoaded)
        fDynLoadedTypes++;
}

void plRegistryPageNode::SetKeyUsed(plKeyImp* key)
{
    plRegistryKeyList* keys = IGetKeyList(key->GetUoid().GetClassType());
    if (keys == nil)
        return;

    keys->SetKeyUsed(key);
}

hsBool plRegistryPageNode::SetKeyUnused(plKeyImp* key)
{
    plRegistryKeyList* keys = IGetKeyList(key->GetUoid().GetClassType());
    if (keys == nil)
        return false;

    plRegistryKeyList::LoadStatus loadStatusChange;
    hsBool removed = keys->SetKeyUnused(key, loadStatusChange);

    // If the key type just changed load status, update our load counts
    if (loadStatusChange == plRegistryKeyList::kDynUnloaded)
        fDynLoadedTypes--;
    else if (loadStatusChange == plRegistryKeyList::kStaticUnloaded)
        fStaticLoadedTypes--;

    return removed;
}

plRegistryKeyList* plRegistryPageNode::IGetKeyList(UInt16 classType) const
{
    KeyMap::const_iterator it = fKeyLists.find(classType);
    if (it != fKeyLists.end())
        return it->second;

    return nil;
}

void plRegistryPageNode::DeleteSource()
{
    hsAssert(fOpenRequests == 0, "Deleting a stream that's open for reading");
    plFileUtils::RemoveFile(fPath);
}