/*
** g_level.h
**
**---------------------------------------------------------------------------
** Copyright 1998-2006 Randy Heit
** All rights reserved.
**
** Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions
** are met:
**
** 1. Redistributions of source code must retain the above copyright
**    notice, this list of conditions and the following disclaimer.
** 2. Redistributions in binary form must reproduce the above copyright
**    notice, this list of conditions and the following disclaimer in the
**    documentation and/or other materials provided with the distribution.
** 3. The name of the author may not be used to endorse or promote products
**    derived from this software without specific prior written permission.
**
** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
**---------------------------------------------------------------------------
**
*/

#ifndef __G_LEVEL_H__
#define __G_LEVEL_H__

#include "doomtype.h"
#include "vectors.h"
#include "sc_man.h"
#include "resourcefiles/file_zip.h"

struct level_info_t;
struct cluster_info_t;
class FSerializer;

#if defined(_MSC_VER)
#pragma section(".yreg$u",read)
#define MSVC_YSEG __declspec(allocate(".yreg$u"))
#define GCC_YSEG
#else
#define MSVC_YSEG
#define GCC_YSEG __attribute__((section(SECTION_YREG))) __attribute__((used))
#endif

// The structure used to control scripts between maps
struct acsdefered_t
{
	enum EType
	{
		defexecute,
		defexealways,
		defsuspend,
		defterminate
	} type;
	int script;
	int args[3];
	int playernum;
};

FSerializer &Serialize(FSerializer &arc, const char *key, acsdefered_t &defer, acsdefered_t *def);


struct FIntermissionDescriptor;
struct FIntermissionAction;

struct FMapInfoParser
{
	enum EFormatType
	{
		FMT_Unknown,
		FMT_Old,
		FMT_New
	};

	FScanner sc;
	int format_type;
	bool HexenHack;

	FMapInfoParser(int format = FMT_Unknown)
	{
		format_type = format;
		HexenHack = false;
	}

	bool ParseLookupName(FString &dest);
	void ParseMusic(FString &name, int &order);
	//void ParseLumpOrTextureName(char *name);
	void ParseLumpOrTextureName(FString &name);
	void ParseExitText(FName formap, level_info_t *info);
	void ParseExitMusic(FName formap, level_info_t *info);
	void ParseExitBackdrop(FName formap, level_info_t *info, bool ispic);

	void ParseCluster();
	void ParseNextMap(FString &mapname);
	level_info_t *ParseMapHeader(level_info_t &defaultinfo);
	void ParseMapDefinition(level_info_t &leveldef);
	void ParseGameInfo();
	void ParseEpisodeInfo ();
	void ParseSkill ();
	void ParseMapInfo (int lump, level_info_t &gamedefaults, level_info_t &defaultinfo);

	void ParseOpenBrace();
	bool ParseCloseBrace();
	bool CheckAssign();
	void ParseAssign();
	void MustParseAssign();
	void ParseComma();
	bool CheckNumber();
	bool CheckFloat();
	void SkipToNext();
	void CheckEndOfFile(const char *block);

	void ParseIntermissionAction(FIntermissionDescriptor *Desc);
	void ParseIntermission();
	void ParseDoomEdNums();
	void ParseSpawnNums();
	void ParseConversationIDs();
	void ParseAMColors(bool);
	FName CheckEndSequence();
	FName ParseEndGame();
	void ParseDamageDefinition();
};

