/** SGX

The   sss    gggg x   x   
     s      g      x x    Engine
      sss   g  gg   x
         s  g   g  x x          Copyright 1995-2009.
     ssss    gggg x   x         Steven Goodwin. All Rights Reserved.
    
This version has been released under the GNU Public License v2.0.

Please see the license files for details.

File: sgx/src/utils/audio/simplesequencer.cpp

*/

#include "sgx/sgx.hpp"
#include "utils/audio/audio.hpp"

#include "domains/midi/midi.hpp"


//
// Patch handling
//

sgxAudioSequencerPatch::sgxAudioSequencerPatch() {
	for(tUINT32 i=0;i<AUDIO_SEQUENCER_MAX_NOTES;++i) {
		m_pSample[i] = NULL;
	}
}

tBOOL
sgxAudioSequencerPatch::assignSample(const CSampleData *pSample, const tUINT32 note) {
	tUINT32 n = sgxRange<tUINT32>(note, 0, AUDIO_SEQUENCER_MAX_NOTES);
	m_pSample[n] = pSample;
	return pSample ? TRUE : FALSE;
}

tBOOL
sgxAudioSequencerPatch::assignSample(const CSampleData *pSample, const tUINT32 noteFrom, const tUINT32 noteTo) {
	tUINT32 n1 = sgxRange<tUINT32>(noteFrom, 0, AUDIO_SEQUENCER_MAX_NOTES);
	tUINT32 n2 = sgxRange<tUINT32>(noteTo, 0, AUDIO_SEQUENCER_MAX_NOTES);
	
	for(tUINT32 i=n1;i<=n2;++i) {
		m_pSample[i] = pSample;
	}
	return TRUE;
}

tBOOL	
sgxAudioSequencerPatch::play(const tUINT32 note, const tUINT32 volume) {
	if (m_pSample[note]) {
		CLogicalChannel *pChnl = CSGXAudioEngine::get()->playSound((CSampleData *)m_pSample[note]);
		pChnl->setVolume(volume / 128.0f);
	}
	return TRUE;
}

//
// Callback Handlers
//

tBOOL	
sgxAudioSequencerCallbackHandler::onAudioSequencerClock() {
	return TRUE;
}

tBOOL	
sgxAudioSequencerCallbackHandler::onAudioSequencerBegins() {
	return TRUE;
}

tBOOL	
sgxAudioSequencerCallbackHandler::onAudioSequencerEnd() {
	return TRUE;
}

tBOOL	
sgxAudioSequencerCallbackHandler::onAudioSequencerNoteOn(const sgxAudioSequencerNote &note) {
	SGX_UNUSED(note);
	return TRUE;
}

tBOOL	
sgxAudioSequencerCallbackHandler::onAudioSequencerNoteOff(const sgxAudioSequencerNote &note) {
	SGX_UNUSED(note);
	return TRUE;
}


//
// Sequencer
//
CSimpleSequencer::CSimpleSequencer() :
m_bPlaying(FALSE),
m_pHandler(NULL),
m_bTightQuantize(FALSE),
m_pFile(NULL) {
	for(tUINT32 i=0;i<16;++i) {
		m_ChannelPatch[i] = NULL;
	}
	setTempo(120);
}


CSimpleSequencer::~CSimpleSequencer() {
	midiFileClose(m_pFile);
	delete m_pFile;
	delete []m_pTracks;
	//

}


void	
CSimpleSequencer::setTempo(const tUINT32 tempo) {
	m_iTempo = tempo;
	const tREAL32 ppqn = 240.0f;
	tREAL32 clock = 16.0f;	// 16ths are triggered
	
	m_iClockPeriod = 60.0f / tempo;
	m_iClockPeriod /= clock;
	m_iClockPeriod /= 4;	// assume 4 beats per bar

	//m_iClockPeriod *= ppqn;

}

void	
CSimpleSequencer::setPatch(const tUINT32 idx, const sgxAudioSequencerPatch &patch) {
	m_Patch[idx] = patch;
}

void	
CSimpleSequencer::assignPatchToChannel(const tUINT32 patch, const tUINT32 channel) {
	sgxAssert(channel < 16);
	sgxAssert(patch < AUDIO_SEQUENCER_MAX_PATCHES);
	m_ChannelPatch[channel] = &m_Patch[patch];
}

void	
CSimpleSequencer::setClockQuantize(const tBOOL state) {
	m_bTightQuantize= state;
}

