#include "global.h"
#include "NotesLoaderBMS.h"
#include "NoteData.h"
#include "GameConstantsAndTypes.h"
#include "RageLog.h"
#include "GameManager.h"
#include "RageException.h"
#include "RageFile.h"
#include "StepsUtil.h"
#include "song.h"
#include "Steps.h"
#include "RageUtil_CharConversions.h"

/*	BMS encoding:     tap-hold
	4&8panel:   Player1     Player2
	Left		11-51		21-61
	Down		13-53		23-63
	Up			15-55		25-65
	Right		16-56		26-66

	6panel:	   Player1
	Left		11-51
	Left+Up		12-52
	Down		13-53
	Up			14-54
	Up+Right	15-55
	Right		16-56

	Notice that 15 and 25 have double meanings!  What were they thinking???
	While reading in, use the 6 panel mapping.  After reading in, detect if 
	only 4 notes are used.  If so, shift the Up+Right column back to the Up
	column

	Hey, folks, BMSes are used for things BESIDES DDR steps,
	and so we're borking up BMSes that are for pnm/bm/etc.

	pnm-nine:   11-15,22-25
	pnm-five:   13-15,21-22
	bm-single5: 11-16
	bm-double5: 11-16,21-26
	bm-single7: 11-16,18-19
	bm-double7: 11-16,18-19,21-26,28-29

	So the magics for these are:
	pnm-nine: nothing >5, with 12, 14, 22 and/or 24
	pnm-five: nothing >5, with 14 and/or 22
	bm-*: can't tell difference between bm-single and dance-solo
		18/19 marks bm-single7, 28/29 marks bm-double7
		bm-double uses 21-26. */

enum BmsTrack
{
	BMS_P1_KEY1 = 0,
	BMS_P1_KEY2,
	BMS_P1_KEY3,
	BMS_P1_KEY4,
	BMS_P1_KEY5,
	BMS_P1_TURN,
	BMS_P1_KEY6,
	BMS_P1_KEY7,
	BMS_P2_KEY1,
	BMS_P2_KEY2,
	BMS_P2_KEY3,
	BMS_P2_KEY4,
	BMS_P2_KEY5,
	BMS_P2_TURN,
	BMS_P2_KEY6,
	BMS_P2_KEY7,
	// max 4 simultaneous auto keysounds
	BMS_AUTO_KEYSOUND_1,
	BMS_AUTO_KEYSOUND_2,
	BMS_AUTO_KEYSOUND_3,
	BMS_AUTO_KEYSOUND_4,
	BMS_AUTO_KEYSOUND_5,
	BMS_AUTO_KEYSOUND_6,
	BMS_AUTO_KEYSOUND_7,
	BMS_AUTO_KEYSOUND_LAST,
	NUM_BMS_TRACKS,
};

const int NUM_NON_AUTO_KEYSOUND_TRACKS = BMS_AUTO_KEYSOUND_1;	
const int NUM_AUTO_KEYSOUND_TRACKS = NUM_BMS_TRACKS - NUM_NON_AUTO_KEYSOUND_TRACKS;	

static bool ConvertRawTrackToTapNote( int iRawTrack, BmsTrack &bmsTrackOut, bool &bIsHoldOut )
{
	if( iRawTrack > 40 )
	{
		bIsHoldOut = true;
		iRawTrack -= 40;
	}
	else
	{
		bIsHoldOut = false;
	}

	switch( iRawTrack )
	{
	case 1:  bmsTrackOut = BMS_AUTO_KEYSOUND_1;	break;
	case 11:	bmsTrackOut = BMS_P1_KEY1;		break;
	case 12:	bmsTrackOut = BMS_P1_KEY2;		break;
	case 13:	bmsTrackOut = BMS_P1_KEY3;		break;
	case 14:	bmsTrackOut = BMS_P1_KEY4;		break;
	case 15:	bmsTrackOut = BMS_P1_KEY5;		break;
	case 16:	bmsTrackOut = BMS_P1_TURN;		break;
	case 18:	bmsTrackOut = BMS_P1_KEY6;		break;
	case 19:	bmsTrackOut = BMS_P1_KEY7;		break;
	case 21:	bmsTrackOut = BMS_P2_KEY1;		break;
	case 22:	bmsTrackOut = BMS_P2_KEY2;		break;
	case 23:	bmsTrackOut = BMS_P2_KEY3;		break;
	case 24:	bmsTrackOut = BMS_P2_KEY4;		break;
	case 25:	bmsTrackOut = BMS_P2_KEY5;		break;
	case 26:	bmsTrackOut = BMS_P2_TURN;		break;
	case 28:	bmsTrackOut = BMS_P2_KEY6;		break;
	case 29:	bmsTrackOut = BMS_P2_KEY7;		break;
	default:	// unknown track
		return false;
	}
	return true;
}


