/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
// Original file Copyright Crytek GMBH or its affiliates, used under license.

#ifndef CRYINCLUDE_CRYENTITYSYSTEM_ENTITYSCRIPT_H
#define CRYINCLUDE_CRYENTITYSYSTEM_ENTITYSCRIPT_H
#pragma once

#include <IScriptSystem.h>
#include "EntityClass.h"

struct IEntityScript
{
    virtual ~IEntityScript(){}
    // Releases IEntityScript interface.
    virtual void Release() = 0;

    virtual IScriptTable* GetScriptTable() const = 0;

    virtual void GetMemoryUsage(ICrySizer* pSizer) const {}
};

struct SEntityScriptEvent
{
    string name;
    HSCRIPTFUNCTION func;
    IEntityClass::EventValueType valueType;
    unsigned char bOutput : 1;
    unsigned char bOldEvent : 1;
};

#define SCRIPT_PROPERTIES_TABLE "Properties"

//! States's standard script functions.
enum EScriptStateFunctions
{
    ScriptState_OnBeginState,
    ScriptState_OnEndState,
    ScriptState_OnUpdate,
    ScriptState_OnTimer,
    ScriptState_OnEvent,
    ScriptState_OnDamage,

    // Used by all other entities!
    ScriptState_OnEnterArea,
    ScriptState_OnLeaveArea,
    ScriptState_OnEnterNearArea,
    ScriptState_OnLeaveNearArea,
    ScriptState_OnProceedFadeArea, // should be OnMoveInsideArea
    ScriptState_OnMoveNearArea,

    // Used only by the local client entities!
    ScriptState_OnLocalClientEnterArea,
    ScriptState_OnLocalClientLeaveArea,
    ScriptState_OnLocalClientEnterNearArea,
    ScriptState_OnLocalClientLeaveNearArea,
    ScriptState_OnLocalClientProceedFadeArea, // should be OnMoveInsideArea
    ScriptState_OnLocalClientMoveNearArea,

    // Used only by audio listener entities!
    ScriptState_OnAudioListenerEnterArea,
    ScriptState_OnAudioListenerLeaveArea,
    ScriptState_OnAudioListenerEnterNearArea,
    ScriptState_OnAudioListenerLeaveNearArea,
    ScriptState_OnAudioListenerProceedFadeArea, // should be OnMoveInsideArea
    ScriptState_OnAudioListenerMoveNearArea,

    ScriptState_OnBind,
    ScriptState_OnBindThis,
    ScriptState_OnUnBind,
    ScriptState_OnUnBindThis,
    ScriptState_OnMove, //!< OnMove script callback called when an entity moves.
    ScriptState_OnCollision,
    ScriptState_OnAnimationEvent,
    ScriptState_OnPhysicsBreak,
    ScriptState_OnSoundDone,
    ScriptState_OnLevelLoaded,
    ScriptState_OnStartLevel,
    ScriptState_OnStartGame,
    ScriptState_OnHidden,
    ScriptState_OnUnhidden,
    ScriptState_OnPreparedFromPool,
    ScriptState_Last,
};

//////////////////////////////////////////////////////////////////////////
enum EScriptStates
{
    SERVER_STATE = 0,
    CLIENT_STATE = 1,
    NUM_STATES,
};

//! Structure that define current state of entity.
//! Contains pointer to script functions that implement state behaivor.
struct SScriptStateFunctions
{
    // Pointers to script state functions.
    HSCRIPTFUNCTION pFunction[ScriptState_Last];        //!< Called when entity is in contact with another entity.
};

//////////////////////////////////////////////////////////////////////////
struct SScriptState
{
    string name;
    SScriptStateFunctions* pStateFuns[NUM_STATES];

    //////////////////////////////////////////////////////////////////////////
    SScriptState();
    ~SScriptState();
    void Free(IScriptSystem* pScriptSystem);
    // Checks if client or server function in this state is implemented.
    bool IsStateFunctionImplemented(EScriptStateFunctions function) const
    {
        if (pStateFuns[SERVER_STATE] && pStateFuns[SERVER_STATE]->pFunction[function])
        {
            return true;
        }
        else if (pStateFuns[CLIENT_STATE] && pStateFuns[CLIENT_STATE]->pFunction[function])
        {
            return true;
        }
        return false;
    }
};