tBOOL
CSimpleSequencer::play() {
	m_bPlaying = TRUE;
	m_fTimecum = 0;
	m_LastAbsTime = 0;
	m_ClockDeltaTime = 64;

	static tBYTE buffer[256];
	for(tUINT32 track=0;track<m_iNumTracks;++track) {
		midiReadInitMessage(&m_pTracks[track].m_Data);
		m_pTracks[track].m_Data.data = &buffer[0];
		m_pTracks[track].m_Data.data_sz = sizeof(buffer);
		m_pTracks[track].m_DataValid = FALSE;
	}
	skipToTime(0);

	return m_pFile ? TRUE : FALSE;
}


tBOOL
CSimpleSequencer::pause() {
	m_bPlaying = FALSE;
	return TRUE;
}


tBOOL
CSimpleSequencer::stop() {
	m_bPlaying = FALSE;
	return TRUE;
}


tBOOL
CSimpleSequencer::update(const tREAL32 telaps) {
	if (m_bPlaying) {
		tREAL32 prev = m_fTimecum;
		m_fTimecum += telaps;
		//
		// TODO Calc dt based on telaps and ppqn and tempo
#if SGX_PLATFORM_WII
		static tREAL32 speed = 2;
#else
		static tREAL32 speed = 1;
#endif
		tUINT32 absTime = m_fTimecum * midiFileGetPPQN(m_pFile) * speed;

		if (absTime - m_LastAbsTime > m_ClockDeltaTime) {
			onAudioSequencerClock();
			m_LastAbsTime = absTime;

			if (m_bTightQuantize) {
				playToTime(absTime);
			}
		}

		if (!m_bTightQuantize) {
			playToTime(absTime);
		}
	}
	return TRUE;
}

void CSimpleSequencer::playToTime(const tUINT32 timecum) {
	processToTime(timecum, TRUE);
}

void CSimpleSequencer::skipToTime(const tUINT32 timecum) {
	processToTime(timecum, FALSE);
}

void CSimpleSequencer::processToTime(const tUINT32 timecum, const tBOOL bIncludeNoteMessages) {
	tUINT32 tracksFinished = 0;
	sgxAudioSequencerNote nd;
	for(tUINT32 track=0;track<m_iNumTracks;++track) {
		tUINT32 rewindPtr = midiReadGetPositionHandle(m_pFile, track);

		if (!m_pTracks[track].m_DataValid) {
			m_pTracks->m_DataValid = midiReadGetNextMessage(m_pFile, track, &m_pTracks[track].m_Data);
		}
		//
		
		while (m_pTracks[track].m_DataValid && m_pTracks[track].m_Data.dwAbsPos <= timecum) {
			const MIDI_MSG &msg = m_pTracks[track].m_Data;
			tMIDI_MSG	type = msg.iType;

			// Process this message
			if (!bIncludeNoteMessages && type == msgNoteOn) {
				midiReadSetPosition(m_pFile, track, rewindPtr);
				return;
			}
			
			if (type == msgNoteOn && msg.MsgData.NoteOn.iVolume == 0) {
				type = msgNoteOff;
			}
			//
			nd.track = track;
			nd.position = msg.dwAbsPos;
			switch(type) {
				case msgNoteOn:
					nd.note = msg.MsgData.NoteOn.iNote;
					nd.channel = msg.MsgData.NoteOn.iChannel;
					nd.volume = msg.MsgData.NoteOn.iVolume;
					onAudioSequencerNoteOn(nd);
					break;
				case msgNoteOff:
					nd.note = msg.MsgData.NoteOn.iNote;
					nd.channel = msg.MsgData.NoteOn.iChannel;
					onAudioSequencerNoteOff(nd);
					break;
				case msgSetProgram:
					assignPatchToChannel(msg.MsgData.ChangeProgram.iProgram, msg.MsgData.ChangeProgram.iChannel);
					break;

				case msgMetaEvent:
					switch(msg.MsgData.MetaEvent.iType) {
						case metaSetTempo:
							setTempo(msg.MsgData.MetaEvent.Data.Tempo.iBPM);
							break;
					}
					break;
			}

			// Get a new one
			rewindPtr = midiReadGetPositionHandle(m_pFile, track);
			m_pTracks[track].m_DataValid = midiReadGetNextMessage(m_pFile, track, &m_pTracks[track].m_Data);
		}
		//
		if (!m_pTracks[track].m_DataValid) {
			++tracksFinished;

		}
	}
	//
	if (tracksFinished == m_iNumTracks) {
		m_bPlaying = FALSE;
		onAudioSequencerEnd();
	}

}

