/*
===========================================================================
Copyright (C) 2024 the OpenMoHAA team

This file is part of OpenMoHAA source code.

OpenMoHAA source code 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 2 of the License,
or (at your option) any later version.

OpenMoHAA source code 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 OpenMoHAA source code; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
===========================================================================
*/

// scriptclass.h: Script class.

#include "scriptclass.h"
#include "../fgame/scriptmaster.h"
#include "../fgame/scriptthread.h"
#include "scriptexception.h"

//====================
// ScriptClass
//====================

MEM_BlockAlloc<ScriptClass> ScriptClass_allocator;

CLASS_DECLARATION(Listener, ScriptClass, NULL) {
    {NULL, NULL}
};

/*
====================
new ScriptClass
====================
*/
void *ScriptClass::operator new(size_t size)
{
    return ScriptClass_allocator.Alloc();
}

/*
====================
delete ptr
====================
*/
void ScriptClass::operator delete(void *ptr)
{
    ScriptClass_allocator.Free(ptr);
}

/*
====================
ScriptClass
====================
*/
ScriptClass::ScriptClass(GameScript *gameScript, Listener *self)
{
    m_Self    = self;
    m_Script  = gameScript;
    m_Threads = NULL;
}

/*
====================
ScriptClass
====================
*/
ScriptClass::ScriptClass()
{
    m_Self    = NULL;
    m_Script  = NULL;
    m_Threads = NULL;
}

/*
====================
~ScriptClass
====================
*/
ScriptClass::~ScriptClass()
{
    if (m_Script == NULL) {
        //throw ScriptException("Attempting to delete dead class.");
        gi.Error(ERR_DROP, "Attempting to delete a dead thread.");
    }

    KillThreads();

    if (!m_Script->m_Filename) {
        // This is a temporary gamescript
        delete m_Script;
    }
}

/*
====================
StoppedNotify
====================
*/
void ScriptClass::StoppedNotify()
{
    delete this;
}

/*
====================
Archive
====================
*/
void ScriptClass::Archive(Archiver& arc) {}

/*
====================
ArchiveInternal
====================
*/
void ScriptClass::ArchiveInternal(Archiver& arc)
{
    Listener::Archive(arc);

    arc.ArchiveObjectPosition(this);
    arc.ArchiveSafePointer(&m_Self);
    GameScript::Archive(arc, m_Script);
}

/*
====================
ArchiveScript
====================
*/
void ScriptClass::ArchiveScript(Archiver& arc, ScriptClass **obj)
{
    ScriptClass  *scr;
    ScriptVM     *m_current;
    ScriptThread *m_thread;
    int           num;
    int           i;

    if (arc.Saving()) {
        scr = *obj;
        scr->ArchiveInternal(arc);

        num = 0;
        for (m_current = scr->m_Threads; m_current != NULL; m_current = m_current->next) {
            num++;
        }

        arc.ArchiveInteger(&num);

        for (m_current = scr->m_Threads; m_current != NULL; m_current = m_current->next) {
            m_current->m_Thread->ArchiveInternal(arc);
        }
    } else {
        scr = new ScriptClass();
        scr->ArchiveInternal(arc);

        arc.ArchiveInteger(&num);

        for (i = 0; i < num; i++) {
            m_thread = new ScriptThread(scr, NULL);
            m_thread->ArchiveInternal(arc);
        }

        *obj = scr;
    }
}

/*
====================
ArchiveCodePos
====================
*/
void ScriptClass::ArchiveCodePos(Archiver& arc, unsigned char **codePos)
{
    m_Script->ArchiveCodePos(arc, codePos);
}

/*
====================
CreateThreadInternal
====================
*/
ScriptThread *ScriptClass::CreateThreadInternal(const ScriptVariable& label)
{
    GameScript   *scr;
    ScriptThread *thread = NULL;

    if (label.GetType() == VARIABLE_STRING || label.GetType() == VARIABLE_CONSTSTRING) {
        if (label.GetType() == VARIABLE_CONSTSTRING) {
            thread = Director.CreateScriptThread(this, label.constStringValue());
        } else {
            thread = Director.CreateScriptThread(this, label.stringValue());
        }
    } else if (label.GetType() == VARIABLE_CONSTARRAY && label.arraysize() > 1) {
        ScriptVariable *script    = label[1];
        ScriptVariable *labelname = label[2];

        if (script->GetType() == VARIABLE_CONSTSTRING) {
            scr = Director.GetGameScript(script->constStringValue());
        } else {
            scr = Director.GetGameScript(script->stringValue());
        }

        if (labelname->GetType() == VARIABLE_CONSTSTRING) {
            thread = Director.CreateScriptThread(scr, GetSelf(), labelname->constStringValue());
        } else {
            thread = Director.CreateScriptThread(scr, GetSelf(), labelname->stringValue());
        }
    } else {
        ScriptError("ScriptClass::CreateThreadInternal: bad argument format");
    }

    return thread;
}

