// Emacs style mode select -*- C++ -*-
//----------------------------------------------------------------------------
//
// Copyright(C) 2000 Simon Howard
// Copyright(C) 2002-2008 Christoph Oelckers
//
// 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 2 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, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
//
//---------------------------------------------------------------------------
//

#ifndef __T_SCRIPT_H__
#define __T_SCRIPT_H__

#include "p_setup.h"
#include "p_lnspec.h"
#include "m_fixed.h"
#include "actor.h"
#include "engineerrors.h"

#ifdef _MSC_VER
// This pragma saves 8kb of wasted code.
#pragma pointers_to_members( full_generality, single_inheritance )
#endif

class DFraggleThinker;


class CFraggleScriptError : public CEngineError
{
public:
	CFraggleScriptError() : CEngineError() {}
	CFraggleScriptError(const char *message) : CEngineError(message) {}
};


class DRunningScript;



inline bool isop(int c)
{
	return !( ( (c)<='Z' && (c)>='A') || ( (c)<='z' && (c)>='a') || 
		( (c)<='9' && (c)>='0') || ( (c)=='_') );
}

//==========================================================================
//
//
//
//==========================================================================

enum
{
  svt_string,
  svt_int,
  svt_mobj,         // a map object
  svt_function,     // functions are stored as variables
  svt_label,        // labels for goto calls are variables
  svt_const,        // const
  svt_fixed,        // haleyjd: fixed-point int - 8-17 std
  svt_pInt,         // pointer to game int
  svt_pMobj,        // pointer to game mobj
  svt_linespec,		// line special (can be used as both function and constant)
};

//==========================================================================
//
//
//
//==========================================================================
typedef int fsfix;

struct svalue_t
{
	int type;

	FString string;
	union
	{
		int i;
		fsfix f;      // haleyjd: 8-17
		AActor *mobj;
	} value;

	svalue_t()
	{
		type = svt_int;
		value.i = 0;
	}

	svalue_t(const svalue_t & other) = default;
	svalue_t& operator=(const svalue_t& other) = default;
	void setInt(int ip)
	{
		value.i = ip;
		type = svt_int;
	}

	void setFixed(fsfix fp)
	{
		value.f = fp;
		type = svt_fixed;
	}

	void setDouble(double dp)
	{
		value.f = fsfix(dp * 65536);
		type = svt_fixed;
	}
};

int intvalue(const svalue_t & v);
fsfix fixedvalue(const svalue_t & v);
double floatvalue(const svalue_t & v);
const char *stringvalue(const svalue_t & v);
AActor *actorvalue(FLevelLocals *Level, const svalue_t &svalue);

//==========================================================================
//
// varoius defines collected in a nicer manner
//
//==========================================================================

enum
{
	VARIABLESLOTS = 16,
	SECTIONSLOTS = 17,
	T_MAXTOKENS = 256,
	TOKENLENGTH = 128,
	MAXARGS = 128,
	MAXSCRIPTS = 257,
};

//==========================================================================
//
// One variable
//
//==========================================================================
struct FParser;

struct DFsVariable : public DObject
{
	DECLARE_CLASS(DFsVariable, DObject)
	HAS_OBJECT_POINTERS

public:
	FString Name;
	TObjPtr<DFsVariable*> next;       // for hashing

	int type;       // svt_string or svt_int: same as in svalue_t
	FString string;
	TObjPtr<AActor*> actor;

	union value_t
	{
		int32_t i;
		fsfix fixed;          // haleyjd: fixed-point
		
		// the following are only used in the global script so we don't need to bother with them
		// when serializing variables.
		int *pI;                // pointer to game int
		AActor **pMobj;         // pointer to game obj
		void (FParser::*handler)();      // for functions
		const FLineSpecial *ls;
	} value;

public:

	DFsVariable(const char *_name = "");

	void GetValue(svalue_t &result);
	void SetValue(FLevelLocals *Level, const svalue_t &newvalue);
	void Serialize(FSerializer &ar);
};

//==========================================================================
//
// hash the variables for speed: this is the hashkey
//
//==========================================================================

inline int variable_hash(const char *n)
{
	return 
              (n[0]? (   ( n[0] + n[1] +   
                   (n[1] ? n[2] +   
				   (n[2] ? n[3]  : 0) : 0) ) % VARIABLESLOTS ) :0);
}


//==========================================================================
//
// Sections
//
//==========================================================================

enum    // section types
{
	st_empty,       // empty {} braces
	st_if,
	st_elseif,
	st_else,
	st_loop,
};

struct DFsSection : public DObject
{
	DECLARE_CLASS(DFsSection, DObject)
	HAS_OBJECT_POINTERS
public:
	int type;
	int start_index;
	int end_index;
	int loop_index;
	TObjPtr<DFsSection*> next;        // for hashing

