#ifndef GAMESTATE_H
#define GAMESTATE_H

#include "Attack.h"
#include "Difficulty.h"
#include "GameConstantsAndTypes.h"
#include "Grade.h"
#include "MessageManager.h"
#include "ModsGroup.h"
#include "RageTimer.h"
#include "PlayerOptions.h"
#include "SongOptions.h"
#include "SongPosition.h"
#include "Preference.h"

#include <map>
#include <deque>
#include <set>

class Character;
class Course;
class Game;
struct lua_State;
class LuaTable;
class PlayerState;
class PlayerOptions;
class Profile;
class Song;
class Steps;
class StageStats;
class Style;
class TimingData;
class Trail;

SortOrder GetDefaultSort();

/** @brief Holds game data that is not saved between sessions. */
class GameState
{
	/** @brief The player number used with Styles where one player controls both sides. */
	PlayerNumber	masterPlayerNumber;
	/** @brief The TimingData that is used for processing certain functions. */
	TimingData * processedTiming;
public:
	/** @brief Set up the GameState with initial values. */
	GameState();
	~GameState();
	/** @brief Reset the GameState back to initial values. */
	void Reset();
	void ResetPlayer( PlayerNumber pn );
	void ResetPlayerOptions( PlayerNumber pn );
	void ApplyCmdline(); // called by Reset
	void ApplyGameCommand( const RString &sCommand, PlayerNumber pn=PLAYER_INVALID );
	/** @brief Start the game when the first player joins in. */
	void BeginGame();
	void JoinPlayer( PlayerNumber pn );
	void UnjoinPlayer( PlayerNumber pn );
	bool JoinInput( PlayerNumber pn );
	bool JoinPlayers();
	void LoadProfiles( bool bLoadEdits = true );
	void SavePlayerProfiles();
	void SavePlayerProfile( PlayerNumber pn );
	bool HaveProfileToLoad();
	bool HaveProfileToSave();
	void SaveLocalData();
	void AddStageToPlayer( PlayerNumber pn );
	void LoadCurrentSettingsFromProfile( PlayerNumber pn );
	/**
	 * @brief Save the specified player's settings to his/her profile.
	 *
	 * This is called at the beginning of each stage.
	 * @param pn the PlayerNumber to save the stats to. */
	void SaveCurrentSettingsToProfile( PlayerNumber pn );
	Song* GetDefaultSong() const;

	void Update( float fDelta );

	// Main state info

	/**
	 * @brief State what the current game is.
	 *
	 * Call this instead of m_pCurGame.Set to make sure that
	 * PREFSMAN->m_sCurrentGame stays in sync.
	 * @param pGame the game to start using. */
	void SetCurGame( const Game *pGame );
	BroadcastOnChangePtr<const Game>	m_pCurGame;
	BroadcastOnChangePtr<const Style>	m_pCurStyle;
	/** @brief Determine which side is joined.
	 *
	 * The left side is player 1, and the right side is player 2. */
	bool					m_bSideIsJoined[NUM_PLAYERS];	// left side, right side
	MultiPlayerStatus			m_MultiPlayerStatus[NUM_MultiPlayer];
	BroadcastOnChange<PlayMode>		m_PlayMode;			// many screens display different info depending on this value
	/**
	 * @brief The number of coins presently in the machine.
	 *
	 * Note that coins are not "credits". One may have to put in two coins 
	 * to get one credit, only to have to put in another four coins to get
	 * the three credits needed to begin the game. */
	BroadcastOnChange<int>			m_iCoins;
	bool			m_bMultiplayer;
	int				m_iNumMultiplayerNoteFields;
	bool DifficultiesLocked() const;
	bool ChangePreferredDifficultyAndStepsType( PlayerNumber pn, Difficulty dc, StepsType st );
	bool ChangePreferredDifficulty( PlayerNumber pn, int dir );
	bool ChangePreferredCourseDifficultyAndStepsType( PlayerNumber pn, CourseDifficulty cd, StepsType st );
	bool ChangePreferredCourseDifficulty( PlayerNumber pn, int dir );
	bool IsCourseDifficultyShown( CourseDifficulty cd );
	Difficulty GetClosestShownDifficulty( PlayerNumber pn ) const;
	Difficulty GetEasiestStepsDifficulty() const;
	Difficulty GetHardestStepsDifficulty() const;
	RageTimer		m_timeGameStarted;	// from the moment the first player pressed Start
	LuaTable		*m_Environment;

	// This is set to a random number per-game/round; it can be used for a random seed.
	int			m_iGameSeed, m_iStageSeed;
	RString		m_sStageGUID;