/*
====================
CreateScriptInternal
====================
*/
ScriptThread *ScriptClass::CreateScriptInternal(const ScriptVariable& label)
{
    GameScript   *scr;
    ScriptThread *thread = NULL;

    if (label.GetType() == VARIABLE_STRING || label.GetType() == VARIABLE_CONSTSTRING) {
        if (label.GetType() == VARIABLE_CONSTSTRING) {
            scr = Director.GetGameScript(label.constStringValue());
        } else {
            scr = Director.GetGameScript(label.stringValue());
        }
        thread = Director.CreateScriptThread(scr, GetSelf(), "");
    } else if (label.GetType() == VARIABLE_CONSTARRAY && label.arraysize() > 1) {
        ScriptVariable *script    = label[1];
        ScriptVariable *labelname = label[2];

        if (script->GetType() == VARIABLE_CONSTSTRING) {
            scr = Director.GetGameScript(script->constStringValue());
        } else {
            scr = Director.GetGameScript(script->stringValue());
        }

        if (labelname->GetType() == VARIABLE_CONSTSTRING) {
            thread = Director.CreateScriptThread(scr, GetSelf(), labelname->constStringValue());
        } else {
            thread = Director.CreateScriptThread(scr, GetSelf(), labelname->stringValue());
        }
    } else {
        ScriptError("ScriptClass::CreateScriptInternal: bad label type '%s'", label.GetTypeName());
    }

    return thread;
}

/*
====================
AddThread
====================
*/
void ScriptClass::AddThread(ScriptVM *thread)
{
    thread->next = m_Threads;
    m_Threads    = thread;
}

/*
====================
KillThreads
====================
*/
void ScriptClass::KillThreads()
{
    ScriptVM *thread;
    ScriptVM *next;

    if (!m_Threads) {
        return;
    }

    for (thread = m_Threads; thread; thread = next) {
        if (g_scripttrace->integer && thread->CanScriptTracePrint()) {
            gi.DPrintf2("---KILLTHREADS THREAD: %p\n", thread);
        }

        thread->m_ScriptClass = NULL;
        next                  = thread->next;
        delete thread->m_Thread;
    }

    m_Threads = NULL;
}

/*
====================
RemoveThread
====================
*/
void ScriptClass::RemoveThread(ScriptVM *thread)
{
    ScriptVM *current;
    ScriptVM *next;

    if (m_Threads == thread) {
        m_Threads = thread->next;

        if (!m_Threads) {
            delete this;
        }
    } else {
        next = m_Threads;
        for (current = m_Threads->next; current != thread; current = current->next) {
            next = current;
        }

        next->next = current->next;
    }
}

/*
====================
Filename
====================
*/
str ScriptClass::Filename()
{
    return m_Script->Filename();
}

/*
====================
FindLabel
====================
*/
unsigned char *ScriptClass::FindLabel(str label)
{
    return m_Script->m_State.FindLabel(label);
}

/*
====================
FindLabel
====================
*/
unsigned char *ScriptClass::FindLabel(const_str label)
{
    return m_Script->m_State.FindLabel(label);
}

/*
====================
NearestLabel
====================
*/
const_str ScriptClass::NearestLabel(unsigned char *pos)
{
    return m_Script->m_State.NearestLabel(pos);
}

/*
====================
GetCatchStateScript
====================
*/
StateScript *ScriptClass::GetCatchStateScript(unsigned char *in, unsigned char *& out)
{
    return m_Script->GetCatchStateScript(in, out);
}

/*
====================
GetScript
====================
*/
GameScript *ScriptClass::GetScript()
{
    return m_Script;
}

/*
====================
GetSelf
====================
*/
Listener *ScriptClass::GetSelf()
{
    return static_cast<Listener *>(m_Self.Pointer());
}
