/*==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 "plPythonSDLModifier.h"
#include "cyPythonInterface.h"

#include "plPythonFileMod.h"
#include "pyKey.h"
#include "cyMisc.h"

#include "pnSceneObject/plSceneObject.h"
#include "plResMgr/plKeyFinder.h"
#include "plAgeDescription/plAgeDescription.h"

#include "plSDL/plSDL.h"
#include "pnNetCommon/plNetApp.h"
#include "plNetClient/plNetClientMgr.h"

plStateDataRecord * GetAgeSDL()
{
    const plPythonSDLModifier * mod = plPythonSDLModifier::FindAgeSDL();
    if ( mod )
        return mod->GetStateCache();
    return nil;
}


#define PyLoggedAssert(cond, text)                  \
    if (!cond) PythonInterface::WriteToLog(text);   \
    hsAssert(cond, text);

plPythonSDLModifier::plPythonSDLModifier(plPythonFileMod* owner) : fOwner(owner)
{
    plStateDescriptor* desc = plSDLMgr::GetInstance()->FindDescriptor(fOwner->fPythonFile, plSDL::kLatestVersion);
    if (desc)
    {
        // Create a Python SDL record with no values set
        int numVars = desc->GetNumVars();
        for (int i = 0; i < numVars; i++)
        {
            plVarDescriptor* var = desc->GetVar(i);

            const char* name = var->GetName();
            int count = var->GetCount();

            fMap[name] = SDLObj(nil, count, false);
        }
    }
}

plPythonSDLModifier::~plPythonSDLModifier()
{
    SDLMap::iterator it;
    for (it = fMap.begin(); it != fMap.end(); it++)
    {
        PyObject* obj = it->second.obj;
        Py_XDECREF(obj);
    }
}

PyObject* plPythonSDLModifier::GetItem(const char* key)
{
    SDLMap::iterator it = fMap.find(key);

    if (it == fMap.end())
    {
        char errmsg[256];
        sprintf(errmsg,"SDL key %s not found",key);
        PyErr_SetString(PyExc_KeyError, errmsg);
        PYTHON_RETURN_ERROR;
    }

    PyObject* val = it->second.obj;
    if (!val)
        val = PyTuple_New(0);

    Py_INCREF(val);
    return val;
}

void plPythonSDLModifier::ISetItem(const char* key, PyObject* value)
{
    if (!value || !PyTuple_Check(value))
    {
        PyLoggedAssert(0, "[SDL] Trying to set a tuple value to something that isn't a tuple");
        return;
    }

    SDLMap::iterator it = fMap.find(key);

    if (it == fMap.end())
    {
        PyLoggedAssert(0, "[SDL] Tried to set a nonexistent SDL value");
        return;
    }

    SDLObj& oldObj = it->second;

    if (oldObj.size != 0 && PyTuple_Size(value) != oldObj.size)
    {
        PyLoggedAssert(0, "[SDL] Wrong size for a fixed size SDL value");
        return;
    }

    Py_XDECREF(oldObj.obj);

    Py_XINCREF(value);
    oldObj.obj = value;
}

void plPythonSDLModifier::SendToClients(const char* key)
{
    SDLMap::iterator it = fMap.find(key);
    if (it != fMap.end())
        it->second.sendToClients = true;
}

void plPythonSDLModifier::SetNotify(pyKey& selfkey, const char* key, float tolerance)
{
    AddNotifyForVar(selfkey.getKey(), key, tolerance);
}

void plPythonSDLModifier::SetItem(const char* key, PyObject* value)
{
    ISetItem(key, value);
    IDirtySynchState(key);
}

void plPythonSDLModifier::SetItemFromSDLVar(plSimpleStateVariable* var)
{
    const char* name = var->GetName();

    // Get the SDL value in Python format
    PyObject* pyVar = ISDLVarToPython(var);

    ISetItem(name, pyVar);
    Py_XDECREF(pyVar);
    // let the sender do the dirty sync state stuff
}


void plPythonSDLModifier::SetDefault(const char* key, PyObject* value)
{
    ISetItem(key, value);
}

void plPythonSDLModifier::SetItemIdx(const char* key, int idx, PyObject* value, hsBool sendImmediate)
{
    if (!value)
    {
        PyLoggedAssert(0, "[SDL] Trying to set a value to nil");
        return;
    }

    SDLMap::iterator it = fMap.find(key);

    if (it == fMap.end())
    {
        PyLoggedAssert(0, "[SDL] Tried to set a nonexistent SDL value");
        return;
    }

    PyObject* pyTuple = it->second.obj;
    int size = it->second.size;

    if (size != 0 && idx >= size)
    {
        PyLoggedAssert(0, "[SDL] Trying to resize a SDL value that can't be");
        return;
    }

    if (pyTuple && pyTuple->ob_refcnt != 1)
    {
        // others already have references to the tuple and expect it to be immutable, must make a copy
        int n = PyTuple_Size(pyTuple);
        PyObject* newTuple = PyTuple_New(n);
        for (int j = 0; j < n; j++)
        {
            PyObject* item = PyTuple_GetItem(pyTuple, j);
            Py_INCREF(item);
            PyTuple_SetItem(newTuple, j, item);
        }
        Py_DECREF(pyTuple);
        pyTuple = newTuple;
        it->second.obj = newTuple;
    }

    if (pyTuple)
    {
        if (PyTuple_Size(pyTuple) <= idx)
        {
            int oldsize = PyTuple_Size(pyTuple);
            _PyTuple_Resize(&pyTuple, idx+1);
            // initialize the tuple elements to None, because Python don't like NULLs
            int j;
            for ( j=oldsize; j<idx+1; j++ )
            {
                Py_INCREF(Py_None);
                PyTuple_SetItem(pyTuple, j, Py_None);
            }
            // _PyTuple_Resize may have changed pyTuple
            it->second.obj = pyTuple;
        }
    }
    else
    {
        int newSize = (size == 0) ? idx+1 : size;
        pyTuple = PyTuple_New(newSize);
        // initialize the tuple elements to None, because Python don't like NULLs
        int j;
        for ( j=0; j<newSize; j++ )
        {
            Py_INCREF(Py_None);
            PyTuple_SetItem(pyTuple, j, Py_None);
        }
        it->second.obj = pyTuple;
    }

    Py_XINCREF(value);  // PyTuple_SetItem doesn't increment the ref count
    PyTuple_SetItem(pyTuple, idx, value);

    IDirtySynchState(key, sendImmediate);
}


const char* plPythonSDLModifier::GetSDLName() const
{
    return fOwner->fPythonFile;
}

void plPythonSDLModifier::SetFlags(const char* name, bool sendImmediate, bool skipOwnershipCheck)
{
    SDLMap::iterator it = fMap.find(name);
    if (it != fMap.end())
    {
        it->second.sendImmediate = sendImmediate;
        it->second.skipLocalCheck = skipOwnershipCheck;
    }
}

void plPythonSDLModifier::SetTagString(const char* name, const char* tag)
{
    SDLMap::iterator it = fMap.find(name);
    if (it != fMap.end())
    {
        it->second.hintString = tag;
    }
}

void plPythonSDLModifier::ISetCurrentStateFrom(const plStateDataRecord* srcState)
{
    plStateDataRecord::SimpleVarsList vars;
    int num = srcState->GetUsedVars(&vars);

    for (int i = 0; i < num; i++)
    {
        plSimpleStateVariable* var = vars[i];

        const char* name = var->GetName();

        // Get the SDL value in Python format
        PyObject* pyVar = ISDLVarToPython(var);

        SetItem(name, pyVar);
        Py_XDECREF(pyVar);
    }

    // Notify the Python code that we updated the SDL record
    if (fOwner->fPyFunctionInstances[plPythonFileMod::kfunc_Load] != nil)
    {
        PyObject* retVal = PyObject_CallMethod(
                fOwner->fPyFunctionInstances[plPythonFileMod::kfunc_Load],
                (char*)fOwner->fFunctionNames[plPythonFileMod::kfunc_Load],
                nil);
        if (retVal == nil)
        {
#ifndef PLASMA_EXTERNAL_RELEASE
            // for some reason this function didn't, remember that and not call it again
            fOwner->fPyFunctionInstances[plPythonFileMod::kfunc_Load] = nil;
#endif //PLASMA_EXTERNAL_RELEASE
            // if there was an error make sure that the stderr gets flushed so it can be seen
            fOwner->ReportError();
        }
        Py_XDECREF(retVal);
    }

    // display any output
    fOwner->DisplayPythonOutput();
}

void plPythonSDLModifier::IPutCurrentStateIn(plStateDataRecord* dstState)
{
    SDLMap::iterator it = fMap.begin();
    for (; it != fMap.end(); it++)
    {
        IPythonVarToSDL(dstState, it->first.c_str());
    }
}

void plPythonSDLModifier::IDirtySynchState(const char* name, hsBool sendImmediate)
{
    SDLMap::iterator it = fMap.find(name);
    if (it != fMap.end())
    {
        uint32_t flags = 0;
        if (it->second.sendToClients)
            flags |= kBCastToClients;
        if (it->second.sendImmediate)
            flags |= kSendImmediately;
        if (it->second.skipLocalCheck)
            flags |= kSkipLocalOwnershipCheck;
        if (sendImmediate)
            flags |= kSendImmediately;

        GetTarget()->DirtySynchState(fOwner->fPythonFile, flags);
    }
}

bool plPythonSDLModifier::IPythonVarIdxToSDL(plSimpleStateVariable* var, int varIdx, int type, PyObject* pyVar, 
                                             const char* hintstring)
{
    switch (type)
    {
    case plVarDescriptor::kShort:
    case plVarDescriptor::kByte:
    case plVarDescriptor::kBool:
    case plVarDescriptor::kInt:
        if (PyInt_Check(pyVar))
        {
            int v = PyInt_AsLong(pyVar);
            var->Set(v, varIdx);
            if (hintstring)
                var->GetNotificationInfo().SetHintString(hintstring);
            return true;
        }
        else if (PyLong_Check(pyVar))
        {
            int v = (int)PyLong_AsLong(pyVar);
            var->Set(v, varIdx);
            if (hintstring)
                var->GetNotificationInfo().SetHintString(hintstring);
            return true;
        }

        else if (PyFloat_Check(pyVar))
        {
            int v = (int)PyFloat_AsDouble(pyVar);
            var->Set(v, varIdx);
            if (hintstring)
                var->GetNotificationInfo().SetHintString(hintstring);
            return true;
        }
        break;

    case plVarDescriptor::kFloat:
        if (PyFloat_Check(pyVar))
        {
            float v = (float)PyFloat_AsDouble(pyVar);
            var->Set(v, varIdx);
            if (hintstring)
                var->GetNotificationInfo().SetHintString(hintstring);
            return true;
        }
        // does python think that its an integer? too bad, we'll make it a float anyhow!
        else if (PyInt_Check(pyVar))
        {
            float v = (float)PyInt_AsLong(pyVar);
            var->Set(v, varIdx);
            if (hintstring)
                var->GetNotificationInfo().SetHintString(hintstring);
            return true;
        }
        break;

    case plVarDescriptor::kString32:
        if (PyString_Check(pyVar))
        {
            char* v = PyString_AsString(pyVar);
            var->Set(v, varIdx);
            if (hintstring)
                var->GetNotificationInfo().SetHintString(hintstring);
        }
        break;

    case plVarDescriptor::kKey:
        {
            pyKey* key = PythonInterface::GetpyKeyFromPython(pyVar);
            if ( key )
                var->Set(key->getKey(),varIdx);
                if (hintstring)
                    var->GetNotificationInfo().SetHintString(hintstring);
        }
        break;

    case plVarDescriptor::kDouble:
        if (PyFloat_Check(pyVar))
        {
            double v = PyFloat_AsDouble(pyVar);
            var->Set(v, varIdx);
            if (hintstring)
                var->GetNotificationInfo().SetHintString(hintstring);
            return true;
        }
        else if (PyInt_Check(pyVar))
        {
            double v = (double)PyInt_AsLong(pyVar);
            var->Set(v, varIdx);
            if (hintstring)
                var->GetNotificationInfo().SetHintString(hintstring);
            return true;
        }
        break;

    case plVarDescriptor::kAgeTimeOfDay:
        break;

    default:
        hsAssert(0, "Not supported yet");
    }

    return false;
}

void plPythonSDLModifier::IPythonVarToSDL(plStateDataRecord* state, const char* name)
{
    plSimpleStateVariable* var = state->FindVar(name);
    PyObject* pyVar = nil;
    SDLMap::iterator it = fMap.find(name);
    if (it != fMap.end())
        pyVar = it->second.obj;

    if (!var || !pyVar)
        return;

    if (PyTuple_Check(pyVar))
    {
        int count = PyTuple_Size(pyVar);
        plSimpleVarDescriptor::Type type = var->GetSimpleVarDescriptor()->GetType();

        for (int i = 0; i < count; i++)
        {
            PyObject* pyVarItem = PyTuple_GetItem(pyVar, i);
            if (pyVarItem)
                IPythonVarIdxToSDL(var, i, type, pyVarItem,it->second.hintString.c_str());
        }
    }
}

PyObject* plPythonSDLModifier::ISDLVarIdxToPython(plSimpleStateVariable* var, int type, int idx)
{
    switch (type)
    {
    case plVarDescriptor::kShort:
    case plVarDescriptor::kByte:
    case plVarDescriptor::kInt:
        {
            int v;
            var->Get(&v, idx);
            return PyInt_FromLong(v);
        }

    case plVarDescriptor::kFloat:
    case plVarDescriptor::kAgeTimeOfDay:
        {
            float v;
            var->Get(&v, idx);
            return PyFloat_FromDouble(v);
        }

    case plVarDescriptor::kBool:
        {
            bool v;
            var->Get(&v, idx);
            return PyLong_FromLong(v);
        }

    case plVarDescriptor::kString32:
        {
            char v[256];
            var->Get(v, idx);
            return PyString_FromString(v);
        }

    case plVarDescriptor::kKey:
        {
            plKey v;
            var->Get(&v, idx);
            PyObject* keyObj = pyKey::New(v);
            return keyObj;
        }

    case plVarDescriptor::kDouble:
        {
            double v;
            var->Get(&v, idx);
            return PyFloat_FromDouble(v);
        }

//  case plVarDescriptor::kStateDescriptor:
//  case plVarDescriptor::kCreatable:
//  case plVarDescriptor::kVector3:
//  case plVarDescriptor::kPoint3:
//  case plVarDescriptor::kRGB:
//  case plVarDescriptor::kRGBA:
//  case plVarDescriptor::kQuaternion:
    default:
        hsAssert(0, "Not supported yet");
    }

    PYTHON_RETURN_NONE;
}

PyObject* plPythonSDLModifier::ISDLVarToPython(plSimpleStateVariable* var)
{
    plSimpleVarDescriptor::Type type = var->GetSimpleVarDescriptor()->GetType();
    int count = var->GetCount();

    PyObject* pyTuple = PyTuple_New(count);

    for (int i = 0; i < count; i++)
    {
        PyObject* varVal = ISDLVarIdxToPython(var, type, i);
        PyTuple_SetItem(pyTuple, i, varVal);
    }

    return pyTuple;
}

bool plPythonSDLModifier::HasSDL(const char* pythonFile)
{
    return (plSDLMgr::GetInstance()->FindDescriptor(pythonFile, plSDL::kLatestVersion) != nil);
}

const plSDLModifier *ExternFindAgeSDL()
{
    return plPythonSDLModifier::FindAgeSDL();
}

const plPythonSDLModifier *ExternFindAgePySDL()
{
    return plPythonSDLModifier::FindAgeSDL();
}

const plPythonSDLModifier* plPythonSDLModifier::FindAgeSDL()
{
    const char* ageName = cyMisc::GetAgeName();

    if (strcmp(ageName, "") == 0)
        return nil; // don't have an age, probably because we're running in max?

    // find the Age Global object
    plLocation loc = plKeyFinder::Instance().FindLocation(ageName,plAgeDescription::GetCommonPage(plAgeDescription::kGlobal));
    if ( loc.IsValid() )
    {
        plUoid oid(loc,plPythonFileMod::Index(), plPythonFileMod::kGlobalNameKonstant);
        if ( oid.IsValid() )
        {
            plKey key = hsgResMgr::ResMgr()->FindKey(oid);

            plPythonFileMod *pfmod = plPythonFileMod::ConvertNoRef(key ? key->ObjectIsLoaded() : nil);
            if ( pfmod )
            {
                plPythonSDLModifier * sdlMod = pfmod->GetSDLMod();
                if(sdlMod)
                    // we found it!
                    return sdlMod;
                
                plNetClientApp::StaticErrorMsg("pfmod %s has a nil python SDL modifier for age sdl %s", 
                    pfmod->GetKeyName().s_str("?"), ageName);
            }
            else
            {
                if (!key)
                    plNetClientApp::StaticErrorMsg("nil key %s for age sdl %s", ageName, oid.StringIze().c_str());
                else
                if (!key->ObjectIsLoaded())
                    plNetClientApp::StaticErrorMsg("key %s not loaded for age sdl %s", 
                        key->GetName().s_str("?"), ageName);
                else
                if (!plPythonFileMod::ConvertNoRef(key->ObjectIsLoaded()))
                    plNetClientApp::StaticErrorMsg("key %s is not a python file mod for age sdl %s", 
                        key->GetName().s_str("?"), ageName);
            }
        }
        else
            plNetClientApp::StaticErrorMsg("Invalid plUoid for age sdl %s", ageName);
    }
    else
        plNetClientApp::StaticErrorMsg("Invalid plLocation for age sdl %s", ageName);

    // couldn't find one (maybe because we didn't look)
    return nil;
}

plKey ExternFindAgeSDLTarget()
{
    return plPythonSDLModifier::FindAgeSDLTarget();
}

plKey plPythonSDLModifier::FindAgeSDLTarget()
{

    // find the Age Global object
    plLocation loc = plKeyFinder::Instance().FindLocation(cyMisc::GetAgeName(),plAgeDescription::GetCommonPage(plAgeDescription::kGlobal));
    if ( loc.IsValid() )
    {
        plUoid oid(loc,plPythonFileMod::Index(), plPythonFileMod::kGlobalNameKonstant);
        if ( oid.IsValid() )
        {
            plKey key = hsgResMgr::ResMgr()->FindKey(oid);

            plPythonFileMod *pfmod = plPythonFileMod::ConvertNoRef(key ? key->GetObjectPtr() : nil);
            if ( pfmod )
            {
                if (pfmod->GetTarget(0))
                    return pfmod->GetTarget(0)->GetKey();
            }
        }
    }

    // couldn't find one (maybe because we didn't look)
    return nil;
}


/////////////////////////////////////////////

pySDLModifier::pySDLModifier(plPythonSDLModifier* sdlMod)
{
    fRecord = sdlMod;
}


PyObject* pySDLModifier::GetAgeSDL()
{
    const char* ageName = cyMisc::GetAgeName();
    if (strcmp(ageName, "") == 0)
        PYTHON_RETURN_NONE; // just return none if the age is blank (running in max?)

    const plPythonSDLModifier* ageSDL = plPythonSDLModifier::FindAgeSDL();
    if ( ageSDL )
    {
        return pySDLModifier::New((plPythonSDLModifier*)ageSDL);
    }

    // didn't find one, throw an exception for the python programmer to chew on
    char errmsg[256];
    sprintf(errmsg,"Age Global SDL for %s does not exist!",ageName);
    plNetClientApp::StaticErrorMsg(errmsg);
    PyErr_SetString(PyExc_KeyError, errmsg);
    PYTHON_RETURN_ERROR;
}


void pySDLModifier::SetDefault(pySDLModifier& self, std::string key, PyObject* value)
{
    self.fRecord->SetDefault(key.c_str(), value);
}

void pySDLModifier::SendToClients(pySDLModifier& self, std::string key)
{
    self.fRecord->SendToClients(key.c_str());
}

void pySDLModifier::SetNotify(pySDLModifier& self, pyKey& selfkey, std::string key, float tolerance)
{
    self.fRecord->SetNotify(selfkey, key.c_str(), tolerance);
}

PyObject* pySDLModifier::GetItem(pySDLModifier& self, std::string key)
{
    return self.fRecord->GetItem(key.c_str());
}

void pySDLModifier::SetItem(pySDLModifier& self, std::string key, PyObject* value)
{
    self.fRecord->SetItem(key.c_str(), value);
}


void pySDLModifier::SetItemIdx(pySDLModifier& self, std::string key, int idx, PyObject* value)
{
    self.fRecord->SetItemIdx(key.c_str(), idx, value);
}

void pySDLModifier::SetItemIdxImmediate(pySDLModifier& self, std::string key, int idx, PyObject* value)
{
    self.fRecord->SetItemIdx(key.c_str(), idx, value, true);
}

void pySDLModifier::SetFlags(pySDLModifier& self, const char* name, bool sendImmediate, bool skipOwnershipCheck)
{
    self.fRecord->SetFlags(name,sendImmediate,skipOwnershipCheck);
}

void pySDLModifier::SetTagString(pySDLModifier& self, const char* name, const char* tag)
{
    self.fRecord->SetTagString(name,tag);
}
