/***************************************************************************
 * tabsongplayer.cpp: implementation of TabSongPlayer class
 *
 * This file is part of KTabEdit, a KDE tabulature editor
 *
 * copyright (C) 2003-2006 the KTabEdit development team <mrgwen@gmail.com>
 ***************************************************************************/

/***************************************************************************
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * See the file COPYING for more information.
 ***************************************************************************/

#include "tabsongplayer.h"
#include "util/assert.h"

#include <QtDebug>
#include <QChar>

#include "tablature/tabbar.h"
#include "tablature/tabnote.h"
#include "tablature/tabtimes.h"
#include "tablature/tabtrack.h"
#include "tablatureModel/tabsongmodel.h"
#include "playbacktracker.h"
#include "settings.h"

TabSongPlayer::TabSongPlayer(const TabSongModel* tempSong, PlaybackTracker* tracker)
: QThread(),
  mTabSong(tempSong),
  scheduler(0),
  mPlaybackTracker(tracker),
  mSettings(Settings::getInstance()),
  stop(false)
{
	PRECONDITION( mPlaybackTracker != 0 );
}

TabSongPlayer::~TabSongPlayer()
{
}

void TabSongPlayer::setScheduler(TSE3::MidiScheduler* midiScheduler)
{
	scheduler = midiScheduler;
}

void TabSongPlayer::stopPlaying()
{
	stop = true;
}

inline long TabSongPlayer::calcDuration(const TabTime& time) const
{
	long temp = 0;
	const Duration duration = time.duration();
	
	switch (duration) {
	case NoneDuration:
		break;
	case SixtyFourth:
		temp = (TSE3::Clock::PPQN / 32);
		break;
	case HundredTwentyEighth:
		temp = (TSE3::Clock::PPQN / 16);
		break;
	case ThirthSecond:  // 1/32, triple croche
		temp = (TSE3::Clock::PPQN / 8);
		break;
	case Sixteenth:  // 1/16, double croche
		temp = (TSE3::Clock::PPQN / 4);
		break;
	case Eighth:  // 1/8, croche
		temp = (TSE3::Clock::PPQN / 2);
		break;
	case Quarter:  // 1/4 - a long vertical line, so we need to find the highest note, noire
		temp = TSE3::Clock::PPQN;
		break;
	case Half: // 1/2, blanche
		temp = (2 * TSE3::Clock::PPQN);
		break;
	case Whole: // whole, ronde
		temp = (4 * TSE3::Clock::PPQN);
		break;
	}
	
	if (time.isDotted() == true)
		temp = temp + temp / 2;
	
	// For the triplet double time duration
	//
	if (time.nTuplet() == 2)
		temp*=2;
	else if (time.nTuplet() > 2 && time.nTuplet() < 8)
		temp*=4;
	else if (time.nTuplet() > 8 && time.nTuplet() < 12)
		temp*=8;
	
	return temp / ((int)time.nTuplet() + 1);
}

inline long TabSongPlayer::calcLink(uint barNb, uint timeNb, uint chord, const TabTrack* tempTrack) const
{
	long time = 0;
	long restTime = 0;
	
	// Check in the next time if the next note is linked with this note
	//
	for (uint i = barNb; i < tempTrack->size(); i++) {
		for (uint j = (i == barNb ? timeNb + 1 : 0); j < tempTrack->barSize(i); j++) {
			if (tempTrack->isTieNote(i, j, chord)) {
				time+=calcDuration(*tempTrack->time(i, j)) + restTime;
				restTime = 0;
			} else
				if (tempTrack->isRest(i, j))
					restTime+=calcDuration(*tempTrack->time(i, j));
				else
					return time;
		}
	}
	
	return time;
}

inline bool TabSongPlayer::checkLinkAndRest(uint barNb, uint timeNb, uint chord, const TabTrack* tempTrack) const
{
	// Check in the next time if the next note is linked with this note
	//
	for (uint i = barNb; i < tempTrack->size(); i++) {
		for (uint j = (i == barNb ? timeNb + 1 : 0); j < tempTrack->barSize(i); j++) {
			if (tempTrack->isTieNote(i, j, chord))
				return false;
			else if (!tempTrack->isRest(i, j))
					return true;
		}
	}
	
	return false;
}

void TabSongPlayer::insertMidiCommand(TSE3::PhraseEdit* phraseEdit, int status, int channel, int data1)
{
	phraseEdit->insert(TSE3::MidiEvent(
				TSE3::MidiCommand(
					status, channel, mSettings->midiPortNumber(), data1)
				, 0));
}

void TabSongPlayer::insertMidiCommand(TSE3::PhraseEdit* phraseEdit, int status, int channel, int data1, int data2)
{
	phraseEdit->insert(TSE3::MidiEvent(
				TSE3::MidiCommand(
					status, channel, mSettings->midiPortNumber(), data1, data2)
				, 0));
}

void TabSongPlayer::insertMidiCommand(TSE3::PhraseEdit* phraseEdit, int status, int channel, int data1, int data2, int time)
{
	phraseEdit->insert(TSE3::MidiEvent(
				TSE3::MidiCommand(
					status, channel, mSettings->midiPortNumber(), data1, data2)
				, time));
}

