#ifndef SONG_H
#define SONG_H

#include "Attack.h"
#include "TimingData.h"
#include "Difficulty.h"
#include "EnumHelper.h"
#include "RageUtil_AutoPtr.h"
#include "RageUtil_CachedObject.h"
#include "RageTypes.h"
#include "Steps.h"
#include <set>

class Style;
class StepsID;
struct lua_State;
struct BackgroundChange;

/** @brief The version of the .ssc file format. */
const static float STEPFILE_VERSION_NUMBER = 0.7f;

/** @brief How many edits for this song can each profile have? */
const int MAX_EDITS_PER_SONG_PER_PROFILE	= 5;
/** @brief How many edits for this song can be available? */
const int MAX_EDITS_PER_SONG			= 5*NUM_ProfileSlot;

extern const int FILE_CACHE_VERSION;

/** @brief The different background layers available. */
enum BackgroundLayer
{
	BACKGROUND_LAYER_1,
	BACKGROUND_LAYER_2,
	//BACKGROUND_LAYER_3, // StepCollection get
	NUM_BackgroundLayer,
	BACKGROUND_LAYER_Invalid
};

/** @brief The different ways of displaying the BPM. */
enum DisplayBPM
{
	DISPLAY_BPM_ACTUAL, /**< Display the song's actual BPM. */
	DISPLAY_BPM_SPECIFIED, /**< Display a specified value or values. */
	DISPLAY_BPM_RANDOM /**< Display a random selection of BPMs. */
};

/** @brief A custom foreach loop for the different background layers. */
#define FOREACH_BackgroundLayer( bl ) FOREACH_ENUM( BackgroundLayer, bl )

/** @brief The different instrument tracks for band type games. */
enum InstrumentTrack
{
	InstrumentTrack_Guitar,
	InstrumentTrack_Rhythm,
	InstrumentTrack_Bass,
	NUM_InstrumentTrack,
	InstrumentTrack_Invalid
};
const RString& InstrumentTrackToString( InstrumentTrack it );
InstrumentTrack StringToInstrumentTrack( const RString& s );

/** @brief The collection of lyrics for the Song. */
struct LyricSegment
{
	float	m_fStartTime; /** @brief When does the lyric show up? */
	RString m_sLyric; /** @brief The lyrics themselves. */
	RageColor m_Color; /** @brief The color of the lyrics. */
};

/** @brief Holds all music metadata and steps for one song. */
class Song
{
	RString m_sSongDir;
public:
	void SetSongDir( const RString sDir ) { m_sSongDir = sDir; }
	RString GetSongDir() { return m_sSongDir; }

	/** @brief When should this song be displayed in the music wheel? */
	enum SelectionDisplay
	{ 
		SHOW_ALWAYS,	/**< always show on the wheel. */
		SHOW_NEVER	/**< never show on the wheel (unless song hiding is turned off). */
	} /** @brief When should this song be displayed in the music wheel? */ m_SelectionDisplay;

	Song();
	~Song();
	void Reset();
	void DetachSteps();
	
	/**
	 * @brief Load a song from the chosen directory.
	 *
	 * This assumes that there is no song present right now.
	 * @param sDir the song directory from which to load. */
	bool LoadFromSongDir( RString sDir );
	// This one takes the effort to reuse Steps pointers as best as it can
	bool ReloadFromSongDir( RString sDir );

	/** @brief Call this after loading a song to clean up invalid data. */
	void TidyUpData();
	
	/**
	 * @brief Get the new radar values, and determine the last beat at the same time.
	 *
	 * This is called by TidyUpData, after saving the Song. */
	void ReCalculateRadarValuesAndLastBeat();
	/**
	 * @brief Translate any titles that aren't in english.
	 *
	 * This is called by TidyUpData. */
	void TranslateTitles();

	/**
	 * @brief Save to the new SSC file format.
	 * @param sPath the path where we're saving the file.
	 * @param bSavingCache a flag to determine if we're saving cache data.
	 */
	bool SaveToSSCFile( RString sPath, bool bSavingCache );
	/** @brief Save to the SSC and SM files no matter what. */
	void Save();
	/** 
	  * @brief Save the current Song to a cache file using the preferred format.
	  * @return its success or failure. */
	bool SaveToCacheFile();
	/**
	 * @brief Save the current Song to a SM file.
	 * @return its success or failure. */
	bool SaveToSMFile();
	/** 
	 * @brief Save the current Song to a DWI file if possible.
	 * @return its success or failure. */
	bool SaveToDWIFile();

