/*
 * mathscript.cpp by Keith Fulton <keith@planeshift.it>
 *
 * Copyright (C) 2004 Atomic Blue (info@planeshift.it, http://www.atomicblue.org)
 *
 *
 * 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 (version 2 of the License)
 * 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, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 *
 */
#include <psconfig.h>

#if defined(CS_EXTENSIVE_MEMDEBUG) || defined(CS_MEMORY_TRACKER)
# undef new
# if defined(CS_EXTENSIVE_MEMDEBUG)
#  undef CS_EXTENSIVE_MEMDEBUG
# endif
# if defined(CS_MEMORY_TRACKER)
#  undef CS_MEMORY_TRACKER
# endif
#endif
#include <new>

#include <ctype.h>

#include "util/psstring.h"
#include "util/log.h"
#include <csutil/array.h>
#include <csutil/hash.h>
#include <csutil/xmltiny.h>
#include <iutil/vfs.h>

#include "util/fparser.h"
#include "util/mathscript.h"
#include "util/strutil.h"
#include "util/consoleout.h"

#include "../../server/bulkobjects/pscharacter.h"
#include "../../server/bulkobjects/psitem.h"

MathScriptLine::MathScriptLine(const char *line,MathScript *myScript)
{
    scriptLine = line;
    csString var = GetWordNumber(scriptLine,1);
    assignee = myScript->GetOrCreateVar(var);
    valid = true;

    var_array = NULL;
    size_t start = scriptLine.FindFirst('=');
    scriptLine.DeleteAt(0,start+1);

    fp.AddFunction("rnd", MathScriptEngine::RandomGen, 1);
    fp.AddFunction("pow", MathScriptEngine::Power, 2);
    fp.AddFunction("getSkillValue", MathScriptEngine::GetSkillValue, 2);
    fp.AddFunction("getAverageSkillValue", MathScriptEngine::GetAverageSkillValue, 4);
    fp.AddFunction("getStatValue", MathScriptEngine::GetStatValue, 2);
    fp.AddFunction("getArmorVSWeaponResistance", MathScriptEngine::GetArmorVSWeaponResistance, 2);

    ParseVariables(myScript);
    ParseFormula(myScript);
}

MathScriptLine::~MathScriptLine()
{
    if (var_array)
        delete[] var_array;
}

MathScriptVar *MathScriptLine::FindVariable(const char *name)
{
    size_t i;
    for (i=0; i<variables.Length(); i++)
    {
        if (variables[i]->name == name)
            return variables[i];
    }
    return NULL;
}

void MathScriptLine::ParseVariables(MathScript *myScript)
{
    size_t start=0;
    psString word;

    while ( start < scriptLine.Length() )
    {
        scriptLine.GetWord(start,word,false);
        if (!word.Length())
        {
            start++;
            continue;
        }
        if (isupper(word.GetAt(0))) // capital first letter means var name
        {
            // A compound var function starts as Target:WeaponAt(Slot)
            // and turns into WeaponAt(Target,Slot)
            // A compound var turns Target::HP into Target_HP
            if (scriptLine.Length() > start+word.Length() && scriptLine[start+word.Length()] == ':')  // compound var name
            {
                MathScriptVar *base = FindVariable(word);
                // get core variable onto list
                if (!base) // unique list
                {
                    base = myScript->GetOrCreateVar(word);
                    variables.Push( base );
                }
                base->type = MathScriptVar::VARTYPE_PTR;
                psString extend;
                scriptLine.GetWord(start+word.Length()+2,extend,false);

                if (scriptLine.Length() > start+word.Length()+extend.Length()+1 && scriptLine[start+word.Length()+1+extend.Length()] == '(')
                {
                    scriptLine.DeleteAt(start,word.Length()+1);
                    word.Append(',');
                    scriptLine.Insert(start+extend.Length()+1,word);
                    word = "";
                }
                else
                {
                    MathScriptVar *var = myScript->GetOrCreateVar(word); // get underlying var

                    scriptLine.SetAt(start+word.Length(),'_');
                 
                    word.Append('_');
                    word.Append(extend);
        
                    if (!FindVariable(word)) // unique list
                    {
                        MathScriptVar *ext = myScript->GetOrCreateVar(word);
                        variables.Push( ext );
                        ext->property = extend;
                        ext->SetVariable(var);
                    }
                }
            }
            else if (!FindVariable(word)) // unique list
                variables.Push( myScript->GetOrCreateVar(word) );
        }
        start += word.Length();
    }
    if (variables.Length())
        var_array = new double[variables.Length()];
}