void TabSongPlayer::insertMidiCommand(TSE3::PhraseEdit* phraseEdit, int status, int channel, int data1, int data2, int time, int velocity, int nextTime)
{
	phraseEdit->insert(TSE3::MidiEvent(
				TSE3::MidiCommand(
					status, channel, mSettings->midiPortNumber(), data1, data2)
				, time, velocity, nextTime));
}

void TabSongPlayer::createMidiHeaders(TSE3::PhraseEdit* phraseEdit)
{
	uint i = 0, pos;
	bool drumTrack = mTabSong->isDrumTrack();
	uint drumTrackNb = 0;
	const TabTrack* track = 0;
	
	if (drumTrack)
		drumTrackNb = mTabSong->drumTrack();
	
	for (TabSongModel::const_iterator it = mTabSong->begin(); it != mTabSong->end(); i++, ++it) {
		track = *it;
		
		pos = i;
		
		if (drumTrack) {
			if (drumTrackNb != 9) {
				if (drumTrackNb < 9) {
					if (i == drumTrackNb)
						pos = 9;
					else if (i > drumTrackNb && i < 9)
						pos = i - 1;
					else if (i >= 9)
						pos = i + 1;
				} else {
					if (i == drumTrackNb)
						pos = 9;
					else if (i >= 9 && i < drumTrackNb)
						pos = i + 1;
				}
			}
		}
		
		// Select the instrument
		//
		/*insertMidiCommand(phraseEdit, TSE3::MidiCommand_ProgramChange, pos, track->patch());
		insertMidiCommand(phraseEdit, TSE3::MidiCommand_ControlChange, pos, TSE3::MidiControl_ReverbDepth, track->reverb());
		insertMidiCommand(phraseEdit, TSE3::MidiCommand_ControlChange, pos, TSE3::MidiControl_PanMSB, track->balance());
		insertMidiCommand(phraseEdit, TSE3::MidiCommand_ControlChange, pos, TSE3::MidiControl_ChorusDepth, track->chorus());
		insertMidiCommand(phraseEdit, TSE3::MidiCommand_ControlChange, pos, TSE3::MidiControl_ChannelVolumeMSB, track->volume() + 20);*/
		
		phraseEdit->insert(TSE3::MidiEvent(
					TSE3::MidiCommand(
						TSE3::MidiCommand_ProgramChange, 
						pos, mSettings->midiPortNumber(), track->patch())
					, 0)
				);
		
		phraseEdit->insert(TSE3::MidiEvent(
					TSE3::MidiCommand(
						TSE3::MidiCommand_ControlChange,
						pos, mSettings->midiPortNumber(), TSE3::MidiControl_ReverbDepth, track->reverb())
					, 0)
		);
		
		phraseEdit->insert(TSE3::MidiEvent(
					TSE3::MidiCommand(
						TSE3::MidiCommand_ControlChange,
						pos, mSettings->midiPortNumber(), TSE3::MidiControl_PanMSB, track->balance())
					, 0)
		);
		
		phraseEdit->insert(TSE3::MidiEvent(
					TSE3::MidiCommand(
						TSE3::MidiCommand_ControlChange,
						pos, mSettings->midiPortNumber(), TSE3::MidiControl_ChorusDepth, track->chorus())
					, 0)
		);
		
		phraseEdit->insert(TSE3::MidiEvent(
					TSE3::MidiCommand(
						TSE3::MidiCommand_ControlChange,
						pos, mSettings->midiPortNumber(), TSE3::MidiControl_ChannelVolumeMSB, track->volume() + 20)
					, 0)
		);
	}
}