	/**
	 * @brief Determine if a second player can join in at this time.
	 * @return true if a player can still enter the game, false otherwise. */
	bool	PlayersCanJoin() const;
	int 	GetCoinsNeededToJoin() const;
	bool	EnoughCreditsToJoin() const { return m_iCoins >= GetCoinsNeededToJoin(); }
	int		GetNumSidesJoined() const;

	const Game*	GetCurrentGame();
	const Style*	GetCurrentStyle() const;
	void	SetCurrentStyle( const Style *pStyle );

	void GetPlayerInfo( PlayerNumber pn, bool& bIsEnabledOut, bool& bIsHumanOut );
	bool IsPlayerEnabled( PlayerNumber pn ) const;
	bool IsMultiPlayerEnabled( MultiPlayer mp ) const;
	bool IsPlayerEnabled( const PlayerState* pPlayerState ) const;
	int	GetNumPlayersEnabled() const;

	/**
	 * @brief Is the specified Player a human Player?
	 * @param pn the numbered Player to check.
	 * @return true if it's a human Player, or false otherwise. */
	bool IsHumanPlayer( PlayerNumber pn ) const;
	int GetNumHumanPlayers() const;
	PlayerNumber GetFirstHumanPlayer() const;
	PlayerNumber GetFirstDisabledPlayer() const;
	bool IsCpuPlayer( PlayerNumber pn ) const;
	bool AnyPlayersAreCpu() const;

	/**
	 * @brief Retrieve the present master player number.
	 * @return The master player number. */
	PlayerNumber GetMasterPlayerNumber() const;

	/**
	 * @brief Set the master player number.
	 * @param p the master player number. */
	void SetMasterPlayerNumber(const PlayerNumber p);

	/**
	 * @brief Retrieve the present timing data being processed.
	 * @return the timing data pointer. */
	TimingData * GetProcessedTimingData() const;

	/**
	 * @brief Set the timing data to be used with processing.
	 * @param t the timing data. */
	void SetProcessedTimingData(TimingData * t);

	bool IsCourseMode() const;
	bool IsBattleMode() const; // not Rave

	/**
	 * @brief Do we show the W1 timing judgment?
	 * @return true if we do, or false otherwise. */
	bool ShowW1() const;

	BroadcastOnChange<RString>	m_sPreferredSongGroup;		// GROUP_ALL denotes no preferred group
	BroadcastOnChange<RString>	m_sPreferredCourseGroup;	// GROUP_ALL denotes no preferred group
	bool		m_bChangedFailTypeOnScreenSongOptions;	// true if FailType was changed in the song options screen
	BroadcastOnChange<StepsType>				m_PreferredStepsType;
	BroadcastOnChange1D<Difficulty,NUM_PLAYERS>		m_PreferredDifficulty;
	BroadcastOnChange1D<CourseDifficulty,NUM_PLAYERS>	m_PreferredCourseDifficulty;// used in nonstop
	BroadcastOnChange<SortOrder>	m_SortOrder;			// set by MusicWheel
	SortOrder	m_PreferredSortOrder;		// used by MusicWheel
	EditMode	m_EditMode;
	bool		IsEditing() const { return m_EditMode != EditMode_Invalid; }
	/**
	 * @brief Are we in the demonstration or jukebox mode?
	 *
	 * ScreenGameplay often does special things when this is set to true. */
	bool		m_bDemonstrationOrJukebox;
	bool		m_bJukeboxUsesModifiers;
	int			m_iNumStagesOfThisSong;
	/**
	 * @brief Increase this every stage while not resetting on a continue.
	 *
	 * This is cosmetic: it's not use for Stage or Screen branching logic. */
	int				m_iCurrentStageIndex;
	/**
	 * @brief The number of stages available for the players.
	 *
	 * This resets whenever a player joins or continues. */
	int				m_iPlayerStageTokens[NUM_PLAYERS];

	RString sExpandedSectionName;

	static int GetNumStagesMultiplierForSong( const Song* pSong );
	static int GetNumStagesForSongAndStyleType( const Song* pSong, StyleType st );
	int GetNumStagesForCurrentSongAndStepsOrCourse() const;

	void		BeginStage();
	void		CancelStage();
	void		CommitStageStats();
	void		FinishStage();
	int			GetNumStagesLeft( PlayerNumber pn ) const;
	int			GetSmallestNumStagesLeftForAnyHumanPlayer() const;
	bool		IsFinalStageForAnyHumanPlayer() const;
	bool		IsAnExtraStage() const;
	bool		IsAnExtraStageAndSelectionLocked() const;
	bool		IsExtraStage() const;
	bool		IsExtraStage2() const;
	Stage		GetCurrentStage() const;
	int		GetCourseSongIndex() const;
	RString		GetPlayerDisplayName( PlayerNumber pn ) const;

	bool		m_bLoadingNextSong;
	int		GetLoadingCourseSongIndex() const;

	// State Info used during gameplay