	const RString &GetSongFilePath() const;
	RString GetCacheFilePath() const;

	void AddAutoGenNotes();
	/**
	 * @brief Automatically generate steps from one type to another.
	 * @param ntTo the StepsType we're making.
	 * @param ntFrom the StepsType we're generating from.
	 */
	void AutoGen( StepsType ntTo, StepsType ntFrom );
	void RemoveAutoGenNotes();

	// Directory this song data came from:
	const RString &GetSongDir() const { return m_sSongDir; }

	/**
	 * @brief Filename associated with this file.
	 *
	 * This will always have a .SSC extension. If we loaded a .SSC, 
	 * this will point to it, but if we loaded any other type,
	 * this will point to a generated .SSC filename. */
	RString m_sSongFileName;

	/** @brief The group this Song is in. */
	RString m_sGroupName;

	/**
	 * @brief the Profile this came from.
	 *
	 * This is ProfileSlot_Invalid if it wasn't loaded from a profile. */
	ProfileSlot	m_LoadedFromProfile;
	/** @brief Is the song file itself a symlink to another file? */
	bool	m_bIsSymLink;
	bool	m_bEnabled;

	/** @brief The title of the Song. */
	RString	m_sMainTitle; 
	/** @brief The subtitle of the Song, if it exists. */
	RString m_sSubTitle; 
	/** @brief The artist of the Song, if it exists. */
	RString m_sArtist;
	/** @brief The transliterated title of the Song, if it exists. */
	RString m_sMainTitleTranslit;
	/** @brief The transliterated subtitle of the Song, if it exists. */
	RString m_sSubTitleTranslit;
	/** @brief The transliterated artist of the Song, if it exists. */
	RString m_sArtistTranslit;

	/* If PREFSMAN->m_bShowNative is off, these are the same as GetTranslit*
	 * below. Otherwise, they return the main titles. */
	RString GetDisplayMainTitle() const;
	RString GetDisplaySubTitle() const;
	RString GetDisplayArtist() const;

	// Returns the transliterated titles, if any; otherwise returns the main titles.
	/**
	 * @brief Retrieve the transliterated title, or the main title if there is no translit.
	 * @return the proper title. */
	RString GetTranslitMainTitle() const
	{ 
		return m_sMainTitleTranslit.size()? m_sMainTitleTranslit: m_sMainTitle; 
	}
	/**
	 * @brief Retrieve the transliterated subtitle, or the main subtitle if there is no translit.
	 * @return the proper subtitle. */
	RString GetTranslitSubTitle() const 
	{ 
		return m_sSubTitleTranslit.size()? m_sSubTitleTranslit: m_sSubTitle;
	}
	/**
	 * @brief Retrieve the transliterated artist, or the main artist if there is no translit.
	 * @return the proper artist. */
	RString GetTranslitArtist() const 
	{ 
		return m_sArtistTranslit.size()? m_sArtistTranslit:m_sArtist; 
	}

	// "title subtitle"
	RString GetDisplayFullTitle() const;
	RString GetTranslitFullTitle() const;

	/** @brief The version of the song/file. */
	float	m_fVersion;
	/** @brief The genre of the song/file. */
	RString m_sGenre;

	/**
	 * @brief The person who worked with the song file who should be credited.
	 *
	 * This is read and saved, but never actually used. */
	RString	m_sCredit;

	RString m_sOrigin; // song origin (for .ssc format)

	RString	m_sMusicFile;
	RString	m_sInstrumentTrackFile[NUM_InstrumentTrack];

	float	m_fMusicLengthSeconds;
	float	m_fFirstBeat;	// beat of first note
	float	m_fLastBeat;	// beat of last note
	float	m_fSpecifiedLastBeat;	// specified last beat of the song
	float	m_fMusicSampleStartSeconds;
	float	m_fMusicSampleLengthSeconds;
	DisplayBPM m_DisplayBPMType;
	float	m_fSpecifiedBPMMin;
	float	m_fSpecifiedBPMMax;	// if a range, then Min != Max