//////////////////////////////////////////////////////////////////////////
// Description:
//    CScriptClass object represent single entity type in script.
//////////////////////////////////////////////////////////////////////////
class CEntityScript
    : public IEntityScript
{
public:
    CEntityScript();
    ~CEntityScript();

    IScriptSystem* GetScriptSystem() const { return m_pScriptSystem; };
    IScriptTable* GetScriptTable() const override { return m_pEntityTable; };

    IScriptTable* GetPropertiesTable() const { return m_pPropertiesTable; };

    // Initialize entity script, return true if success.
    // Init does not load the script. you must also call LoadScript before using it.
    virtual bool Init(const char* sTableName, const char* sScriptFilename);

    // Initialise entity script from a (code-created) script table.
    // Allows pure-C++ entities with no script file present.
    virtual bool Init(const char* sTableName, IScriptTable* pScriptTable);
    virtual void Release() { delete this; };

    // Description:
    //    Loads the script.
    //    It is safe to call LoadScript multiple times, only first time the script will be loaded, if bForceReload is not specified.
    virtual bool LoadScript(bool bForceReload = false);

    int GetStateId(const char* sStateName) const;
    const char* GetStateName(int nStateId) const;
    SScriptState* GetState(const char* sStateName);
    SScriptState* GetState(int nStateId);

    ILINE bool ShouldExecuteCall(int state) const
    {
        if (state == CLIENT_STATE && gEnv->IsClient())
        {
            return true;
        }
        else if (state == SERVER_STATE && (m_bDefaultOnly || gEnv->bServer))
        {
            return true;
        }
        return false;
    }

    void CallStateFunction(SScriptState* pState, IScriptTable* pThis, EScriptStateFunctions function)
    {
        for (int i = 0; i < NUM_STATES; i++)
        {
            if (ShouldExecuteCall(i) && pState->pStateFuns[i] && pState->pStateFuns[i]->pFunction[function])
            {
                Script::Call(m_pScriptSystem, pState->pStateFuns[i]->pFunction[function], pThis);
            }
        }
    }
    template <class P1>
    void CallStateFunction(SScriptState* pState, IScriptTable* pThis, EScriptStateFunctions function, const P1& p1)
    {
        for (int i = 0; i < NUM_STATES; i++)
        {
            if (ShouldExecuteCall(i) && pState->pStateFuns[i] && pState->pStateFuns[i]->pFunction[function])
            {
                Script::Call(m_pScriptSystem, pState->pStateFuns[i]->pFunction[function], pThis, p1);
            }
        }
    }
    template <class P1, class P2>
    void CallStateFunction(SScriptState* pState, IScriptTable* pThis, EScriptStateFunctions function, const P1& p1, const P2& p2)
    {
        for (int i = 0; i < NUM_STATES; i++)
        {
            if (ShouldExecuteCall(i) && pState->pStateFuns[i] && pState->pStateFuns[i]->pFunction[function])
            {
                Script::Call(m_pScriptSystem, pState->pStateFuns[i]->pFunction[function], pThis, p1, p2);
            }
        }
    }
    template <class P1, class P2, class P3>
    void CallStateFunction(SScriptState* pState, IScriptTable* pThis, EScriptStateFunctions function, const P1& p1, const P2& p2, const P3& p3)
    {
        for (int i = 0; i < NUM_STATES; i++)
        {
            if (ShouldExecuteCall(i) && pState->pStateFuns[i] && pState->pStateFuns[i]->pFunction[function])
            {
                Script::Call(m_pScriptSystem, pState->pStateFuns[i]->pFunction[function], pThis, p1, p2, p3);
            }
        }
    }
    template <class P1, class P2, class P3, class P4>
    void CallStateFunction(SScriptState* pState, IScriptTable* pThis, EScriptStateFunctions function, const P1& p1, const P2& p2, const P3& p3, const P4& p4)
    {
        for (int i = 0; i < NUM_STATES; i++)
        {
            if (ShouldExecuteCall(i) && pState->pStateFuns[i] && pState->pStateFuns[i]->pFunction[function])
            {
                Script::Call(m_pScriptSystem, pState->pStateFuns[i]->pFunction[function], pThis, p1, p2, p3, p4);
            }
        }
    }

    void Call_OnInit(IScriptTable* pThis, bool isReload);
    void Call_OnShutDown(IScriptTable* pThis);
    void Call_OnReset(IScriptTable* pThis, bool toGame);
    void Call_OnTransformFromEditorDone(IScriptTable* pThis);

    // Load events from the entity script.
    int GetEventCount() const { return m_events.size(); };
    const SEntityScriptEvent& GetEvent(int nIndex) const { return m_events[nIndex]; };
    const SEntityScriptEvent* FindEvent(const char* sEvent) const;

    void CallEvent(IScriptTable* pThis, const char* sEvent, float fValue);
    void CallEvent(IScriptTable* pThis, const char* sEvent, double fValue);
    void CallEvent(IScriptTable* pThis, const char* sEvent, bool bValue);
    void CallEvent(IScriptTable* pThis, const char* sEvent, const char* sValue);
    void CallEvent(IScriptTable* pThis, const char* sEvent, IScriptTable* pTable);
    void CallEvent(IScriptTable* pThis, const char* sEvent, const Vec3& vValue);

private:
    void Clear();
    void EnumStates();
    void LoadEvents();
    void DelegateProperties();
    void InitializeStateTable(IScriptTable* pStateTable, SScriptStateFunctions* scriptState);
    void InitializeNamedStates(IScriptTable* pTable, int nStateNum);

    size_t CountInOutEvents(IScriptTable* pEventsTable, std::vector<SEntityScriptEvent>& events, bool bOutput);
    void ParseInOutEvents(IScriptTable* pEventsTable, std::vector<SEntityScriptEvent>& events, bool bOutput);

private:
    IScriptSystem* m_pScriptSystem;
    // Original Entity table used as an entity prototype table.
    SmartScriptTable m_pEntityTable;
    SmartScriptTable m_pPropertiesTable;

    string m_sTableName;
    string m_sScriptFilename;

    HSCRIPTFUNCTION m_pOnSpawnFunc;
    HSCRIPTFUNCTION m_pOnDestroyFunc;
    HSCRIPTFUNCTION m_pOnInitFunc[NUM_STATES];
    HSCRIPTFUNCTION m_pOnShutDown[NUM_STATES];
    HSCRIPTFUNCTION m_pOnReset;
    HSCRIPTFUNCTION m_pOnTransformFromEditorDone;

    // Default state.
    SScriptState m_defaultState;

    // List of all available states in the script.
    std::vector<SScriptState> m_states;

    std::vector<SEntityScriptEvent> m_events;

    bool m_bScriptLoaded;
    bool m_bDefaultOnly;
};


#endif // CRYINCLUDE_CRYENTITYSYSTEM_ENTITYSCRIPT_H