	DFsSection()
	{
		next = nullptr;
	}

	void Serialize(FSerializer &ar);
};



//==========================================================================
//
// Tokens
//
//==========================================================================

enum tokentype_t
{
	name_,   // a name, eg 'count1' or 'frag'
	number,
	operator_,
	string_,
	unset,
	function          // function name
};

enum    // brace types: where current_section is a { or }
{
	bracket_open,
	bracket_close
};

//==========================================================================
//
// Errors
//
//==========================================================================

class CFsError
{
public:
	// trying to throw strings crashes VC++ badly so we have to use a static buffer. :(
	char msg[2048];

	CFsError(const FString &in)
	{
		strncpy(msg, in.GetChars(), 2047);
		msg[2047]=0;
	}
};

// throw this object to regularly terminate a script's execution.
class CFsTerminator
{
	int fill;
};

//==========================================================================
//
// Scripts
//
//==========================================================================

class DFsScript : public DObject
{
	DECLARE_CLASS(DFsScript, DObject)
	HAS_OBJECT_POINTERS

public:
	// script data

	TArray<char> Data;
	int scriptnum;  // this script's number
	int len;

	// {} sections

	TObjPtr<DFsSection*> sections[SECTIONSLOTS];

	// variables:

	TObjPtr<DFsVariable*> variables[VARIABLESLOTS];

	// ptr to the parent script
	// the parent script is the script above this level
	// eg. individual linetrigger scripts are children
	// of the levelscript, which is a child of the
	// global_script
	TObjPtr<DFsScript*> parent;

	// haleyjd: 8-17
	// child scripts.
	// levelscript holds ptrs to all of the level's scripts
	// here.
	TObjPtr<DFsScript*> children[MAXSCRIPTS];


	TObjPtr<AActor*> trigger;        // object which triggered this script

	bool lastiftrue;     // haleyjd: whether last "if" statement was 
	// true or false

	DFsScript();
	void OnDestroy() override;
	void Serialize(FSerializer &ar);

	DFsVariable *NewVariable(const char *name, int vtype);
	void NewFunction(const char *name, void (FParser::*handler)());

	DFsVariable *VariableForName(const char *name);
	DFsVariable *FindVariable(const char *name, DFsScript *global);
	void ClearVariables(bool complete= false);
	DFsVariable *NewLabel(char *labelptr);
	char *LabelValue(const svalue_t &v);

	char *SectionStart(const DFsSection *sec);
	char *SectionEnd(const DFsSection *sec);
	char *SectionLoop(const DFsSection *sec);
	void ClearSections();
	void ClearChildren();

	int MakeIndex(const char *p) { return int(p-Data.Data()); }

	// preprocessor
	int section_hash(const char *b) { return MakeIndex(b) % SECTIONSLOTS; }
	DFsSection *NewSection(const char *brace);
	DFsSection *FindSectionStart(const char *brace);
	DFsSection *FindSectionEnd(const char *brace);
	char *ProcessFindChar(char *data, char find);
	void DryRunScript(FLevelLocals *Level);
	void Preprocess(FLevelLocals *Level);
	void ParseInclude(FLevelLocals *Level, char *lumpname);
	void ParseScript(char *rover, DFraggleThinker *th);
	void ParseData(char *rover, char *data, char *end);
};

//==========================================================================
//
// The script parser
//
//==========================================================================

struct FParser
{
	struct operator_t
	{
		const char *string;
		void (FParser::*handler)(svalue_t &, int, int, int); // left, mid, right
		int direction;
	};

	enum
	{
		forward,
		backward
	};

	static operator_t operators[];
	static int num_operators;


	char *LineStart;
	char *Rover;

	char *Tokens[T_MAXTOKENS];
	tokentype_t TokenType[T_MAXTOKENS];
	int NumTokens;
	FLevelLocals *Level;
	DFsScript *Script;       // the current script
	DFsSection *Section;
	DFsSection *PrevSection;
	int BraceType;

	int t_argc;                     // number of arguments
	svalue_t *t_argv;               // arguments
	svalue_t t_return;              // returned value
	FString t_func;					// name of current function

	FParser(FLevelLocals *l, DFsScript *scr)
	{
		Level = l;
		LineStart = NULL;
		Rover = NULL;
		Tokens[0] = new char[scr->len+32];	// 32 for safety. FS seems to need a few bytes more than the script's actual length.
		NumTokens = 0;
		Script = scr;
		Section = PrevSection = NULL;
		BraceType = 0;
	}

	~FParser()
	{
		if (Tokens[0]) delete [] Tokens[0];
	}

	void NextToken();
	char *GetTokens(char *s);
	void PrintTokens();
	void ErrorMessage(FString msg);

