/* Song - Holds all music metadata and steps for one song. */

// MENUCOLOR SM FILE TAG CODE - NotesLoaderSM.cpp, NotesLoaderDWI.cpp, NotesWriterSM.cpp, Song.h, SongManager.cpp,
//								NotesWriterDWI.cpp
// HIDE SONG UNTIL X STAGES:	NotesLoaderSM.cpp, Song.h, Song.cpp, MusicWheel.cpp, NotesWriterSM.cpp
//								ScreenSelectGroup.cpp
// SPECIFY SORT LIST -	SongManager.cpp, SongManager.h, Song.cpp, Song.h, GameConstantsAndTypes.cpp,
//						GameConstantsAndTypes.h, MusicWheel.cpp, SongUtil.cpp, NotesLoaderSM.cpp,
//						NotesWriterSM.cpp
// LOAD SONGS FROM MEMCARD:	SongManager.cpp, SongManager.h, ProfileManager.h, Song.cpp, Song.h, GameState.cpp,
//							PrefsManager.cpp, PrefsManager.h

#ifndef SONG_H
#define SONG_H

#include "PlayerNumber.h"
#include "GameConstantsAndTypes.h"
#include "Grade.h"
#include "TimingData.h"
#include "Attack.h"

class Steps;
class Style;
class NotesLoader;
class LyricsLoader;
class Profile;
class StepsID;

#define MAX_EDITS_PER_SONG_PER_PROFILE	5
#define MAX_EDITS_PER_SONG				5*NUM_PROFILE_SLOTS

extern const int FILE_CACHE_VERSION;


struct BackgroundChange 
{
	BackgroundChange() { m_fStartBeat=-1; m_fRate=1; m_bFadeLast=false; m_bRewindMovie=false; m_bLoop=true; };
	BackgroundChange( float s, CString n, float r=1.f, bool f=false, bool m=false, bool l=true ) { m_fStartBeat=s; m_sBGName=n; m_fRate=r; m_bFadeLast=f; m_bRewindMovie=m; m_bLoop=l; };
	float m_fStartBeat;
	CString m_sBGName;
	float m_fRate;
	bool m_bFadeLast;
	bool m_bRewindMovie;
	bool m_bLoop;
};

void SortBackgroundChangesArray( vector<BackgroundChange> &arrayBackgroundChanges );

struct LyricSegment
{
	float	m_fStartTime;
	CString m_sLyric;
	RageColor m_Color;
};


class Song
{
	CString m_sSongDir;

public:
	// Set when this song should be displayed in the music wheel:
	enum SelectionDisplay { SHOW_ALWAYS,		// all the time
		   SHOW_ROULETTE,		// only when rouletting
		   SHOW_NEVER,			// never (unless song hiding is turned off)
		   SHOW_1,				// when there is only one stage left
		   SHOW_2,				// when there are only two stages left (etc...)
		   SHOW_3,
		   SHOW_4,
		   SHOW_5,
		   SHOW_6,
		   SHOW_ES,				// only show in extra stage
		   SHOW_OMES,			// only show in one more extra stage
	}		
	m_SelectionDisplay;

	Song();
	~Song();
	void Reset();

	bool IsPlayerSong();

	NotesLoader *MakeLoader( CString sDir ) const;

	bool LoadFromSongDir( CString sDir );
	bool LoadFromPlayerSongDir( CString sDir, CString sGroupName );

	void TidyUpData();	// call after loading to clean up invalid data
	void ReCalculateRadarValuesAndLastBeat();	// called by TidyUpData, and after saving
	void TranslateTitles();	// called by TidyUpData

	void SaveToSMFile( CString sPath, bool bSavingCache );
	void Save();	// saves SM and DWI
	void SaveToCacheFile();
	void SaveToDWIFile();

	const CString &GetSongFilePath() const;
	CString GetCacheFilePath() const;

	void AddAutoGenNotes();
	void AutoGen( StepsType ntTo, StepsType ntFrom );	// create Steps of type ntTo from Steps of type ntFrom
	void RemoveAutoGenNotes();

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

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

	CString m_sGroupName;
	CString m_sGenre;
	CString m_sListSortPosition;

	ProfileSlot	m_LoadedFromProfile;	// PROFILE_SLOT_INVALID if wasn't loaded from a profile
	bool	m_bIsSymLink;

	CString	m_sMainTitle, m_sSubTitle, m_sArtist;
	CString m_sMainTitleTranslit, m_sSubTitleTranslit, m_sArtistTranslit;

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

	/* Returns the transliterated titles, if any; otherwise returns the main titles. */
	CString GetTranslitMainTitle() const { return m_sMainTitleTranslit.size()? m_sMainTitleTranslit: m_sMainTitle; }
	CString GetTranslitSubTitle() const { return m_sSubTitleTranslit.size()? m_sSubTitleTranslit: m_sSubTitle; }
	CString GetTranslitArtist() const { return m_sArtistTranslit.size()? m_sArtistTranslit:m_sArtist; }

	/* "title subtitle" */
	CString GetFullDisplayTitle() const;
	CString GetFullTranslitTitle() const;

	/* This is read and saved, but never actually used. */
	CString	m_sCredit;

	// Song attacks stuff
	bool			m_bHasSongAttacks;	// True if there is an attack
	CStringArray	m_sAttackString;	// String to be written in NotesWriter
	AttackArray		m_Attacks;			// Attack data

	//CStringArray	m_sAttacksName;
	//vector<AttackArray> AttacksList;