static StepsType DetermineStepsType( int iPlayer, const NoteData &nd )
{
	ASSERT( NUM_BMS_TRACKS == nd.GetNumTracks() );

	bool bTrackHasNote[NUM_NON_AUTO_KEYSOUND_TRACKS];
	ZERO( bTrackHasNote );

	int iLastRow = nd.GetLastRow();
	for( int t=0; t<NUM_NON_AUTO_KEYSOUND_TRACKS; t++ )
	{
		for( int r=0; r<=iLastRow; r++ )
		{
			if( nd.GetTapNote(t, r).type != TapNote::empty )
			{
				bTrackHasNote[t] = true;
				break;				
			}
		}
	}

	int iNumNonEmptyTracks = 0;
	for( int t=0; t<NUM_NON_AUTO_KEYSOUND_TRACKS; t++ )
		if( bTrackHasNote[t] )
			iNumNonEmptyTracks++;

	switch( iPlayer )
	{
	case 1:		// "1 player"		
		/*	Track counts:
			4 - DDR
			5 - PNM 5-key
			6 - DDR Solo, BM 5-key
			8 - BM 7-key
			9 - PNM 9-key */
		switch( iNumNonEmptyTracks ) 
		{
		case 4:		return STEPS_TYPE_DANCE_SINGLE;
		case 5:		return STEPS_TYPE_PNM_FIVE;
		case 6:
			// FIXME: There's no way to distinguish between these types.
			// They use the same tracks.  Assume it's a BM type since they
			// are more common.
			//return STEPS_TYPE_DANCE_SOLO;
			return STEPS_TYPE_BM_SINGLE5;
		case 8:		return STEPS_TYPE_BM_SINGLE7;
		case 9:		return STEPS_TYPE_PNM_NINE;
		default:	return STEPS_TYPE_INVALID;
		}
		break;
	case 2:		// couple/battle
		return STEPS_TYPE_DANCE_COUPLE;
	case 3:		// double
	/*	Track counts:
		8 - DDR Double
		12 - BM Double 5-key
		16 - BM Double 7-key */
		switch( iNumNonEmptyTracks ) 
		{
		case 8:		return STEPS_TYPE_BM_SINGLE7;
		case 12:	return STEPS_TYPE_BM_DOUBLE5;
		case 16:	return STEPS_TYPE_BM_DOUBLE7;
		default:	return STEPS_TYPE_INVALID;
		}
		break;
	default:
		LOG->Warn( "Invalid #PLAYER value %d", iPlayer );
		return STEPS_TYPE_INVALID;
	}
}

float BMSLoader::GetBeatsPerMeasure( const MeasureToTimeSig_t &sigs, int iMeasure )
{
	map<int, float>::const_iterator time_sig = sigs.find( iMeasure );
	if( time_sig != sigs.end() )
		return 4.0f * time_sig->second;
	return 4.0f;
}

int BMSLoader::GetMeasureStartRow( const MeasureToTimeSig_t &sigs, int iMeasureNo )
{
	int iRowNo = 0;
	for( int i = 0; i < iMeasureNo; ++i )
		iRowNo += BeatToNoteRow( GetBeatsPerMeasure(sigs, i) );
	return iRowNo;
}


void BMSLoader::SearchForDifficulty( CString sTag, Steps *pOut )
{
	sTag.ToLower();

	/* Only match "Light" in parentheses. */
	if( sTag.find( "(light" ) != sTag.npos )
		pOut->SetDifficulty( DIFFICULTY_EASY );
	else if( sTag.find( "another" ) != sTag.npos )
		pOut->SetDifficulty( DIFFICULTY_HARD );

	if( sTag.find( "(solo)" ) != sTag.npos )
	{
		pOut->SetDescription( "Edit" );
		pOut->SetDifficulty( DIFFICULTY_EDIT );
	}

	LOG->Trace( "Tag \"%s\" is %s", sTag.c_str(), DifficultyToString(pOut->GetDifficulty()).c_str() );
}