	// NULL on ScreenSelectMusic if the currently selected wheel item isn't a Song.
	BroadcastOnChangePtr<Song>	m_pCurSong;
	// The last Song that the user manually changed to.
	Song*		m_pPreferredSong;
	BroadcastOnChangePtr1D<Steps,NUM_PLAYERS> m_pCurSteps;

	// NULL on ScreenSelectMusic if the currently selected wheel item isn't a Course.
	BroadcastOnChangePtr<Course>	m_pCurCourse;
	// The last Course that the user manually changed to.
	Course*		m_pPreferredCourse;
	BroadcastOnChangePtr1D<Trail,NUM_PLAYERS>	m_pCurTrail;

	bool		m_bBackedOutOfFinalStage;

	// Music statistics:
	SongPosition m_Position;

	BroadcastOnChange<bool> m_bGameplayLeadIn;

	// if re-adding noteskin changes in courses, add functions and such here -aj
	void GetAllUsedNoteSkins( vector<RString> &out ) const;

	static const float MUSIC_SECONDS_INVALID;

	void ResetMusicStatistics();	// Call this when it's time to play a new song.  Clears the values above.
	void UpdateSongPosition( float fPositionSeconds, const TimingData &timing, const RageTimer &timestamp = RageZeroTimer, bool bUpdatePlayers = false );
	float GetSongPercent( float beat ) const;

	bool AllAreInDangerOrWorse() const;
	bool OneIsHot() const;

	// Haste
	float	m_fHasteRate; // [-1,+1]; 0 = normal speed
	float	m_fLastHasteUpdateMusicSeconds;
	float	m_fAccumulatedHasteSeconds;

	// Random Attacks & Attack Mines
	vector<RString>		m_RandomAttacks;

	// used in PLAY_MODE_BATTLE
	float	m_fOpponentHealthPercent;

	// used in PLAY_MODE_RAVE
	float	m_fTugLifePercentP1;

	// used in workout
	bool	m_bGoalComplete[NUM_PLAYERS];
	bool	m_bWorkoutGoalComplete;

	/** @brief Primarily called at the end of a song to stop all attacks. */
	void RemoveAllActiveAttacks();
	PlayerNumber GetBestPlayer() const;
	StageResult GetStageResult( PlayerNumber pn ) const;

	/** @brief Call this function when it's time to play a new stage. */
	void ResetStageStatistics();

	// Options stuff
	ModsGroup<SongOptions>	m_SongOptions;

	/**
	 * @brief Did the current game mode change the default Noteskin?
	 *
	 * This is true if it has: see Edit/Sync Songs for a common example.
	 * Note: any mode that wants to use this must set it explicitly. */
	bool m_bDidModeChangeNoteSkin;

	void GetDefaultPlayerOptions( PlayerOptions &po );
	void GetDefaultSongOptions( SongOptions &so );
	void ResetToDefaultSongOptions( ModsLevel l );
	void ApplyPreferredModifiers( PlayerNumber pn, RString sModifiers );
	void ApplyStageModifiers( PlayerNumber pn, RString sModifiers );
	void ClearStageModifiersIllegalForCourse();
	void ResetOptions();

	bool CurrentOptionsDisqualifyPlayer( PlayerNumber pn );
	bool PlayerIsUsingModifier( PlayerNumber pn, const RString &sModifier );

	PlayerOptions::FailType GetPlayerFailType( const PlayerState *pPlayerState ) const;

	// character stuff
	Character* m_pCurCharacters[NUM_PLAYERS];

	bool HasEarnedExtraStage() const { return m_bEarnedExtraStage; }
	EarnedExtraStage GetEarnedExtraStage() const { return CalculateEarnedExtraStage(); }

	// Ranking Stuff
	struct RankingFeat
	{
		enum { SONG, COURSE, CATEGORY } Type;
		Song* pSong;		// valid if Type == SONG
		Steps* pSteps;		// valid if Type == SONG
		Course* pCourse;	// valid if Type == COURSE
		Grade grade;
		int iScore;
		float fPercentDP;
		RString Banner;
		RString Feat;
		RString *pStringToFill;
	};

	void GetRankingFeats( PlayerNumber pn, vector<RankingFeat> &vFeatsOut ) const;
	bool AnyPlayerHasRankingFeats() const;
	void StoreRankingName( PlayerNumber pn, RString name );	// Called by name entry screens
	vector<RString*> m_vpsNamesThatWereFilled;	// filled on StoreRankingName, 

	// Award stuff
	// lowest priority in front, highest priority at the back.
	deque<StageAward> m_vLastStageAwards[NUM_PLAYERS];
	deque<PeakComboAward> m_vLastPeakComboAwards[NUM_PLAYERS];