long TabSongPlayer::createTheSong(TSE3::PhraseEdit* phraseEdit)
{
	uint i, j, k, l, pos;
	TabSongModel::const_iterator it;
	bool drumTrack = mTabSong->isDrumTrack();
	uint drumTrackNb = 0;
	int note = 0;
	long countTime;
	long lastTime = 0;
	const TabTrack* track = 0;
	std::vector<long> time(0);
	std::vector<long> nextTime;
	
	if (drumTrack)
		drumTrackNb = mTabSong->drumTrack();
	
	for (j = 0, it = mTabSong->begin(); it != mTabSong->end(); j++, ++it) {
		track = *it;
		
		countTime = 0;
		pos = j;
		
		if (drumTrack) {
			if (j == drumTrackNb)
				pos = 9;
			else if (j > drumTrackNb && j < 9)
				pos = j - 1;
			else if (j >= 9)
				pos = j + 1;
		}
		
		time.resize(track->nbStrings());
		nextTime.resize(track->nbStrings());
		
		for (i = 0; i < track->nbStrings(); i++) {
			time[i] = nextTime[i] = 0;
		}
		
		for (k = 0; k < track->size(); k++) {
			for (l = 0; l < track->barSize(k); l++) {
				// Add duration for the others chords that aren't played
				//
				for (i = 0; i < track->nbStrings(); i++) {
					// Don't add linked note and rest between
					// this note and the linked note
					//
					if ((!track->isTieNote(k, l, i) && !track->isRest(k, l)) || (track->isRest(k, l) && checkLinkAndRest(k, l, i, track)))
						nextTime[i]+=calcDuration(*track->time(k, l));
				}
				
				if (track->isRest(k, l) == false) {
					// Add the notes to be played
					//
					for (i = 0; i < track->nbStrings(); i++) {
						
						if (!track->isEmptyNote(k, l, i) && !track->isTieNote(k, l, i)) {
							if (drumTrack && pos == 9)
								note = track->fret(k, l, i);
							else
								note = track->tune(track->nbStrings() - i - 1) + track->fret(k, l, i);
							
							nextTime[i]+=calcLink(k, l, i, track);
							
							//insertMidiCommand(phraseEdit, TSE3::MidiCommand_NoteOn, pos, note, 0x60, time[i]);
							//insertMidiCommand(phraseEdit, TSE3::MidiCommand_NoteOff, pos, note, 0x60, nextTime[i]);
							phraseEdit->insert(TSE3::MidiEvent(
								TSE3::MidiCommand(
									TSE3::MidiCommand_NoteOn,
									pos, mSettings->midiPortNumber(), note, 0x60)
								, time[i])
							);
							
							phraseEdit->insert(TSE3::MidiEvent(
								TSE3::MidiCommand(
									TSE3::MidiCommand_NoteOff,
									pos, mSettings->midiPortNumber(), note, 0x60)
								, nextTime[i] - 1)
							);
						
						}
					}
				}
				
				time = nextTime;
				
				for (i = 0; i < track->nbStrings(); i++) {
					// Check the greatter last time
					//
					if (lastTime < time[i])
						lastTime = time[i];
				}
				
				// Event for the time tracking
				//
				countTime+=calcDuration(*track->time(k, l));
				
				insertMidiCommand(phraseEdit, PlaybackTracker::playBackTrackCmd, pos, 0x0, 0x0, countTime);
				/*phraseEdit.insert(TSE3::MidiEvent(
							TSE3::MidiCommand(
								PlaybackTracker::playBackTrackCmd,
								pos, mSettings->midiPortNumber(), 0x0, 0x0)
							, countTime)
						);*/
			}
		}
	}
	
	return lastTime;
}

void TabSongPlayer::fillWithEmptyNotesForEnd(TSE3::PhraseEdit* phraseEdit, uint lastTime)
{
	int i, pos;
	bool drumTrack = mTabSong->isDrumTrack();
	int drumTrackNb = 0;
	
	if (drumTrack)
		drumTrackNb = mTabSong->drumTrack();
	
	for (i = 0; i < mTabSong->size(); i++) {
		pos = i;
		
		if (drumTrack) {
			if (i == drumTrackNb)
				pos = 9;
			else if (i > drumTrackNb && i < 9)
				pos = i - 1;
			else if (i >= 9)
				pos = i + 1;
		}
		
		//insertMidiCommand(phraseEdit, TSE3::MidiCommand_NoteOn, pos, 0, 0, lastTime, 0, lastTime + TSE3::Clock::PPQN);
		
		phraseEdit->insert(TSE3::MidiEvent(
			TSE3::MidiCommand(
				TSE3::MidiCommand_NoteOn, 
				pos, mSettings->midiPortNumber(), 0, 0)
			, lastTime, 0, lastTime + TSE3::Clock::PPQN)
		);
	}
}

void TabSongPlayer::assembleTheSong(TSE3::PhraseEdit* phraseEdit, TSE3::Transport* transport, TSE3::Song* song)
{
	song->tempoTrack()->insert(TSE3::Event<TSE3::Tempo>(TSE3::Tempo(mTabSong->tempo()), 0));
	
	TSE3::Phrase *phrase = phraseEdit->createPhrase(song->phraseList());
	TSE3::Part   *part   = new TSE3::Part(0, phraseEdit->lastClock());
	part->setPhrase(phrase);
	(*song)[0]->insert(part);
	
	transport->attachCallback(mPlaybackTracker);
	mPlaybackTracker->resetCursor();
}

void TabSongPlayer::playTheSong(TSE3::Transport* transport, TSE3::Song* song)
{
	transport->play(song, 0);
	
	do {
		transport->poll();
		
		if (stop)
			transport->stop();
	} while(transport->status() != TSE3::Transport::Resting && !stop);
	
	if (stop)
		transport->stop();
}

void TabSongPlayer::run()
{
	if (!scheduler)
		return ;
	
	TSE3::Song    song(1);
	TSE3::Metronome metronome;
	TSE3::Transport transport(&metronome, scheduler);
	TSE3::PhraseEdit phraseEdit;
	
	long lastTime = 0;
	
	qDebug() << "midi Port = " << mSettings->midiPortNumber() << "\n";
	
	createMidiHeaders(&phraseEdit);
	createTheSong(&phraseEdit);
	fillWithEmptyNotesForEnd(&phraseEdit, lastTime);
	assembleTheSong(&phraseEdit, &transport, &song);
	playTheSong(&transport, &song);
	
}