bool BMSLoader::LoadFromBMSFile( const CString &sPath, const NameToData_t &mapNameToData, Steps &out )
{
	LOG->Trace( "Steps::LoadFromBMSFile( '%s' )", sPath.c_str() );

	out.m_StepsType = STEPS_TYPE_INVALID;

	// BMS player code.  Fill in below and use to determine StepsType.
	int iPlayer = -1;
	CString sData;
	if( GetTagFromMap( mapNameToData, "#player", sData ) )
		iPlayer = atoi(sData);
	if( GetTagFromMap( mapNameToData, "#playlevel", sData ) )
		out.SetMeter( atoi(sData) );
	if( GetTagFromMap( mapNameToData, "#title", sData ) )
	{
		sData.MakeLower();

		// extract the Steps description (looks like 'Music <BASIC>')
		int iPosOpenBracket = sData.Find( "<" );
		if( iPosOpenBracket == -1 )
			iPosOpenBracket = sData.Find( "(" );
		int iPosCloseBracket = sData.Find( ">" );
		if( iPosCloseBracket == -1 )
			iPosCloseBracket = sData.Find( ")" );

		if( iPosOpenBracket != -1  &&  iPosCloseBracket != -1 )
			sData = sData.substr( iPosOpenBracket+1, 
			iPosCloseBracket-iPosOpenBracket-1 );
		LOG->Trace( "Steps description found to be '%s'", sData.c_str() );

		out.SetDescription(sData);

		// if there's a 6 in the description, it's probably part of "6panel" or "6-panel"
		if( sData.Find("6") != -1 )
			out.m_StepsType = STEPS_TYPE_DANCE_SOLO;
	}

	NoteData ndNotes;
	ndNotes.SetNumTracks( NUM_BMS_TRACKS );

	/* Read time signatures.  Note that these can differ across files in the same
	 * song. */
	MeasureToTimeSig_t mapMeasureToTimeSig;
	ReadTimeSigs( mapNameToData, mapMeasureToTimeSig );

	NameToData_t::const_iterator it;
	for( it = mapNameToData.lower_bound("#00000"); it != mapNameToData.end(); ++it )
	{
		const CString &sName = it->first;
		if( sName.size() != 6 || sName[0] != '#' || !IsAnInt( sName.substr(1,5) ) )
			 continue;

		// this is step or offset data.  Looks like "#00705"
		int iMeasureNo = atoi( sName.substr(1,3).c_str() );
		int iRawTrackNum = atoi( sName.substr(4,2).c_str() );
		int iRowNo = GetMeasureStartRow( mapMeasureToTimeSig, iMeasureNo );
		float fBeatsPerMeasure = GetBeatsPerMeasure( mapMeasureToTimeSig, iMeasureNo );
		const CString &sNoteData = it->second;

		vector<TapNote> vTapNotes;
		for( int i=0; i+1<sNoteData.GetLength(); i+=2 )
		{
			CString sNoteId = sNoteData.substr(i,2);
			if( sNoteId != "00" )
			{
				TapNote tn = TAP_ORIGINAL_TAP;
				map<CString,int>::const_iterator it = m_mapWavIdToKeysoundIndex.find(sNoteId);
				if( it != m_mapWavIdToKeysoundIndex.end() )
				{
					tn.bKeysound = true;
					tn.iKeysoundIndex = it->second;
				}
				vTapNotes.push_back( tn );
			}
			else
			{
				vTapNotes.push_back( TAP_EMPTY );
			}
		}

		const unsigned uNumNotesInThisMeasure = vTapNotes.size();
		for( unsigned j=0; j<uNumNotesInThisMeasure; j++ )
		{
			if( vTapNotes[j].type != TapNote::empty )
			{
				float fPercentThroughMeasure = (float)j/(float)uNumNotesInThisMeasure;

				int row = iRowNo + (int) ( fPercentThroughMeasure * fBeatsPerMeasure * ROWS_PER_BEAT );

				// some BMS files seem to have funky alignment, causing us to write gigantic cache files.
				// Try to correct for this by quantizing.
				row = Quantize( row, ROWS_PER_MEASURE/64 );
					
				BmsTrack bmsTrack;
				bool bIsHold;
				if( ConvertRawTrackToTapNote(iRawTrackNum, bmsTrack, bIsHold) )
				{
					TapNote tn = vTapNotes[j];
					if( bmsTrack == BMS_AUTO_KEYSOUND_1 )
					{
						tn.type = TapNote::autoKeysound;

						// shift the auto keysound as far right as possible
						int iLastEmptyTrack = -1;
						if( ndNotes.GetTapLastEmptyTrack(row,iLastEmptyTrack)  &&
							iLastEmptyTrack >= BMS_AUTO_KEYSOUND_1 )
						{
							bmsTrack = (BmsTrack)iLastEmptyTrack;
						}
						else
						{
							// no room for this note.  Drop it.
							continue;
						}
					}
					else if( bIsHold )
					{
						tn.type = TapNote::hold_head;
					}
					else
					{
						tn.type = TapNote::tap;
					}

					ndNotes.SetTapNote( bmsTrack, row, tn );
				}
			}
		}
	}

	// we're done reading in all of the BMS values

	out.m_StepsType = DetermineStepsType( iPlayer, ndNotes );
	if( out.m_StepsType == STEPS_TYPE_INVALID )
	{
		LOG->Warn( "Couldn't determine note type of file '%s'", sPath.c_str() );
		return false;
	}


	// shift all of the autokeysound tracks onto the main tracks
	for( int t=BMS_AUTO_KEYSOUND_1; t<BMS_AUTO_KEYSOUND_1+NUM_AUTO_KEYSOUND_TRACKS; t++ )
	{
		FOREACH_NONEMPTY_ROW_IN_TRACK( ndNotes, t, row )
		{
			TapNote tn = ndNotes.GetTapNote( t, row );
			int iEmptyTrack;
			if( ndNotes.GetTapFirstEmptyTrack(row, iEmptyTrack) )
			{
				ndNotes.SetTapNote( iEmptyTrack, row, tn );
				ndNotes.SetTapNote( t, row, TAP_EMPTY );
			}
			else
			{
				LOG->Warn( "No room to shift." );
			}
		}
	}


	int iNumNewTracks = GameManager::StepsTypeToNumTracks( out.m_StepsType );
	vector<int> iTransformNewToOld;
	iTransformNewToOld.resize( iNumNewTracks, -1 );

	switch( out.m_StepsType )
	{
	case STEPS_TYPE_DANCE_SINGLE:
		iTransformNewToOld[0] = BMS_P1_KEY1;
		iTransformNewToOld[1] = BMS_P1_KEY3;
		iTransformNewToOld[2] = BMS_P1_KEY5;
		iTransformNewToOld[3] = BMS_P1_TURN;
		break;
	case STEPS_TYPE_DANCE_DOUBLE:
	case STEPS_TYPE_DANCE_COUPLE:
		iTransformNewToOld[0] = BMS_P1_KEY1;
		iTransformNewToOld[1] = BMS_P1_KEY3;
		iTransformNewToOld[2] = BMS_P1_KEY5;
		iTransformNewToOld[3] = BMS_P1_TURN;
		iTransformNewToOld[4] = BMS_P2_KEY1;
		iTransformNewToOld[5] = BMS_P2_KEY3;
		iTransformNewToOld[6] = BMS_P2_KEY5;
		iTransformNewToOld[7] = BMS_P2_TURN;
		break;
	case STEPS_TYPE_DANCE_SOLO:
	case STEPS_TYPE_BM_SINGLE5:
		// Hey! Why are these exactly the same? :-)
		iTransformNewToOld[0] = BMS_P1_KEY1;
		iTransformNewToOld[1] = BMS_P1_KEY2;
		iTransformNewToOld[2] = BMS_P1_KEY3;
		iTransformNewToOld[3] = BMS_P1_KEY4;
		iTransformNewToOld[4] = BMS_P1_KEY5;
		iTransformNewToOld[5] = BMS_P1_TURN;
		break;
	case STEPS_TYPE_PNM_FIVE:
		iTransformNewToOld[0] = BMS_P1_KEY3;
		iTransformNewToOld[1] = BMS_P1_KEY4;
		iTransformNewToOld[2] = BMS_P1_KEY5;
		// fix these columns!
		iTransformNewToOld[3] = BMS_P2_KEY2;
		iTransformNewToOld[4] = BMS_P2_KEY3;
		break;
	case STEPS_TYPE_PNM_NINE:
		iTransformNewToOld[0] = BMS_P1_KEY1; // lwhite
		iTransformNewToOld[1] = BMS_P1_KEY2; // lyellow
		iTransformNewToOld[2] = BMS_P1_KEY3; // lgreen
		iTransformNewToOld[3] = BMS_P1_KEY4; // lblue
		iTransformNewToOld[4] = BMS_P1_KEY5; // red
		// fix these columns!
		iTransformNewToOld[5] = BMS_P2_KEY2; // rblue
		iTransformNewToOld[6] = BMS_P2_KEY3; // rgreen
		iTransformNewToOld[7] = BMS_P2_KEY4; // ryellow
		iTransformNewToOld[8] = BMS_P2_KEY5; // rwhite
		break;
	case STEPS_TYPE_BM_DOUBLE5:
		iTransformNewToOld[0] = BMS_P1_KEY1;
		iTransformNewToOld[1] = BMS_P1_KEY2;
		iTransformNewToOld[2] = BMS_P1_KEY3;
		iTransformNewToOld[3] = BMS_P1_KEY4;
		iTransformNewToOld[4] = BMS_P1_KEY5;
		iTransformNewToOld[5] = BMS_P1_TURN;
		iTransformNewToOld[6] = BMS_P2_KEY1;
		iTransformNewToOld[7] = BMS_P2_KEY2;
		iTransformNewToOld[8] = BMS_P2_KEY3;
		iTransformNewToOld[9] = BMS_P2_KEY4;
		iTransformNewToOld[10] = BMS_P2_KEY5;
		iTransformNewToOld[11] = BMS_P2_TURN;
		break;
	case STEPS_TYPE_BM_SINGLE7:
		iTransformNewToOld[0] = BMS_P1_KEY1;
		iTransformNewToOld[1] = BMS_P1_KEY2;
		iTransformNewToOld[2] = BMS_P1_KEY3;
		iTransformNewToOld[3] = BMS_P1_KEY4;
		iTransformNewToOld[4] = BMS_P1_KEY5;
		iTransformNewToOld[5] = BMS_P1_KEY6;
		iTransformNewToOld[6] = BMS_P1_KEY7;
		iTransformNewToOld[7] = BMS_P1_TURN;
		break;
	case STEPS_TYPE_BM_DOUBLE7:
		iTransformNewToOld[0] = BMS_P1_KEY1;
		iTransformNewToOld[1] = BMS_P1_KEY2;
		iTransformNewToOld[2] = BMS_P1_KEY3;
		iTransformNewToOld[3] = BMS_P1_KEY4;
		iTransformNewToOld[4] = BMS_P1_KEY5;
		iTransformNewToOld[5] = BMS_P1_KEY6;
		iTransformNewToOld[6] = BMS_P1_KEY7;
		iTransformNewToOld[7] = BMS_P1_TURN;
		iTransformNewToOld[8] = BMS_P2_KEY1;
		iTransformNewToOld[9] = BMS_P2_KEY2;
		iTransformNewToOld[10] = BMS_P2_KEY3;
		iTransformNewToOld[11] = BMS_P2_KEY4;
		iTransformNewToOld[12] = BMS_P2_KEY5;
		iTransformNewToOld[13] = BMS_P2_KEY6;
		iTransformNewToOld[14] = BMS_P2_KEY7;
		iTransformNewToOld[15] = BMS_P2_TURN;
		break;
	default:
		ASSERT(0);
	}

	NoteData noteData2;
	noteData2.SetNumTracks( iNumNewTracks );
	noteData2.LoadTransformed( ndNotes, iNumNewTracks, &*iTransformNewToOld.begin() );

	out.SetNoteData( noteData2 );

	out.TidyUpData();

	return true;
}