	void Run(char *rover, char *data, char *end);
	void RunStatement();
	int FindOperator(int start, int stop, const char *value);
	int FindOperatorBackwards(int start, int stop, const char *value);
	void SimpleEvaluate(svalue_t &, int n);
	void PointlessBrackets(int *start, int *stop);
	void EvaluateExpression(svalue_t &, int start, int stop);
	void EvaluateFunction(svalue_t &, int start, int stop);

	void OPequals(svalue_t &, int, int, int);           // =

	void OPplus(svalue_t &, int, int, int);             // +
	void OPminus(svalue_t &, int, int, int);            // -
	void OPmultiply(svalue_t &, int, int, int);         // *
	void OPdivide(svalue_t &, int, int, int);           // /
	void OPremainder(svalue_t &, int, int, int);        // %

	void OPor(svalue_t &, int, int, int);               // ||
	void OPand(svalue_t &, int, int, int);              // &&
	void OPnot(svalue_t &, int, int, int);              // !

	void OPor_bin(svalue_t &, int, int, int);           // |
	void OPand_bin(svalue_t &, int, int, int);          // &
	void OPnot_bin(svalue_t &, int, int, int);          // ~

	void OPcmp(svalue_t &, int, int, int);              // ==
	void OPnotcmp(svalue_t &, int, int, int);           // !=
	void OPlessthan(svalue_t &, int, int, int);         // <
	void OPgreaterthan(svalue_t &, int, int, int);      // >

	void OPincrement(svalue_t &, int, int, int);        // ++
	void OPdecrement(svalue_t &, int, int, int);        // --

	void OPstructure(svalue_t &, int, int, int);    // in t_vari.c

	void OPlessthanorequal(svalue_t &, int, int, int);     // <=
	void OPgreaterthanorequal(svalue_t &, int, int, int);  // >=

	void spec_brace();
	bool spec_if();
	bool spec_elseif(bool lastif);
	void spec_else(bool lastif);
	void spec_for();
	void spec_while();
	void CreateVariable(int newvar_type, DFsScript *newvar_script, int start, int stop);
	void ParseVarLine(int newvar_type, DFsScript *newvar_script, int start);
	bool spec_variable();
	void spec_script();

	DFsSection *looping_section();
	FString GetFormatString(int startarg);
	bool CheckArgs(int cnt);

	PClassActor * T_GetMobjType(svalue_t arg);
	int T_GetPlayerNum(const svalue_t &arg);
	AActor *T_GetPlayerActor(const svalue_t &arg);

	AActor* actorvalue(const svalue_t &svalue)
	{
		return ::actorvalue(Level, svalue);
	}

	void SF_Print();
	void SF_Rnd();
	void SF_Continue();
	void SF_Break();
	void SF_Goto();
	void SF_Return();
	void SF_Include();
	void SF_Input();
	void SF_Beep();
	void SF_Clock();
	void SF_ExitLevel();
	void SF_Tip();
	void SF_TimedTip();
	void SF_PlayerTip();
	void SF_Message();
	void SF_PlayerMsg();
	void SF_PlayerInGame();
	void SF_PlayerName();
	void SF_PlayerObj();
	void SF_StartScript();      // FPUKE needs to access this
	void SF_ScriptRunning();
	void SF_Wait();
	void SF_TagWait();
	void SF_ScriptWait();
	void SF_ScriptWaitPre();    // haleyjd: new wait types
	void SF_Player();
	void SF_Spawn();
	void SF_RemoveObj();
	void SF_KillObj();
	void SF_ObjX();
	void SF_ObjY();
	void SF_ObjZ();
	void SF_ObjAngle();
	void SF_Teleport();
	void SF_SilentTeleport();
	void SF_DamageObj();
	void SF_ObjSector();
	void SF_ObjHealth();
	void SF_ObjFlag();
	void SF_PushThing();
	void SF_ReactionTime();
	void SF_MobjTarget();
	void SF_MobjMomx();
	void SF_MobjMomy();
	void SF_MobjMomz();
	void SF_PointToAngle();
	void SF_PointToDist();
	void SF_SetCamera();
	void SF_ClearCamera();
	void SF_StartSound();
	void SF_StartSectorSound();
	void SF_FloorHeight();
	void SF_MoveFloor();
	void SF_CeilingHeight();
	void SF_MoveCeiling();
	void SF_LightLevel();
	void SF_FadeLight();
	void SF_FloorTexture();
	void SF_SectorColormap();
	void SF_CeilingTexture();
	void SF_ChangeHubLevel();
	void SF_StartSkill();
	void SF_OpenDoor();
	void SF_CloseDoor();
	void SF_RunCommand();
	void SF_LineTrigger();
	void SF_ChangeMusic();
	void SF_SetLineBlocking();
	void SF_SetLineMonsterBlocking();
	void SF_SetLineTexture();
	void SF_Max();
	void SF_Min();
	void SF_Abs();
	void SF_Gameskill();
	void SF_Gamemode();
	void SF_IsPlayerObj();
	void SF_PlayerKeys();
	void SF_PlayerAmmo();
	void SF_MaxPlayerAmmo();
	void SF_PlayerWeapon();
	void SF_PlayerSelectedWeapon();
	void SF_GiveInventory();
	void SF_TakeInventory();
	void SF_CheckInventory();
	void SF_SetWeapon();
	void SF_MoveCamera();
	void SF_ObjAwaken();
	void SF_AmbientSound();
	void SF_ExitSecret();
	void SF_MobjValue();
	void SF_StringValue();
	void SF_IntValue();
	void SF_FixedValue();
	void SF_SpawnExplosion();
	void SF_RadiusAttack();
	void SF_SetObjPosition();
	void SF_TestLocation();
	void SF_HealObj();  //no pain sound
	void SF_ObjDead();
	void SF_SpawnMissile();
	void SF_MapThingNumExist();
	void SF_MapThings();
	void SF_ObjState();
	void SF_LineFlag();
	void SF_PlayerAddFrag();
	void SF_SkinColor();
	void SF_PlayDemo();
	void SF_CheckCVar();
	void SF_Resurrect();
	void SF_LineAttack();
	void SF_ObjType();
	void SF_Sin();
	void SF_ASin();
	void SF_Cos();
	void SF_ACos();
	void SF_Tan();
	void SF_ATan();
	void SF_Exp();
	void SF_Log();
	void SF_Sqrt();
	void SF_Floor();
	void SF_Pow();
	void SF_NewHUPic();
	void SF_DeleteHUPic();
	void SF_ModifyHUPic();
	void SF_SetHUPicDisplay();
	void SF_SetCorona();
	void SF_Ls();
	void SF_LevelNum();
	void SF_MobjRadius();
	void SF_MobjHeight();
	void SF_ThingCount();
	void SF_SetColor();
	void SF_SpawnShot2();
	void SF_KillInSector();
	void SF_SectorType();
	void SF_SetLineTrigger();
	void SF_ChangeTag();
	void SF_WallGlow();
	void RunLineSpecial(const FLineSpecial *);