	CString	m_sMusicFile;
	float	m_fMusicLengthSeconds;
	float	m_fFirstBeat;
	float	m_fLastBeat;
	float	m_fMusicSampleStartSeconds;
	float	m_fMusicSampleLengthSeconds;
	enum { DISPLAY_ACTUAL, DISPLAY_SPECIFIED, DISPLAY_RANDOM } m_DisplayBPMType;
	float		m_fSpecifiedBPMMin;
	float		m_fSpecifiedBPMMax;	// if a range, then Min != Max

	CString	m_sBannerFile;
	CString m_sLyricsFile;
	CString	m_sBackgroundFile;
	CString	m_sCDTitleFile;

	CString m_sMenuColor;

	CString GetMusicPath() const;
	CString GetBannerPath() const;
	CString	GetLyricsPath() const;
	CString GetBackgroundPath() const;
	CString GetCDTitlePath() const;

	// For avoiding UNLOCKMAN bug when writing cache files
	bool m_bCreatingSongCache;

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

	bool HasMusic() const;
	bool HasBanner() const;
	bool HasBackground() const;
	bool HasCDTitle() const;
	bool HasMovieBackground() const;
	bool HasBGChanges() const;
	bool HasLyrics() const;

	bool Matches(CString sGroup, CString sSong) const;

	TimingData					m_Timing;
	vector<BackgroundChange>	m_BackgroundChanges;	// this must be sorted before gameplay
	vector<BackgroundChange>	m_ForegroundChanges;	// this must be sorted before gameplay
	vector<LyricSegment>		m_LyricSegments;		// this must be sorted before gameplay

	void AddBPMSegment( const BPMSegment &seg ) { m_Timing.AddBPMSegment( seg ); }
	void AddStopSegment( const StopSegment &seg ) { m_Timing.AddStopSegment( seg ); }
	void AddTickSegment( const TickSegment &seg ) { m_Timing.AddTickSegment( seg ); }
	void AddBackgroundChange( BackgroundChange seg );
	void AddForegroundChange( BackgroundChange seg );
	void AddLyricSegment( LyricSegment seg );

	void GetDisplayBpms( DisplayBpms &AddTo ) const;
	CString GetBackgroundAtBeat( float fBeat ) const;

	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 ); }
	void GetBeatAndBPSFromElapsedTime( float fElapsedTime, float &fBeatOut, float &fBPSOut, bool &bFreezeOut ) const { m_Timing.GetBeatAndBPSFromElapsedTime( fElapsedTime, fBeatOut, fBPSOut, bFreezeOut ); }
	float GetBeatFromElapsedTime( float fElapsedTime ) const { return m_Timing.GetBeatFromElapsedTime( fElapsedTime ); }
	float GetElapsedTimeFromBeat( float fBeat ) const { return m_Timing.GetElapsedTimeFromBeat( fBeat ); }
	bool HasSignificantBpmChangesOrStops() const;

	int GetTickcountAtBeat( float fBeat ) const { return m_Timing.GetTickcountAtBeat( fBeat ); }
	void SetTickcountAtBeat( float fBeat, int iTickcount) { m_Timing.SetTickcountAtBeat( fBeat, iTickcount ); }

	bool SongCompleteForStyle( const Style *st ) const;
	bool HasStepsType( StepsType st ) const;
	bool HasStepsTypeAndDifficulty( StepsType st, Difficulty dc ) const;
	const vector<Steps*>& GetAllSteps( StepsType st=STEPS_TYPE_INVALID ) const { return st==STEPS_TYPE_INVALID? m_vpSteps:m_vpStepsByType[st]; }
	void GetSteps( vector<Steps*>& arrayAddTo, StepsType st = STEPS_TYPE_INVALID, Difficulty dc = DIFFICULTY_INVALID, int iMeterLow = -1, int iMeterHigh = -1, const CString &sDescription = "", bool bIncludeAutoGen = true, int Max = -1 ) const;
	Steps* GetStepsByDifficulty( StepsType st, Difficulty dc, bool bIncludeAutoGen = true ) const;
	Steps* GetStepsByMeter( StepsType st, int iMeterLow, int iMeterHigh ) const;
	Steps* GetStepsByDescription( StepsType st, CString sDescription ) const;
	Steps* GetClosestNotes( StepsType st, Difficulty dc ) const;
	bool IsEasy( StepsType st ) const;
	bool IsTutorial() const;
	bool HasEdits( StepsType st ) const;
	SelectionDisplay GetDisplayed() const;
	bool NormallyDisplayed() const;
	bool NeverDisplayed() const;
	bool RouletteDisplayed() const;
	bool HideUntilXStages() const;
	bool ShowInDemonstrationAndRanking() const;

	int ShowWhenXStages() const;

	bool ShowOnExtra1() const;
	bool ShowOnExtra2() const;

	void AddSteps( Steps* pSteps );		// we are responsible for deleting the memory pointed to by pSteps!
	void RemoveSteps( const Steps* pSteps );

	void FreeAllLoadedFromProfiles();
	bool WasLoadedFromProfile() const { return m_LoadedFromProfile != PROFILE_SLOT_INVALID; }
	int GetNumStepsLoadedFromProfile( ProfileSlot slot ) const;
	bool IsEditAlreadyLoaded( Steps* pSteps ) const;

private:
	bool m_bIsPlayerSong;

	void AdjustDuplicateSteps(); // part of TidyUpData
	void DeleteDuplicateSteps( vector<Steps*> &vSteps );

	vector<Steps*> m_vpSteps;
	vector<Steps*> m_vpStepsNotLocked;
	vector<Steps*> m_vpStepsByType[NUM_STEPS_TYPES];
};

#endif

/*
 * (c) 2001-2004 Chris Danford, Glenn Maynard
 * 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.
 */