void BMSLoader::GetApplicableFiles( CString sPath, CStringArray &out )
{
	GetDirListing( sPath + CString("*.bms"), out );
	GetDirListing( sPath + CString("*.bme"), out );
}

bool BMSLoader::ReadBMSFile( const CString &sPath, NameToData_t &mapNameToData )
{
	RageFile file;
	if( !file.Open(sPath) )
		RageException::Throw( "Failed to open \"%s\" for reading: %s", sPath.c_str(), file.GetError().c_str() );
	while( !file.AtEOF() )
	{
		CString line;
		if( file.GetLine( line ) == -1 )
		{
			LOG->Warn( "Error reading \"%s\": %s", sPath.c_str(), file.GetError().c_str() );
			return false;
		}

		StripCrnl(line);

		// BMS value names can be separated by a space or a colon.
		size_t iIndexOfSeparator = line.find_first_of( ": " );
		CString value_name = line.substr( 0, iIndexOfSeparator );
		CString value_data;
		if( iIndexOfSeparator != line.npos )
			value_data = line.substr( iIndexOfSeparator+1 );

		value_name.MakeLower();
		mapNameToData.insert( pair<CString,CString>(value_name, value_data) );
	}

	return true;
}

bool BMSLoader::GetTagFromMap( const NameToData_t &mapNameToData, const CString &sName, CString &sOut )
{
	NameToData_t::const_iterator it;
	it = mapNameToData.find( sName );
	if( it == mapNameToData.end() )
		return false;

	sOut = it->second;
	
	return true;
}