	DRunningScript *SaveCurrentScript();

};

//==========================================================================
//
// Running scripts
//
//==========================================================================

enum waittype_e
{
    wt_none,        // not waiting
	wt_delay,       // wait for a set amount of time
	wt_tagwait,     // wait for sector to stop moving
	wt_scriptwait,  // wait for script to finish
	wt_scriptwaitpre, // haleyjd - wait for script to start
};

class DRunningScript : public DObject
{
	DECLARE_CLASS(DRunningScript, DObject)
	HAS_OBJECT_POINTERS

public:
	DRunningScript(AActor *trigger=NULL, DFsScript *owner = NULL, int index = 0) ;
	void OnDestroy() override;
	void Serialize(FSerializer &arc);

	TObjPtr<DFsScript*> script;
	
	// where we are
	int save_point;
	
	int wait_type;
	int wait_data;  // data for wait: tagnum, counter, script number etc
	
	// saved variables
	TObjPtr<DFsVariable*> variables[VARIABLESLOTS];
	
	TObjPtr<DRunningScript*> prev, next;  // for chain
	TObjPtr<AActor*> trigger;
};

//-----------------------------------------------------------------------------
//
// This thinker eliminates the need to call the Fragglescript functions from the main code
//
//-----------------------------------------------------------------------------
class DFraggleThinker : public DThinker
{
	DECLARE_CLASS(DFraggleThinker, DThinker)
	HAS_OBJECT_POINTERS
public:
	static const int DEFAULT_STAT = STAT_SCRIPTS;
	int zoom = 1;
	AActor *trigger_obj;	// this is a transient pointer not being subjected to GC.
	TObjPtr<DFsScript*> GlobalScript;
	TObjPtr<DFsScript*> LevelScript;
	TObjPtr<DRunningScript*> RunningScripts;
	TArray<TObjPtr<AActor*> > SpawnedThings;

	DFraggleThinker();	// This class needs a real constructor because it has non-serializable content.
	void Construct();
	void OnDestroy() override;


	void Serialize(FSerializer & arc);
	void Tick();
	void InitFunctions();
	size_t PropagateMark();
	size_t PointerSubstitution (DObject *old, DObject *notOld, bool nullOnFail);
	bool wait_finished(DRunningScript *script);
	void AddRunningScript(DRunningScript *runscr);

	static TObjPtr<DFraggleThinker*> ActiveThinker;
};

//-----------------------------------------------------------------------------
//
// Global stuff
//
//-----------------------------------------------------------------------------

#include "t_fs.h"

void script_error(const char *s, ...) GCCPRINTF(1,2);
void FS_EmulateCmd(FLevelLocals *l, char * string);

#endif