	// Attract stuff
	int m_iNumTimesThroughAttract;	// negative means play regardless of m_iAttractSoundFrequency setting
	bool IsTimeToPlayAttractSounds() const;
	void VisitAttractScreen( const RString sScreenName );

	// PlayerState
	/** @brief Allow access to each player's PlayerState. */
	PlayerState* m_pPlayerState[NUM_PLAYERS];
	PlayerState* m_pMultiPlayerState[NUM_MultiPlayer];

	// Preferences
	static Preference<bool> m_bAutoJoin;

	// These options have weird interactions depending on m_bEventMode, 
	// so wrap them.
	bool		m_bTemporaryEventMode;
	bool		IsEventMode() const;
	CoinMode	GetCoinMode() const;
	Premium		GetPremium() const;

	// Edit stuff
	
	/**
	 * @brief Is the game right now using Song timing or Steps timing?
	 *
	 * Different options are available depending on this setting. */
	bool m_bIsUsingStepTiming;
	/**
	 * @brief Are we presently in the Step Editor, where some rules apply differently?
	 *
	 * TODO: Find a better way to implement this. */
	bool m_bInStepEditor;
	BroadcastOnChange<StepsType> m_stEdit;
	BroadcastOnChange<CourseDifficulty> m_cdEdit;
	BroadcastOnChangePtr<Steps> m_pEditSourceSteps;
	BroadcastOnChange<StepsType> m_stEditSource;
	BroadcastOnChange<int> m_iEditCourseEntryIndex;
	BroadcastOnChange<RString> m_sEditLocalProfileID;
	Profile* GetEditLocalProfile();

	// Workout stuff
	float GetGoalPercentComplete( PlayerNumber pn );
	bool IsGoalComplete( PlayerNumber pn )	{ return GetGoalPercentComplete( pn ) >= 1; }

	bool m_bDopefish;

	// Lua
	void PushSelf( lua_State *L );

	// Keep extra stage logic internal to GameState.
private:
	EarnedExtraStage	CalculateEarnedExtraStage() const;
	int	m_iAwardedExtraStages[NUM_PLAYERS];
	bool	m_bEarnedExtraStage;

	GameState(const GameState& rhs);
	GameState& operator=(const GameState& rhs);

};

PlayerNumber GetNextHumanPlayer( PlayerNumber pn );
PlayerNumber GetNextEnabledPlayer( PlayerNumber pn );
PlayerNumber GetNextCpuPlayer( PlayerNumber pn );
PlayerNumber GetNextPotentialCpuPlayer( PlayerNumber pn );
MultiPlayer GetNextEnabledMultiPlayer( MultiPlayer mp );

/** @brief A foreach loop to act on each human Player. */
#define FOREACH_HumanPlayer( pn ) for( PlayerNumber pn=GetNextHumanPlayer((PlayerNumber)-1); pn!=PLAYER_INVALID; pn=GetNextHumanPlayer(pn) )
/** @brief A foreach loop to act on each enabled Player. */
#define FOREACH_EnabledPlayer( pn ) for( PlayerNumber pn=GetNextEnabledPlayer((PlayerNumber)-1); pn!=PLAYER_INVALID; pn=GetNextEnabledPlayer(pn) )
/** @brief A foreach loop to act on each CPU Player. */
#define FOREACH_CpuPlayer( pn ) for( PlayerNumber pn=GetNextCpuPlayer((PlayerNumber)-1); pn!=PLAYER_INVALID; pn=GetNextCpuPlayer(pn) )
/** @brief A foreach loop to act on each potential CPU Player. */
#define FOREACH_PotentialCpuPlayer( pn ) for( PlayerNumber pn=GetNextPotentialCpuPlayer((PlayerNumber)-1); pn!=PLAYER_INVALID; pn=GetNextPotentialCpuPlayer(pn) )
/** @brief A foreach loop to act on each Player in MultiPlayer. */
#define FOREACH_EnabledMultiPlayer( mp ) for( MultiPlayer mp=GetNextEnabledMultiPlayer((MultiPlayer)-1); mp!=MultiPlayer_Invalid; mp=GetNextEnabledMultiPlayer(mp) )



extern GameState*	GAMESTATE;	// global and accessable from anywhere in our program

#endif

/**
 * @file
 * @author Chris Danford, Glenn Maynard, Chris Gomez (c) 2001-2004
 * @section LICENSE
 * All rights reserved.
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, and/or sell copies of the Software, and to permit persons to
 * whom the Software is furnished to do so, provided that the above
 * copyright notice(s) and this permission notice appear in all copies of
 * the Software and that both the above copyright notice(s) and this
 * permission notice appear in supporting documentation.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF
 * THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS
 * INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT
 * OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
 * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
 * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 * PERFORMANCE OF THIS SOFTWARE.
 */