#define DEFINE_MAP_OPTION(name, old) \
	static void MapOptHandler_##name(FMapInfoParser &parse, level_info_t *info); \
	static FMapOptInfo MapOpt_##name = \
		{ #name, MapOptHandler_##name, old }; \
	MSVC_YSEG FMapOptInfo *mapopt_##name GCC_YSEG = &MapOpt_##name; \
	static void MapOptHandler_##name(FMapInfoParser &parse, level_info_t *info)


struct FMapOptInfo
{
	const char *name;
	void (*handler) (FMapInfoParser &parse, level_info_t *levelinfo);
	bool old;
};

enum ELevelFlags : unsigned int
{
	LEVEL_NOINTERMISSION		= 0x00000001,
	LEVEL_NOINVENTORYBAR		= 0x00000002,	// This effects Doom only, since it's the only one without a standard inventory bar.
	LEVEL_DOUBLESKY				= 0x00000004,
	LEVEL_HASFADETABLE			= 0x00000008,	// Level uses Hexen's fadetable mapinfo to get fog

	LEVEL_MAP07SPECIAL			= 0x00000010,
	LEVEL_BRUISERSPECIAL		= 0x00000020,
	LEVEL_CYBORGSPECIAL			= 0x00000040,
	LEVEL_SPIDERSPECIAL			= 0x00000080,

	LEVEL_SPECLOWERFLOOR		= 0x00000100,
	LEVEL_SPECOPENDOOR			= 0x00000200,
	LEVEL_SPECLOWERFLOORTOHIGHEST=0x00000300,
	LEVEL_SPECACTIONSMASK		= 0x00000300,

	LEVEL_MONSTERSTELEFRAG		= 0x00000400,
	LEVEL_ACTOWNSPECIAL			= 0x00000800,
	LEVEL_SNDSEQTOTALCTRL		= 0x00001000,
	LEVEL_FORCETILEDSKY		= 0x00002000,

	LEVEL_CROUCH_NO				= 0x00004000,
	LEVEL_JUMP_NO				= 0x00008000,
	LEVEL_FREELOOK_NO			= 0x00010000,
	LEVEL_FREELOOK_YES			= 0x00020000,

	// The absence of both of the following bits means that this level does not
	// use falling damage (though damage can be forced with dmflags,.
	LEVEL_FALLDMG_ZD			= 0x00040000,	// Level uses ZDoom's falling damage
	LEVEL_FALLDMG_HX			= 0x00080000,	// Level uses Hexen's falling damage

	LEVEL_HEADSPECIAL			= 0x00100000,	// Heretic episode 1/4
	LEVEL_MINOTAURSPECIAL		= 0x00200000,	// Heretic episode 2/5
	LEVEL_SORCERER2SPECIAL		= 0x00400000,	// Heretic episode 3
	LEVEL_SPECKILLMONSTERS		= 0x00800000,

	LEVEL_STARTLIGHTNING		= 0x01000000,	// Automatically start lightning
	LEVEL_FILTERSTARTS			= 0x02000000,	// Apply mapthing filtering to player starts
	LEVEL_LOOKUPLEVELNAME		= 0x04000000,	// Level name is the name of a language string
	LEVEL_USEPLAYERSTARTZ		= 0x08000000,	// Use the Z position of player starts

	LEVEL_SWAPSKIES				= 0x10000000,	// Used by lightning
	LEVEL_NOALLIES				= 0x20000000,	// i.e. Inside Strife's front base
	LEVEL_CHANGEMAPCHEAT		= 0x40000000,	// Don't display cluster messages
	LEVEL_VISITED				= 0x80000000,	// Used for intermission map

	// The flags uint64_t is now split into 2 DWORDs 
	LEVEL2_RANDOMPLAYERSTARTS	= 0x00000001,	// Select single player starts randomnly (no voodoo dolls)
	LEVEL2_ALLMAP				= 0x00000002,	// The player picked up a map on this level

	LEVEL2_LAXMONSTERACTIVATION	= 0x00000004,	// Monsters can open doors depending on the door speed
	LEVEL2_LAXACTIVATIONMAPINFO	= 0x00000008,	// LEVEL_LAXMONSTERACTIVATION is not a default.

	LEVEL2_MISSILESACTIVATEIMPACT=0x00000010,	// Missiles are the activators of SPAC_IMPACT events, not their shooters
	LEVEL2_FROZEN				= 0x00000020,	// Game is frozen by a TimeFreezer

	LEVEL2_KEEPFULLINVENTORY	= 0x00000040,	// doesn't reduce the amount of inventory items to 1

	LEVEL2_PRERAISEWEAPON		= 0x00000080,	// players should spawn with their weapons fully raised (but not when respawning it multiplayer)
	LEVEL2_MONSTERFALLINGDAMAGE	= 0x00000100,
	LEVEL2_CLIPMIDTEX			= 0x00000200,
	LEVEL2_WRAPMIDTEX			= 0x00000400,

	LEVEL2_CHECKSWITCHRANGE		= 0x00000800,	

	LEVEL2_PAUSE_MUSIC_IN_MENUS	= 0x00001000,
	LEVEL2_TOTALINFIGHTING		= 0x00002000,
	LEVEL2_NOINFIGHTING			= 0x00004000,

	LEVEL2_NOMONSTERS			= 0x00008000,
	LEVEL2_INFINITE_FLIGHT		= 0x00010000,

	LEVEL2_ALLOWRESPAWN			= 0x00020000,

	LEVEL2_FORCETEAMPLAYON		= 0x00040000,
	LEVEL2_FORCETEAMPLAYOFF		= 0x00080000,

	LEVEL2_CONV_SINGLE_UNFREEZE	= 0x00100000,
	LEVEL2_RAILINGHACK			= 0x00200000,	// but UDMF requires them to be separate to have more control
	LEVEL2_DUMMYSWITCHES		= 0x00400000,
	LEVEL2_HEXENHACK			= 0x00800000,	// Level was defined in a Hexen style MAPINFO

	LEVEL2_SMOOTHLIGHTING		= 0x01000000,	// Level uses the smooth lighting feature.
	LEVEL2_POLYGRIND			= 0x02000000,	// Polyobjects grind corpses to gibs.
	LEVEL2_RESETINVENTORY		= 0x04000000,	// Resets player inventory when starting this level (unless in a hub)
	LEVEL2_RESETHEALTH			= 0x08000000,	// Resets player health when starting this level (unless in a hub)

	LEVEL2_NOSTATISTICS			= 0x10000000,	// This level should not have statistics collected
	LEVEL2_ENDGAME				= 0x20000000,	// This is an epilogue level that cannot be quit.
	LEVEL2_NOAUTOSAVEHINT		= 0x40000000,	// tell the game that an autosave for this level does not need to be kept
	LEVEL2_FORGETSTATE			= 0x80000000,	// forget this map's state in a hub
	
	// More flags!
	LEVEL3_FORCEFAKECONTRAST	= 0x00000001,	// forces fake contrast even with fog enabled
	LEVEL3_REMOVEITEMS			= 0x00000002,	// kills all INVBAR items on map change.
	LEVEL3_ATTENUATE			= 0x00000004,	// attenuate lights?
	LEVEL3_NOLIGHTFADE			= 0x00000008,	// no light fading to black.
	LEVEL3_NOCOLOREDSPRITELIGHTING = 0x00000010,	// draw sprites only with color-less light
	LEVEL3_EXITNORMALUSED		= 0x00000020,
	LEVEL3_EXITSECRETUSED		= 0x00000040,
};


struct FSpecialAction
{
	FName Type;					// this is initialized before the actors...
	uint8_t Action;
	int Args[5];
};

class DScroller;

class FScanner;
struct level_info_t;

struct FOptionalMapinfoData
{
	FOptionalMapinfoData *Next = nullptr;
	FName identifier = NAME_None;
	virtual ~FOptionalMapinfoData() {}
	virtual FOptionalMapinfoData *Clone() const = 0;
};

struct FOptionalMapinfoDataPtr
{
	FOptionalMapinfoData *Ptr;

	FOptionalMapinfoDataPtr() throw() : Ptr(NULL) {}
	~FOptionalMapinfoDataPtr() { if (Ptr!=NULL) delete Ptr; }
	FOptionalMapinfoDataPtr(const FOptionalMapinfoDataPtr &p) throw() : Ptr(p.Ptr->Clone()) {}
	FOptionalMapinfoDataPtr &operator= (FOptionalMapinfoDataPtr &p) throw() { Ptr = p.Ptr->Clone(); return *this; }
};

typedef TMap<FName, FOptionalMapinfoDataPtr> FOptData;
typedef TMap<int, FName> FMusicMap;

enum EMapType : int
{
	MAPTYPE_UNKNOWN = 0,
	MAPTYPE_DOOM,
	MAPTYPE_HEXEN,
	MAPTYPE_BUILD,
	MAPTYPE_UDMF	// This does not distinguish between namespaces.
};

struct FExitText
{
	enum EDefined
	{
		DEF_TEXT = 1,
		DEF_MUSIC = 2,
		DEF_BACKDROP = 4,
		DEF_LOOKUP = 8,
		DEF_PIC = 16
	};
	int16_t mDefined = 0;
	int16_t mOrder = -1;
	FString mText;
	FString mMusic;
	FString mBackdrop;

	FExitText(int def = 0, int order = -1, const FString &text = "", const FString &backdrop = "", const FString &music = "")
		: mDefined(int16_t(def)), mOrder(int16_t(order)), mText(text), mMusic(music), mBackdrop(backdrop)
	{
	}
};

struct level_info_t
{
	int			levelnum;
	
	FString		MapName;
	FString		NextMap;
	FString		NextSecretMap;
	FString		PName;
	FString		SkyPic1;
	FString		SkyPic2;
	FString		FadeTable;
	FString		F1Pic;
	FString		BorderTexture;
	FString		MapBackground;

	TMap<FName, FExitText> ExitMapTexts;

	int			cluster;
	int			partime;
	int			sucktime;
	int32_t		flags;
	uint32_t	flags2;
	uint32_t	flags3;

	FString		Music;
	FString		LevelName;
	int8_t		WallVertLight, WallHorizLight;
	int			musicorder;
	FCompressedBuffer	Snapshot;
	TArray<acsdefered_t> deferred;
	float		skyspeed1;
	float		skyspeed2;
	uint32_t	fadeto;
	uint32_t	outsidefog;
	int			cdtrack;
	unsigned int cdid;
	double		gravity;
	double		aircontrol;
	int			WarpTrans;
	int			airsupply;
	uint32_t	compatflags, compatflags2;
	uint32_t	compatmask, compatmask2;
	FString		Translator;	// for converting Doom-format linedef and sector types.
	int			DefaultEnvironment;	// Default sound environment for the map.
	FName		Intermission;
	FName		deathsequence;
	FName		slideshow;
	uint32_t	hazardcolor;
	uint32_t	hazardflash;
	int			fogdensity;
	int			outsidefogdensity;
	int			skyfog;
	float		pixelstretch;

	// Redirection: If any player is carrying the specified item, then
	// you go to the RedirectMap instead of this one.
	FName		RedirectType;
	FString		RedirectMapName;

	FString		EnterPic;
	FString		ExitPic;
	FString 	InterMusic;
	int			intermusicorder;
	TMap <FName, std::pair<FString, int> > MapInterMusic;

	FString		SoundInfo;
	FString		SndSeq;

	double		teamdamage;

	FOptData	optdata;
	FMusicMap	MusicMap;

	TArray<FSpecialAction> specialactions;

	TArray<int> PrecacheSounds;
	TArray<FString> PrecacheTextures;
	TArray<FName> PrecacheClasses;
	
	TArray<FString> EventHandlers;

	int8_t		lightmode;
	int8_t		brightfog;
	int8_t		lightadditivesurfaces;
	int8_t		notexturefill;
	FVector3	skyrotatevector;
	FVector3	skyrotatevector2;


	level_info_t() 
	{ 
		Reset(); 
	}
	~level_info_t()
	{
		Snapshot.Clean();
		ClearDefered();
	}
	void Reset();
	bool isValid();
	FString LookupLevelName ();
	void ClearDefered()
	{
		deferred.Clear();
	}
	level_info_t *CheckLevelRedirect ();

	template<class T>
	T *GetOptData(FName id, bool create = true)
	{
		FOptionalMapinfoDataPtr *pdat = optdata.CheckKey(id);
		
		if (pdat != NULL)
		{
			return static_cast<T*>(pdat->Ptr);
		}
		else if (create)
		{
			T *newobj = new T;
			optdata[id].Ptr = newobj;
			return newobj;
		}
		else return NULL;
	}
};


struct cluster_info_t
{
	int			cluster;
	FString		FinaleFlat;
	FString		ExitText;
	FString		EnterText;
	FString		MessageMusic;
	int			musicorder;
	int			flags;
	int			cdtrack;
	FString		ClusterName;
	unsigned int cdid;

	void Reset();

};

// Cluster flags
#define CLUSTER_HUB				0x00000001	// Cluster uses hub behavior
#define CLUSTER_EXITTEXTINLUMP	0x00000002	// Exit text is the name of a lump
#define CLUSTER_ENTERTEXTINLUMP	0x00000004	// Enter text is the name of a lump
#define CLUSTER_FINALEPIC		0x00000008	// Finale "flat" is actually a full-sized image
#define CLUSTER_LOOKUPEXITTEXT	0x00000010	// Exit text is the name of a language string
#define CLUSTER_LOOKUPENTERTEXT	0x00000020	// Enter text is the name of a language string
#define CLUSTER_LOOKUPNAME		0x00000040	// Name is the name of a language string
#define CLUSTER_LOOKUPCLUSTERNAME 0x00000080	// Cluster name is the name of a language string
#define CLUSTER_ALLOWINTERMISSION 0x00000100  // Allow intermissions between levels in a hub.

extern TArray<level_info_t> wadlevelinfos;
extern TArray<cluster_info_t> wadclusterinfos;

extern bool savegamerestore;

// mapname will be changed if it is a valid warptrans
bool CheckWarpTransMap (FString &mapname, bool substitute);

void G_InitNew (const char *mapname, bool bTitleLevel);

// Can be called by the startup code or M_Responder.
// A normal game starts at map 1,
// but a warp test can start elsewhere
void G_DeferedInitNew (const char *mapname, int skill = -1);
struct FGameStartup;
void G_DeferedInitNew (FGameStartup *gs);

void G_ExitLevel (int position, bool keepFacing);
void G_SecretExitLevel (int position);
const char *G_GetExitMap();
const char *G_GetSecretExitMap();

enum 
{
	CHANGELEVEL_KEEPFACING = 1,
	CHANGELEVEL_RESETINVENTORY = 2,
	CHANGELEVEL_NOMONSTERS = 4,
	CHANGELEVEL_CHANGESKILL = 8,
	CHANGELEVEL_NOINTERMISSION = 16,
	CHANGELEVEL_RESETHEALTH = 32,
	CHANGELEVEL_PRERAISEWEAPON = 64,
};

void G_ChangeLevel(const char *levelname, int position, int flags, int nextSkill=-1);

void G_StartTravel ();
int G_FinishTravel ();

void G_DoLoadLevel (int position, bool autosave, bool newGame);

void G_InitLevelLocals (void);

void G_AirControlChanged ();

cluster_info_t *FindClusterInfo (int cluster);
level_info_t *FindLevelInfo (const char *mapname, bool allowdefault=true);
level_info_t *FindLevelByNum (int num);
level_info_t *CheckLevelRedirect (level_info_t *info);

FString CalcMapName (int episode, int level);

void G_ParseMapInfo (FString basemapinfo);

void G_ClearSnapshots (void);
void P_RemoveDefereds ();
void G_SnapshotLevel (void);
void G_UnSnapshotLevel (bool keepPlayers);
void G_ReadSnapshots (FResourceFile *);
void G_WriteSnapshots (TArray<FString> &, TArray<FCompressedBuffer> &);
void G_WriteVisited(FSerializer &arc);
void G_ReadVisited(FSerializer &arc);
void G_ClearHubInfo();

enum ESkillProperty
{
	SKILLP_FastMonsters,
	SKILLP_Respawn,
	SKILLP_RespawnLimit,
	SKILLP_DisableCheats,
	SKILLP_AutoUseHealth,
	SKILLP_SpawnFilter,
	SKILLP_EasyBossBrain,
	SKILLP_ACSReturn,
	SKILLP_NoPain,
	SKILLP_EasyKey,
	SKILLP_SlowMonsters,
	SKILLP_Infight,
	SKILLP_PlayerRespawn,
};
enum EFSkillProperty	// floating point properties
{
	SKILLP_AmmoFactor,
	SKILLP_DropAmmoFactor,
	SKILLP_ArmorFactor,
	SKILLP_HealthFactor,
	SKILLP_DamageFactor,
	SKILLP_Aggressiveness,
	SKILLP_MonsterHealth,
	SKILLP_FriendlyHealth,
	SKILLP_KickbackFactor,
};

int G_SkillProperty(ESkillProperty prop);
double G_SkillProperty(EFSkillProperty prop);
const char * G_SkillName();

typedef TMap<FName, FString> SkillMenuNames;

typedef TMap<FName, FName> SkillActorReplacement;

struct FSkillInfo
{
	FName Name = NAME_None;
	double AmmoFactor, DoubleAmmoFactor, DropAmmoFactor;
	double DamageFactor;
	double ArmorFactor;
	double HealthFactor;
	double KickbackFactor;

	bool FastMonsters;
	bool SlowMonsters;
	bool DisableCheats;
	bool AutoUseHealth;

	bool EasyBossBrain;
	bool EasyKey;
	bool NoMenu;
	int RespawnCounter;
	int RespawnLimit;
	double Aggressiveness;
	int SpawnFilter;
	int ACSReturn;
	FString MenuName;
	FString PicName;
	SkillMenuNames MenuNamesForPlayerClass;
	bool MustConfirm;
	FString MustConfirmText;
	char Shortcut;
	FString TextColor;
	SkillActorReplacement Replace;
	SkillActorReplacement Replaced;
	double MonsterHealth;
	double FriendlyHealth;
	bool NoPain;
	int Infighting;
	bool PlayerRespawn;

	FSkillInfo() {}
	FSkillInfo(const FSkillInfo &other)
	{
		operator=(other);
	}
	FSkillInfo &operator=(const FSkillInfo &other);
	int GetTextColor() const;

	void SetReplacement(FName a, FName b);
	FName GetReplacement(FName a);
	void SetReplacedBy(FName b, FName a);
	FName GetReplacedBy(FName b);
};

extern TArray<FSkillInfo> AllSkills;
extern int DefaultSkill;

struct FEpisode
{
	FString mEpisodeName;
	FString mEpisodeMap;
	FString mPicName;
	char mShortcut;
	bool mNoSkill;
};

extern TArray<FEpisode> AllEpisodes;

int ParseUMapInfo(int lumpnum);
void CommitUMapinfo(level_info_t *defaultinfo);


#endif //__G_LEVEL_H__