void MathScriptLine::ParseFormula(MathScript *myScript)
{
    csString varlist;
    varlist = "";
    if (variables.Length())
    {
        varlist = variables[0]->name;
        size_t i;
        for (i=1; i<variables.Length(); i++)
        {
            varlist.Append(',');
            varlist.Append(variables[i]->name);
        }
    }
    size_t ret = fp.Parse( scriptLine.GetData(), varlist.GetData() );
    if (ret != (size_t)-1)
    {
        valid=false;
        printf( "Caller: %s", myScript->name.GetData());
        Error4("Error in Col %d: %s\n Script: \"%s\" ",ret,fp.ErrorMsg(),scriptLine.GetData() );
    }
    else
        fp.Optimize();
}

void MathScriptLine::Execute()
{
    if (!valid)
    {
        Error2("Attempted to execute bad Mathscript line (%s).\n",scriptLine.GetData());
        assignee->SetValue(0);
        return;
    }

    size_t i;
    for (i=0; i<variables.Length(); i++)
        var_array[i] = variables[i]->GetValue();

    assignee->SetValue( fp.Eval(var_array) );
}




MathScript::MathScript(iDocumentNode *node)
{
    name = node->GetAttributeValue("name");
    ParseScript( node->GetContentsValue() );
}

MathScript::MathScript(const char *myname,const char *script)
{
    name = myname;
    ParseScript(script);
}

void MathScript::ParseScript(const char *parsescript)
{
    psString script(parsescript);
    uintptr_t  i,line_start = 0;

    while (line_start < script.Length() )
    {
        i = script.FindFirst(';',line_start);
        if (i==SIZET_NOT_FOUND)
        {
            i = script.Length();
        }
        psString line;
        if (i-line_start > 0)
        {

            script.SubString(line,line_start,i-line_start);

            // avoids empty lines (spaces, tabs, carriage returns)
            line.ReplaceAllSubString("\t"," ");
            line.ReplaceAllSubString("\r"," ");
            line.ReplaceAllSubString("\n"," ");
            line = line.Trim();
            if (!line.IsEmpty()) {
                MathScriptLine *newline = new MathScriptLine(line,this);
                scriptLines.Push(newline);
            }
        }
        line_start = i+1;
    }
}

MathScript::~MathScript()
{
    while (scriptLines.Length())
        delete scriptLines.Pop();

    csHash<MathScriptVar *>::GlobalIterator it (variables.GetIterator ());
    while (it.HasNext ())
    {
        MathScriptVar* var = it.Next ();
        delete var;
    }
}

MathScriptVar *MathScript::GetVar(const char *name)
{
    unsigned int key = csHashCompute(name);
    csHash<MathScriptVar*>::Iterator iter = variables.GetIterator(key);
    while (iter.HasNext())
    {
        MathScriptVar *ptr = iter.Next();
        if (ptr->name == name)
            return ptr;
    }
    return NULL;
}

MathScriptVar *MathScript::GetOrCreateVar(const char *name)
{
    MathScriptVar *var = GetVar(name);
    if (var)
        return var;

    // CPrintf(CON_DEBUG, "Creating var <%s> in script <%s>\n",name,this->name.GetData() );

    unsigned int key = csHashCompute(name);
    var = new MathScriptVar;
    var->name = name;
    var->SetValue(0);

    variables.Put(key,var);
    return var;
}

void MathScript::Execute()
{
    size_t i;
    MathScriptVar *exitsignal = GetVar("exit");

    if (exitsignal)
        exitsignal->SetValue(0); // clear exit condition before running

    for (i=0; i<scriptLines.Length(); i++)
    {
        scriptLines[i]->Execute();
        if (exitsignal && exitsignal->GetValue()!=0)
        {
            // printf("Terminating mathscript at line %d of %d.\n",i, scriptLines.Length());
            break;
        }
    }
}


void MathScript::DumpAllVars()
{
    csHash<MathScriptVar*>::GlobalIterator iter = variables.GetIterator();

    CPrintf(CON_DEBUG, "\nAll vars for '%s'\n-----------------------------------------\n",
            name.GetData());
    while (iter.HasNext())
    {
        MathScriptVar *var = iter.Next();
        CPrintf(CON_DEBUG, "%25s=%1.4f\n",var->name.GetData(),var->GetValue() );
    }
}