enum
{
	BMS_TRACK_TIME_SIG = 2,
	BMS_TRACK_BPM = 3,
	BMS_TRACK_BPM_REF = 8,
	BMS_TRACK_STOP = 9
};

void BMSLoader::ReadGlobalTags( const NameToData_t &mapNameToData, Song &out )
{
	CString sData;
	if( GetTagFromMap( mapNameToData, "#title", sData ) )
	{
		// strip Steps type out of description leaving only song title - looks like 'B4U <BASIC>'
		size_t iIndex = sData.find_last_of('<');
		if( iIndex == sData.npos )
			iIndex = sData.find_last_of('(');
		if( iIndex != sData.npos )
		{
			sData = sData.Left( iIndex );
			GetMainAndSubTitlesFromFullTitle( sData, out.m_sMainTitle, out.m_sSubTitle );
		}
		else
			out.m_sMainTitle = sData;
	}

	GetTagFromMap( mapNameToData, "#artist", out.m_sArtist );
	GetTagFromMap( mapNameToData, "#backbmp", out.m_sBackgroundFile );
	GetTagFromMap( mapNameToData, "#wav", out.m_sMusicFile );

	if( GetTagFromMap( mapNameToData, "#bpm", sData ) )
	{
		BPMSegment newSeg( 0, strtof(sData, NULL) );
		out.AddBPMSegment( newSeg );

		LOG->Trace( "Inserting new BPM change at beat %f, BPM %f", newSeg.m_fStartBeat, newSeg.m_fBPM );
	}

	NameToData_t::const_iterator it;
	for( it = mapNameToData.lower_bound("#wav"); it != mapNameToData.end(); ++it )
	{
		const CString &sName = it->first;

		if( sName.size() != 6 || sName.Left(4) != "#wav" )
			continue;

		// this is keysound file name.  Looks like "#WAV1A"
		CString sData = it->second;
		CString sWavID = sName.Right(2);

		/* Due to bugs in some programs, many BMS files have a "WAV" extension
		 * on files in the BMS for files that actually have some other extension.
		 * Do a search.  Don't do a wildcard search; if sData is "song.wav",
		 * we might also have "song.png", which we shouldn't match. */
		if( !IsAFile(out.GetSongDir()+sData) )
		{
			const char *exts[] = { "ogg", "wav", "mp3", NULL }; // XXX: stop duplicating these everywhere
			for( unsigned i = 0; exts[i] != NULL; ++i )
			{
				CString fn = SetExtension( sData, exts[i] );
				if( IsAFile(out.GetSongDir()+fn) )
				{
					sData = fn;
					break;
				}
			}
		}
		if( !IsAFile(out.GetSongDir()+sData) )
			LOG->Trace( "Song \"%s\" references key \"%s\" that can't be found",
				m_sDir.c_str(), sData.c_str() );

		sWavID.MakeUpper();		// HACK: undo the MakeLower()
		out.m_vsKeysoundFile.push_back( sData );
		m_mapWavIdToKeysoundIndex[ sWavID ] = out.m_vsKeysoundFile.size()-1;
		LOG->Trace( "Inserting keysound index %lu '%s'", out.m_vsKeysoundFile.size()-1, sWavID.c_str() );
	}

	/* Time signature tags affect all other global timing tags, so read them first. */
	MeasureToTimeSig_t mapMeasureToTimeSig;
	ReadTimeSigs( mapNameToData, mapMeasureToTimeSig );

	for( it = mapNameToData.lower_bound("#00000"); it != mapNameToData.end(); ++it )
	{
		const CString &sName = it->first;
		if( sName.size() != 6 || sName[0] != '#' || !IsAnInt( sName.substr(1,5) ) )
			 continue;
		// this is step or offset data.  Looks like "#00705"
		int iMeasureNo	= atoi( sName.substr(1,3).c_str() );
		int iBMSTrackNo	= atoi( sName.substr(4,2).c_str() );
		int iStepIndex = GetMeasureStartRow( mapMeasureToTimeSig, iMeasureNo );
		float fBeatsPerMeasure = GetBeatsPerMeasure( mapMeasureToTimeSig, iMeasureNo );
		int iRowsPerMeasure = BeatToNoteRow( fBeatsPerMeasure );

		CString sData = it->second;
		int totalPairs = sData.size() / 2;
		for( int i = 0; i < totalPairs; ++i )
		{
			CString sPair = sData.substr(i*2,2);

			int iVal = 0;
			if( sscanf( sPair, "%x", &iVal ) == 0 || iVal == 0 )
				continue;

			int iRow = iStepIndex + (i * iRowsPerMeasure) / totalPairs;
			float fBeat = NoteRowToBeat( iRow );

			switch( iBMSTrackNo )
			{
			case BMS_TRACK_BPM:
				out.SetBPMAtBeat( fBeat, (float) iVal );
				LOG->Trace( "Inserting new BPM change at beat %f, BPM %i", fBeat, iVal );
				break;

			case BMS_TRACK_BPM_REF:
			{
				CString sTagToLookFor = ssprintf( "#bpm%02x", iVal );
				CString sBPM;
				if( GetTagFromMap( mapNameToData, sTagToLookFor, sBPM ) )
				{
					float fBPM = strtof( sBPM, NULL );

					BPMSegment newSeg( fBeat, fBPM );
					out.AddBPMSegment( newSeg );
					LOG->Trace( "Inserting new BPM change at beat %f, BPM %f", newSeg.m_fStartBeat, newSeg.m_fBPM );
				}
				else
					LOG->Warn( "Couldn't find tag '%s' in '%s'.", sTagToLookFor.c_str(), m_sDir.c_str() );
				break;
			}
			case BMS_TRACK_STOP:
			{
				CString sTagToLookFor = ssprintf( "#stop%02x", iVal );
				CString sBeats;
				if( GetTagFromMap( mapNameToData, sTagToLookFor, sBeats ) )
				{
					// find the BPM at the time of this freeze
					float fBPS = out.m_Timing.GetBPMAtBeat(fBeat) / 60.0f;
					float fBeats = strtof(sBeats,NULL) / 48.0f;
					float fFreezeSecs = fBeats / fBPS;

					StopSegment newSeg( fBeat, fFreezeSecs );
					out.AddStopSegment( newSeg );
					LOG->Trace( "Inserting new Freeze at beat %f, secs %f", newSeg.m_fStartBeat, newSeg.m_fStopSeconds );
				}
				else
					LOG->Warn( "Couldn't find tag '%s' in '%s'.", sTagToLookFor.c_str(), m_sDir.c_str() );
				break;
			}
			}
		}
			
		switch( iBMSTrackNo )
		{
		case BMS_TRACK_BPM_REF:
		{
			// XXX: offset
			int iBPMNo;
			sscanf( sData, "%x", &iBPMNo );	// data is in hexadecimal

			CString sBPM;
			CString sTagToLookFor = ssprintf( "#bpm%02x", iBPMNo );
			if( GetTagFromMap( mapNameToData, sTagToLookFor, sBPM ) )
			{
				float fBPM = strtof( sBPM, NULL );

				BPMSegment newSeg( NoteRowToBeat(iStepIndex), fBPM );
				out.AddBPMSegment( newSeg );
				LOG->Trace( "Inserting new BPM change at beat %f, BPM %f", newSeg.m_fStartBeat, newSeg.m_fBPM );
			}
			else
				LOG->Warn( "Couldn't find tag '%s' in '%s'.", sTagToLookFor.c_str(), m_sDir.c_str() );

			break;
		}
		}
	}
}