tUINT32
CSimpleSequencer::getDuration(const sgxAudioSequencerNote &note) {
	tUINT32 rewindHandle = midiReadGetPositionHandle(m_pFile, note.track);
	tUINT32 duration = note.position;
	tUINT32 noteoff = 0;

	// TODO: do I need copy the msg data, and replace it, so implict msgs work, etc.
	while(midiReadGetNextMessage(m_pFile, note.track, &m_pTracks[note.track].m_Data)) {
		const MIDI_MSG &msg = m_pTracks[note.track].m_Data;

		// Process this message
		if (msg.iType == msgNoteOn && msg.MsgData.NoteOn.iVolume == 0)  {
			if (msg.MsgData.NoteOn.iNote == note.note && msg.MsgData.NoteOn.iChannel == note.channel) {
				noteoff = msg.dwAbsPos;
			}
		}
		if (msg.iType == msgNoteOff)  {
			if (msg.MsgData.NoteOn.iNote == note.note && msg.MsgData.NoteOn.iChannel == note.channel) {
				noteoff = msg.dwAbsPos;
			}
		}
		//

	}
	midiReadSetPosition(m_pFile, note.track, rewindHandle);
	//
	sgxAssert(noteoff);
	return noteoff - duration;
}


tBOOL	
CSimpleSequencer::onAudioSequencerClock() {
	m_pHandler && m_pHandler->onAudioSequencerClock();
	return TRUE;
}

tBOOL	
CSimpleSequencer::onAudioSequencerEnd() {
	m_pHandler && m_pHandler->onAudioSequencerEnd();
	return TRUE;
}

tBOOL	
CSimpleSequencer::playNote(const sgxAudioSequencerNote &note) {
	return m_ChannelPatch[note.channel] && m_ChannelPatch[note.channel]->play(note.note, note.volume);
}

tBOOL	
CSimpleSequencer::onAudioSequencerNoteOn(const sgxAudioSequencerNote &note) {
	if (!m_pHandler || m_pHandler->onAudioSequencerNoteOn(note)) {
		if (0) {
			static tUINT32 lowest = 128, highest =0;
			sgxTrace("ON : %d  %d %d ", note.note, note.channel, note.volume);
			if (note.channel != 10) {
			lowest = sgxMin(lowest, note.note) ;
			highest = sgxMax(highest, note.note) ;
			}
		}

		playNote(note);
		//tUINT32 dur = getDuration(note);
	}
	return TRUE;
}

tBOOL	
CSimpleSequencer::onAudioSequencerNoteOff(const sgxAudioSequencerNote &note) {
	if (!m_pHandler || !m_pHandler->onAudioSequencerNoteOff(note)) {
		sgxTrace("OFF: %d  %d  ", note.note, note.channel);
	}
	return TRUE;
}


void	
CSimpleSequencer::setHandler(sgxAudioSequencerCallbackHandler *pHandler) {
	m_pHandler = pHandler;
}

tBOOL CSimpleSequencer::loadMIDI(const sgxString &filename) {
MIDI_FILE  *pFile = midiFileOpen(filename.c_str());

	if (!pFile) {
		return FALSE;
	}
	if (m_pFile) {
		midiFileClose(m_pFile);
	}

	m_pFile = pFile;

	m_iNumTracks = midiReadGetNumTracks(m_pFile);
	m_pTracks = new SequencerTrack[m_iNumTracks];

	return TRUE;
}

tBOOL
CSimpleSequencer::loadPatch(sgxAudioSequencerPatch *pMainVoice, const tUINT32 patch, const sgxString &path, const tBOOL scenario) {
SGX_UNUSED(scenario);

	sgxAssert(scenario == 0);

	CSGXAudioEngine *pAud = CSGXAudioEngine::get();

	tBOOL rt = TRUE;

	sgxString str;
	for(tUINT32 i=0;i<127;++i) {
		sgxStringFormat(str, "%s/%d", path.c_str(), i);
		pMainVoice->assignSample(pAud->registerScenarioSound(str), i);
	}
	// TODO: For all empty notes, find closest and map

	setPatch(patch, *pMainVoice);
	return rt;
}

tBOOL
CSimpleSequencer::loadDrumKit(sgxAudioSequencerPatch *pDrumSetPatch, const sgxString &path, const tBOOL scenario) {
SGX_UNUSED(scenario);

	sgxAssert(scenario == 0);

	CSGXAudioEngine *pAud = CSGXAudioEngine::get();
	
	pDrumSetPatch->assignSample(pAud->registerScenarioSound(path+"/kick"), MIDI_DRUM_BASS_DRUM);
	pDrumSetPatch->assignSample(pAud->registerScenarioSound(path+"/snare"), MIDI_DRUM_ELECTRIC_SNARE);
	pDrumSetPatch->assignSample(pAud->registerScenarioSound(path+"/chh"), MIDI_DRUM_CLOSED_HI_HAT);
	pDrumSetPatch->assignSample(pAud->registerScenarioSound(path+"/chh"), MIDI_DRUM_COWBELL);

	return TRUE ;
}