MathScriptEngine::MathScriptEngine(const char *xmlfile,iVFS *vfs)
{
    csRef<iDocumentSystem> xml = csPtr<iDocumentSystem>(new csTinyDocumentSystem);

    csRef<iDataBuffer> buff = vfs->ReadFile( xmlfile );
    if ( !buff || !buff->GetSize() )
    {
        return;
    }

    csRef<iDocument> doc = xml->CreateDocument();
    const char* error = doc->Parse( buff );
    if ( error )
    {
        return;
    }
    csRef<iDocumentNode> root    = doc->GetRoot();
    if (!root)
        return;

    csRef<iDocumentNode> topNode = root->GetNode("mathscript");
    if (!topNode)
        return;
    csRef<iDocumentNodeIterator> iter = topNode->GetNodes();

    while ( iter->HasNext() )
    {
        csRef<iDocumentNode> node = iter->Next();

        if (node->GetType() != CS_NODE_ELEMENT )
            continue;

        // This is a widget so read it's factory to create it.
        if ( strcmp( node->GetValue(), "script" ) == 0 )
        {
            MathScript *scr = new MathScript(node);
            unsigned int key = csHashCompute(scr->name);
            scripts.Put(key,scr);
        }
    }
}

MathScriptEngine::~MathScriptEngine()
{
    csHash<MathScript *>::GlobalIterator it (scripts.GetIterator ());
    while (it.HasNext ())
    {
        MathScript* script = it.Next ();
        delete script;
    }
    scripts.DeleteAll();
}

MathScript *MathScriptEngine::FindScript(const char *name)
{
    unsigned int key = csHashCompute(name);
    csHash<MathScript*>::Iterator iter = scripts.GetIterator(key);
    while (iter.HasNext())
    {
        MathScript *ptr = iter.Next();
        if (ptr->name == name)
            return ptr;
    }
    return NULL;
}

csRandomGen MathScriptEngine::rng;

double MathScriptEngine::RandomGen(const double *dummy)
{
    return MathScriptEngine::rng.Get();
}

double MathScriptEngine::Power(const double *parms)
{
    return pow(parms[0],parms[1]);
}

double MathScriptEngine::GetSkillValue(const double *parms)
{
    psCharacter *my_char = (psCharacter *)(intptr_t)parms[0];
    PSSKILL skill = (PSSKILL)(int)parms[1];

    double value = my_char->GetSkills()->GetSkillRank(skill);

    // always give a small % of melee (unharmed) skill
    if (skill==PSSKILL_MARTIALARTS && value==0)
        value = 0.2;

    return value;
}

double MathScriptEngine::GetAverageSkillValue(const double *parms)
{
    psCharacter *my_char = (psCharacter *)(intptr_t)parms[0];
    PSSKILL skill1 = (PSSKILL)(int)parms[1];
    PSSKILL skill2 = (PSSKILL)(int)parms[2];
    PSSKILL skill3 = (PSSKILL)(int)parms[3];

    double v1 = my_char->GetSkills()->GetSkillRank(skill1);

    if (skill2!=PSSKILL_NONE) {
        double v2 = my_char->GetSkills()->GetSkillRank(skill2);
        v1 = (v1+v2)/2;
    }

    if (skill3!=PSSKILL_NONE) {
        double v3 = my_char->GetSkills()->GetSkillRank(skill3);
        v1 = (v1+v3)/2;
    }

    // always give a small % of combat skill, or players will never be able to get the first exp
    if (v1==0)
        v1 = 0.7;

    return v1;
}

double MathScriptEngine::GetStatValue(const double *parms)
{
    psCharacter *my_char = (psCharacter *)(intptr_t)parms[0];
    PSITEMSTATS_STAT stat = (PSITEMSTATS_STAT)(int)parms[1];

    return (double)my_char->GetAttributes()->GetStat(stat);
}

double MathScriptEngine::GetArmorVSWeaponResistance(const double *parms)
{
    psItem *weapon = (psItem *)(intptr_t)parms[0];
    psItem *armor  = (psItem *)(intptr_t)parms[1];

    // if no armor return 1
    if (!armor)
        return 1.0F;

    return weapon->GetArmorVSWeaponResistance(armor->GetCurrentStats());
}