void BMSLoader::ReadTimeSigs( const NameToData_t &mapNameToData, MeasureToTimeSig_t &out )
{
	NameToData_t::const_iterator it;
	for( it = mapNameToData.lower_bound("#00000"); it != mapNameToData.end(); ++it )
	{
		const CString &sName = it->first;
		if( sName.size() != 6 || sName[0] != '#' || !IsAnInt( sName.substr(1,5) ) )
			 continue;

		// this is step or offset data.  Looks like "#00705"
		const CString &sData = it->second;
		int iMeasureNo	= atoi( sName.substr(1,3).c_str() );
		int iBMSTrackNo	= atoi( sName.substr(4,2).c_str() );
		if( iBMSTrackNo == BMS_TRACK_TIME_SIG )
			out[iMeasureNo] = (float) atof(sData);
	}
}

bool BMSLoader::LoadFromDir( CString sDir, Song &out )
{
	LOG->Trace( "Song::LoadFromBMSDir(%s)", sDir.c_str() );

	ASSERT( out.m_vsKeysoundFile.empty() );

	m_sDir = sDir;
	CStringArray arrayBMSFileNames;
	GetApplicableFiles( sDir, arrayBMSFileNames );

	/* We should have at least one; if we had none, we shouldn't have been
	 * called to begin with. */
	ASSERT( arrayBMSFileNames.size() );

	/* Read all BMS files. */
	vector<NameToData_t> aBMSData;
	for( unsigned i=0; i<arrayBMSFileNames.size(); i++ )
	{
		aBMSData.push_back( NameToData_t() );
		ReadBMSFile( out.GetSongDir() + arrayBMSFileNames[i], aBMSData.back() );
	}

	/* Create a Steps for each. */
	vector<Steps*> apSteps;
	for( unsigned i=0; i<arrayBMSFileNames.size(); i++ )
		apSteps.push_back( new Steps );

	/* Try to figure out the difficulty of each file. */
	for( unsigned i=0; i<arrayBMSFileNames.size(); i++ )
	{
		Steps *pSteps = apSteps[i];
		pSteps->SetDifficulty( DIFFICULTY_MEDIUM );
		CString sTag;
		if( GetTagFromMap( aBMSData[i], "#title", sTag ) )
			SearchForDifficulty( sTag, pSteps );
		if( GetTagFromMap( aBMSData[i], "#genre", sTag ) )
			SearchForDifficulty( sTag, pSteps );
	}

	/* Prefer to read global tags from a DIFFICULTY_MEDIUM file.  These tend to
	 * have the least cruft in the #TITLE tag, so it's more likely to get a clean
	 * title. */
	int iMainDataIndex = 0;
	for( unsigned i=1; i<apSteps.size(); i++ )
		if( apSteps[i]->GetDifficulty() == DIFFICULTY_MEDIUM )
			iMainDataIndex = i;

	ReadGlobalTags( aBMSData[iMainDataIndex], out );


	// Now that we've parsed the keysound data, load the Steps from the rest 
	// of the .bms files.
	for( unsigned i=0; i<arrayBMSFileNames.size(); i++ )
	{
		Steps* pNewNotes = apSteps[i];
		const bool ok = LoadFromBMSFile( out.GetSongDir() + arrayBMSFileNames[i], aBMSData[i], *pNewNotes );
		if( ok )
			out.AddSteps( pNewNotes );
		else
			delete pNewNotes;
	}

	SlideDuplicateDifficulties( out );

	ConvertString( out.m_sMainTitle, "japanese" );
	ConvertString( out.m_sArtist, "japanese" );


	return true;
}


void BMSLoader::SlideDuplicateDifficulties( Song &p )
{
	/* BMS files have to guess the Difficulty from the meter; this is inaccurate,
	 * and often leads to duplicates.  Slide duplicate difficulties upwards.  We
	 * only do this with BMS files, since a very common bug was having *all*
	 * difficulties slid upwards due to (for example) having two beginner steps.
	 * We do a second pass in Song::TidyUpData to eliminate any remaining duplicates
	 * after this. */
	for( int i=0; i<NUM_STEPS_TYPES; i++ )
	{
		StepsType st = (StepsType)i;

		for( unsigned j=0; j<=DIFFICULTY_CHALLENGE; j++ ) // not DIFFICULTY_EDIT
		{
			Difficulty dc = (Difficulty)j;

			vector<Steps*> vSteps;
			p.GetSteps( vSteps, st, dc );

			StepsUtil::SortNotesArrayByDifficulty( vSteps );
			for( unsigned k=1; k<vSteps.size(); k++ )
			{
				Steps* pSteps = vSteps[k];
			
				Difficulty dc2 = min( (Difficulty)(dc+1), DIFFICULTY_CHALLENGE );
				pSteps->SetDifficulty( dc2 );
			}
		}
	}
}

/*
 * (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.
 */