	RString m_sBannerFile;		// typically a 16:5 ratio graphic (e.g. 256x80)
	//RString m_sJacketFile;	// typically square (e.g. 192x192, 256x256)
	//RString m_sCDFile;		// square (e.g. 128x128 [DDR 1st-3rd])
	//RString m_sDiscFile;		// rectangular (e.g. 256x192 [Pump], 200x150 [MGD3])
	/** @brief The location of the lyrics file, if it exists. */
	RString m_sLyricsFile;
	RString m_sBackgroundFile;
	RString m_sCDTitleFile;

	AttackArray m_Attacks;
	vector<RString>	m_sAttackString;

	RString GetMusicPath() const;
	RString GetInstrumentTrackPath( InstrumentTrack it ) const;
	RString GetBannerPath() const;
	//RString GetJacketPath() const;
	//RString GetCDImagePath() const;
	//RString GetDiscPath() const;
	RString	GetLyricsPath() const;
	RString GetBackgroundPath() const;
	RString GetCDTitlePath() const;

	// For loading only:
	bool m_bHasMusic, m_bHasBanner, m_bHasBackground;

	bool HasMusic() const;
	bool HasInstrumentTrack( InstrumentTrack it ) const;
	/**
	 * @brief Does this song have a banner?
	 * @return true if it does, false otherwise. */
	bool HasBanner() const;
	/**
	 * @brief Does this song have a background?
	 * @return true if it does, false otherwise. */
	bool HasBackground() const;
	//bool HasJacket() const;
	//bool HasCDImage() const;
	//bool HasDisc() const;
	bool HasCDTitle() const;
	bool HasMovieBackground() const;
	bool HasBGChanges() const;
	bool HasLyrics() const;
	bool HasAttacks() const;

	bool Matches(RString sGroup, RString sSong) const;

	/** @brief The Song's TimingData. */
	TimingData m_SongTiming;

	typedef vector<BackgroundChange> 	VBackgroundChange;
private:
	/**
	 * @brief The background changes (sorted by layer) that are for this Song.
	 *
	 * This uses an AutoPtr instead of a raw pointer so that the
	 * auto gen'd copy constructor works correctly.
	 *
	 * This must be sorted before gameplay. */
	AutoPtrCopyOnWrite<VBackgroundChange>	m_BackgroundChanges[NUM_BackgroundLayer];
	/**
	 * @brief The foreground changes that are for this Song.
	 *
	 * This uses an AutoPtr instead of a raw pointer so that the
	 * auto gen'd copy constructor works correctly.
	 *
	 * This must be sorted before gameplay. */
	AutoPtrCopyOnWrite<VBackgroundChange>	m_ForegroundChanges;
public:
	const vector<BackgroundChange>	&GetBackgroundChanges( BackgroundLayer bl ) const;
	vector<BackgroundChange>	&GetBackgroundChanges( BackgroundLayer bl );
	const vector<BackgroundChange>	&GetForegroundChanges() const;
	vector<BackgroundChange>	&GetForegroundChanges();

	/**
	 * @brief The list of LyricSegments.
	 *
	 * This must be sorted before gameplay. */
	vector<LyricSegment>			m_LyricSegments;

/* [splittiming]
	void AddBPMSegment( const BPMSegment &seg ) { m_Timing.AddBPMSegment( seg ); }
	void AddStopSegment( const StopSegment &seg ) { m_Timing.AddStopSegment( seg ); }
	void AddWarpSegment( const WarpSegment &seg ) { m_Timing.AddWarpSegment( seg ); }
*/
	void AddBackgroundChange( BackgroundLayer blLayer, BackgroundChange seg );
	void AddForegroundChange( BackgroundChange seg );
	void AddLyricSegment( LyricSegment seg );

	void GetDisplayBpms( DisplayBpms &AddTo ) const;
	const BackgroundChange &GetBackgroundAtBeat( BackgroundLayer iLayer, float fBeat ) const;

/* [splittiming]
	float GetBPMAtBeat( float fBeat ) const { return m_Timing.GetBPMAtBeat( fBeat ); }
	void SetBPMAtBeat( float fBeat, float fBPM ) { m_Timing.SetBPMAtBeat( fBeat, fBPM ); }
	BPMSegment& GetBPMSegmentAtBeat( float fBeat ) { return m_Timing.GetBPMSegmentAtBeat( fBeat ); }
*/
	
	Steps *CreateSteps();
	void InitSteps(Steps *pSteps);

	/* [splittiming]
	float SongGetBeatFromElapsedTime( float fElapsedTime ) const 
	{
		return m_SongTiming.GetBeatFromElapsedTime( fElapsedTime );
	}
	float StepsGetBeatFromElapsedTime( float fElapsedTime, const Steps &steps ) const 
	{
		return steps.m_Timing.GetBeatFromElapsedTime( fElapsedTime );
	}
	
	float SongGetElapsedTimeFromBeat( float fBeat ) const
	{
		return m_SongTiming.GetElapsedTimeFromBeat( fBeat );
	}
	float StepsGetElapsedTimeFromBeat( float fBeat, const Steps &steps ) const
	{
		return steps.m_Timing.GetElapsedTimeFromBeat( fBeat );
	}
	*/
	
	/* [splittiming]
	float GetBeatFromElapsedTime( float fElapsedTime ) const 
	{ 
		return m_Timing.GetBeatFromElapsedTime( fElapsedTime );
	}
	float GetElapsedTimeFromBeat( float fBeat ) const { return m_Timing.GetElapsedTimeFromBeat( fBeat ); }
	*/
	
	bool HasSignificantBpmChangesOrStops() const;
	float GetStepsSeconds() const;
	bool IsLong() const;
	bool IsMarathon() const;

	bool SongCompleteForStyle( const Style *st ) const;
	bool HasStepsType( StepsType st ) const;
	bool HasStepsTypeAndDifficulty( StepsType st, Difficulty dc ) const;
	const vector<Steps*>& GetAllSteps() const { return m_vpSteps; }
	const vector<Steps*>& GetStepsByStepsType( StepsType st ) const { return m_vpStepsByType[st]; }
	bool IsEasy( StepsType st ) const;
	bool IsTutorial() const;
	bool HasEdits( StepsType st ) const;

	void SetEnabled( bool b ) { m_bEnabled = b; }
	bool GetEnabled() const { return m_bEnabled; }
	/**
	 * @brief Determine if the song should be shown on the MusicWheel normally.
	 *
	 * Songs that are not displayed normally may still be available during
	 * random selection, extra stages, or other special conditions.
	 * @return true if displayed normally, false otherwise. */
	bool NormallyDisplayed() const;
	bool ShowInDemonstrationAndRanking() const;

	/**
	 * @brief Add the chosen Steps to the Song.
	 *
	 * We are responsible for deleting the memory pointed to by pSteps!
	 * @param pSteps the new steps. */
	void AddSteps( Steps* pSteps );
	void DeleteSteps( const Steps* pSteps, bool bReAutoGen = true );

	void FreeAllLoadedFromProfile( ProfileSlot slot = ProfileSlot_Invalid, const set<Steps*> *setInUse = NULL );
	bool WasLoadedFromProfile() const { return m_LoadedFromProfile != ProfileSlot_Invalid; }
	void GetStepsLoadedFromProfile( ProfileSlot slot, vector<Steps*> &vpStepsOut ) const;
	int GetNumStepsLoadedFromProfile( ProfileSlot slot ) const;
	bool IsEditAlreadyLoaded( Steps* pSteps ) const;

	/**
	 * @brief An array of keysound file names (e.g. "beep.wav").
	 *
	 * The index in this array corresponds to the index in TapNote.  If you 
	 * change the index in here, you must change all NoteData too.
	 * Any note that doesn't have a value in the range of this array
	 * means "this note doesn't have a keysound". */
	vector<RString> m_vsKeysoundFile;

	CachedObject<Song> m_CachedObject;

	// Lua
	void PushSelf( lua_State *L );

private:
	/** @brief the Steps that belong to this Song. */
	vector<Steps*> m_vpSteps;
	/** @brief the Steps of a particular StepsType that belong to this Song. */
	vector<Steps*> m_vpStepsByType[NUM_StepsType];
};

#endif

/**
 * @file
 * @author Chris Danford, Glenn Maynard (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.
 */
