/***************************************************************************
 * midi.cpp: implementation of Midi class
 *
 * This file is part of KTabEdit, a KDE tabulature editor
 *
 * copyright (C) 2003-2006 the KTabEdit development team <mrgwen@gmail.com>
 * copyright (C) 2002-2003 the KGuitar 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 "midi.h"

#include <QtDebug>

#include "tablature/tabbeateffects.h"
#include "tablature/tabchord.h"
#include "tablature/tabduration.h"

#include "tablature/tabbend.h"
#include "tablature/tabdynamic.h"

#include "tablature/tabgracenotetransition.h"
#include "tablature/tabgracenote.h"

#include "tablature/tabharmonic.h"
#include "tablature/tabslide.h"
#include "tablature/tabtrill.h"
#include "tablature/tabnoteeffect.h"

#include "tablature/tabnote.h"

#include "tablature/songkey.h"
#include "tablature/timesignature.h"

#include "tablature/tabmixtablechanges.h"
#include "tablature/tabtimes.h"
#include "tablature/tabbarproperties.h"

#include "tablature/tabbar.h"

#include "tablature/tablyrics.h"
#include "tablature/tabproperties.h"

#include "tablature/tabsong.h"

#include "tablature/trackmidiproperties.h"
#include "tablature/trackproperties.h"

#include "tablature/tabtrack.h"

Midi::Midi()
{
}

Midi::~Midi()
{
}

bool Midi::loadFile(const QString& fileName)
{
	uint len;
	QList<QVector< Midi::MidiEvent> >::const_iterator it;
	TabSong* tabSong;
	
	mFile.setName(fileName);
	
	if (!mFile.open(IO_ReadOnly))
		throw "IO Error";
	
	mStream.setDevice(&mFile);
	
	readMidiHeader();
	
	for (uint i = 0; i < mTrackNum; i++) {
		len = readTrackChunk();
		
		mMidiSong.push_back(readMidiTrack());
	}
	
	mFile.close();
	
	for (it = mMidiSong.begin(); it != mMidiSong.end(); ++it) {
		qDebug() << "\nnew track\n\n";
		QVector<Midi::MidiEvent> event(*it);
		tabSong->push_front(parseMidiTrack(event, tabSong));
	}
	
	setTabSong(tabSong);
	
	return true;
}

QString Midi::vectorToQString(const QVector<Q_UINT8>& array)
{
	QString temp;
	
	for (uint i = 0; i < array.size(); i++)
		temp.append(array[i]);
	
	return temp;
}

uint Midi::readTrackChunk()
{
	const QString mtrk("MTrk");
	char header[5];
	Q_UINT8 length;
	uint result;
	
	mStream.readRawBytes(header, 4);
	header[4] = 0;
	
	if (header != mtrk || mStream.atEnd())
		throw "Bad midi file\n";
	
	mStream >> length;
	result = length << 24;
	
	mStream >> length;
	result+=length << 16;
	
	mStream >> length;
	result+=length << 8;
	
	mStream >> length;
	result+=length;
	
	if (mStream.atEnd())
		throw "Bad midi file\n";
	
	return result;
}

uint Midi::readChunk()
{
	const QString mthd("MThd");
	const QString mtrk("MTrk");
	char header[5];
	Q_UINT32 length;
	uint result;
	
	mStream.readRawBytes(header, 4);
	header[4] = 0;
	
	if ((header != mthd && header != mtrk) || mStream.atEnd())
		throw "Bad midi file\n";
	
	mStream >> length;
	result+=length << 24;
	
	mStream >> length;
	result+=length << 16;
	
	mStream >> length;
	result+=length << 8;
	
	mStream >> length;
	result = length;
	
	if (mStream.atEnd())
		throw "Bad midi file\n";
	
	return result;
}

unsigned long Midi::readMidiNumber()
{
	unsigned long value;
	Q_UINT8 byte;
	
	mStream >> byte;
	value = byte;
	
	if (value & 0x80) {
		value&=0x7f;
		
		do {
			mStream >> byte;
			value = (value << 7) + (byte & 0x7f);
		} while (byte & 0x80);
	}
	
	if (mStream.atEnd())
		throw "Bad midi file\n";
	
	return value;
}

QVector<Q_UINT8> Midi::readString()
{
	Q_UINT32 len;
	char* readBlock;
	QVector<Q_UINT8> result;
	
	len = readMidiNumber();
	
	readBlock = new char[len + 1];
	
	mStream.readRawBytes(readBlock, len);
	
	for (uint i = 0; i < len; i++)
		result.push_back(readBlock[i]);
	
	delete readBlock;
	
	return result;
}

QVector<Q_UINT8> Midi::readByte(uint numberOfByte)
{
	QVector<Q_UINT8> result;
	Q_UINT8 byte;
	
	for (uint i = 0; i < numberOfByte; i++) {
		mStream >> byte;
		result.push_back(byte);
	}
	
	return result;
}

void Midi::readMidiHeader()
{
	const QString midiHeader("MThd");
	char header[5];
	Q_UINT32 length;
	
	mStream.readRawBytes(header, 4);
	header[4] = 0;
	
	if (header != midiHeader)
		throw "Bad midi header\n";
	
	mStream >> length;
	
	if (length != 6)
		throw "Bad midi header the length is not correct\n";
	
	mStream >> mMidiFormat;
	mStream >> mTrackNum;
	mStream >> mDivisions;
}

QVector<Midi::MidiEvent> Midi::readMidiTrack()
{
	bool end = false;
	QVector<MidiEvent> vectorOfEvents;
	
	while (!end) {
		MidiEvent midiEvent;
		Q_UINT8 eventStatus = 0x7f; // Note on
		Q_UINT8 eventCode;
		Q_UINT8 byte;
		
		midiEvent.mDeltaTime = readMidiNumber();
		
		if ((byte = mFile.getch()) & 0x80) 
			eventStatus = byte;
		else
			mFile.ungetch(byte);
		
		// Event
		//
		if (eventStatus == 0xff) {
			mStream >> eventCode;
			
			midiEvent.mMetaEvent = true;
			
			switch (eventCode) {
			// Sequence number
			//
			case 0x00: {
				Q_UINT8 byte;
				
				mStream >> byte;
				
				if (byte != 2)
					throw "Error\n";
				
				midiEvent.mEvent = MidiEvent::SequenceNumber;
				
				midiEvent.mArrayByte = readByte(2);
				
				break ;
			}
			
			// Text
			//
			case 0x01: {
// 				qDebug() << "message\n";
				midiEvent.mEvent = MidiEvent::Text;
				
				midiEvent.mArrayByte = readString();
				break ;
			}
			
			// Copyright
			//
			case 0x02: {
// 				qDebug() << "copyright\n";
				midiEvent.mEvent = MidiEvent::Copyright;
				
				midiEvent.mArrayByte = readString();
				break ;
			}
			
			// TrackName
			//
			case 0x03: {
// 				qDebug() << "track name\n";
				midiEvent.mEvent = MidiEvent::TrackName;
				
				midiEvent.mArrayByte = readString();
				break ;
			}
			
			// Instrument
			//
			case 0x04: {
// 				qDebug() << "instrument\n";
				midiEvent.mEvent = MidiEvent::Instrument;
				midiEvent.mArrayByte = readString();
				break ;
			}
			
			// Lyric
			//
			case 0x05: {
// 				qDebug() << "lyric\n";
				midiEvent.mEvent = MidiEvent::Lyric;
				
				midiEvent.mArrayByte = readString();
				break ;
			}
			
			// Marker
			//
			case 0x06: {
// 				qDebug() << "marker\n";
				midiEvent.mEvent = MidiEvent::Marker;
				
				midiEvent.mArrayByte = readString();
				break ;
			}
			
			// Cue point
			//
			case 0x07: {
// 				qDebug() << "cue point\n";
				midiEvent.mEvent = MidiEvent::CuePoint;
				
				midiEvent.mArrayByte = readString();
				break ;
			}
			
			// Midi port
			//
			case 0x21: {
				Q_UINT8 byte;
				
				mStream >> byte;
				
				if (byte != 1)
					throw "Bad midi port\n";
				
				midiEvent.mEvent = MidiEvent::MidiPort;
				
				midiEvent.mArrayByte = readByte(1);
				break ;
			}
			
			// End of track
			//
			case 0x2f: {
				Q_UINT8 byte;
				
				mStream >> byte;
				
				if (byte)
					throw "Bad midi track\n";
				
				end = true;
				midiEvent.mEvent = MidiEvent::EndOfTrack;
				break ;
			}
			
			// Tempo
			//
			case 0x51: {
				Q_UINT8 byte;
				
				mStream >> byte;
				
				if (byte != 3)
					throw "Bad midi tempo\n";
				
				midiEvent.mEvent = MidiEvent::Tempo;
				
				midiEvent.mArrayByte = readByte(3);
				break;
			}
			
			// Start time of the track (SMPTE)
			//
			case 0x54: {
				Q_UINT8 byte;
				
				mStream >> byte;
				
				if (byte != 5)
					throw "Bad midi SMPTE\n";
				
				
				midiEvent.mEvent = MidiEvent::SMPTE;
				midiEvent.mArrayByte = readByte(5);
				break;
			}
			
			// Time signature
			//
			case 0x58: {
				Q_UINT8 byte;
				
				mStream >> byte;
				
				if (byte != 4)
					throw "Bad midi time signature\n";
				
// 				qDebug() << "time signature\n";
				midiEvent.mEvent = MidiEvent::TimeSignature;
				midiEvent.mArrayByte = readByte(4);
				break;
			}
			
			default:
				Q_UINT32 len = readMidiNumber();
				Q_UINT8 byte;
				
				for (uint i = 0; i < len; i++)
					mStream >> byte;
			}
		} else if (eventStatus == 0xf0 || eventStatus == 0xf7 ) {
			Q_UINT8 byte;
			uint len = readMidiNumber();
			
			for (uint i = 0; i < len; i++)
				mStream >> byte;
			
		} else {
			Q_UINT8 byte;
			
			switch (eventStatus & 0xf0) {
			// Note off
			//
			case 0x80: {
// 				qDebug() << "note off\n";
				
				midiEvent.mEvent = MidiEvent::NoteOff;
				midiEvent.mArrayByte = readByte(2);
				break;
			}
			// Note on
			//
			case 0x90: {
// 				qDebug() << "note on\n";
				
				midiEvent.mEvent = MidiEvent::NoteOn;
				midiEvent.mArrayByte = readByte(2);
				break;
			}
			// Poly after touch
			//
			case 0xa0:
// 				qDebug() << "poly after\n";
				
				midiEvent.mEvent = MidiEvent::PolyAfterTouch;
				midiEvent.mArrayByte = readByte(2);
				break;
			// Controler change
			//
			case 0xb0:
// 				qDebug() << "control change\n";
				midiEvent.mEvent = MidiEvent::ControlerChange;
				midiEvent.mArrayByte = readByte(2);
				break;
			// Midi pitch bend
			//
			case 0xe0:
// 				qDebug() << "pitch bend\n";
				midiEvent.mEvent = MidiEvent::PitchBend;
				midiEvent.mArrayByte = readByte(1);
				break;
			// Program change
			//
			case 0xc0:
// 				qDebug() << "pgm change\n";
				midiEvent.mEvent = MidiEvent::ProgramChange;
				midiEvent.mArrayByte = readByte(1);
				break;
			// Channel pressure
			//
			case 0xd0:
// 				qDebug() << "channel\n";
				midiEvent.mEvent = MidiEvent::ChannelPressure;
				
				midiEvent.mArrayByte = readByte(1);
				break;
			case 0xf0:
				mStream >> byte;
// 				qDebug() << "unknown\n";
				break;
			// End of exclusive
			//
			case 0xf7:
				break;
			default:
				break;
			}
		}
		
		if (midiEvent.mEvent != MidiEvent::EndOfTrack)
			vectorOfEvents.push_back(midiEvent);
	}
	
	return vectorOfEvents;
}

uint Midi::findNoteOff(Q_UINT8 noteOn, uint start, const QVector<Midi::MidiEvent>& midiEvent)
{
	uint time = 0;
	
	for (uint i = start; i < midiEvent.size(); i++) {
		if ((midiEvent[i].mEvent == MidiEvent::NoteOff || (midiEvent[i].mEvent == MidiEvent::NoteOn && midiEvent[i].mArrayByte[1] == 0)) &&
			midiEvent[i].mArrayByte[0] == noteOn)
			return time + midiEvent[i].mDeltaTime;
		
		if (midiEvent[i].mMetaEvent == false)
			time+=midiEvent[i].mDeltaTime;
	}
	
	return time;
}

void Midi::addTime(TabTrack* tabTrack, Q_UINT8 note, unsigned long duration)
{
	
}

TabTrack* Midi::parseMidiTrack(const QVector<Midi::MidiEvent>& midiEvent, TabSong* tabSong)
{
	TabTrack* track;
	uint time = 0;
	uint positionOfBar = 0;
	
	for (uint i = 0; i < midiEvent.size(); i++) {
		if (midiEvent[i].mMetaEvent == false)
			time+=midiEvent[i].mDeltaTime;
		
		if (midiEvent[i].mEvent == MidiEvent::NoteOn)
			qDebug() << "On\n";
		if (midiEvent[i].mEvent == MidiEvent::NoteOff)
			qDebug() << "Off\n";
		qDebug() << "delta : " << midiEvent[i].mDeltaTime << "\n";
		
		switch (midiEvent[i].mEvent) {
		case MidiEvent::Copyright:
			tabSong->setCopyright(vectorToQString(midiEvent[i].mArrayByte));
			break;
		case MidiEvent::TrackName:
			track->setName(vectorToQString(midiEvent[i].mArrayByte));
			break;
		case MidiEvent::NoteOn: {
			uint result;
			
/*			qDebug() << "time " << time << " " << i << " " << TSE3::Clock::PPQN << " " << mDivisions << "\n";
			qDebug() << (uint)midiEvent[i].mArrayByte[0] << " " << (uint)midiEvent[i].mArrayByte[1] << " " << midiEvent[i].mDeltaTime << "\n";*/
			
			// Check if the velocity is zero
			//
			if (midiEvent[i].mArrayByte[1] == 0)
				break;
			
			result = findNoteOff(midiEvent[i].mArrayByte[0], i + 1, midiEvent);
			
			addTime(track, midiEvent[i].mArrayByte[0], result);
// 			qDebug() << "result = " << result << "\n";
			
			// 960 <- noire
			
			break;
		}
		
		// Nothing to do with note off
		// event
		//
		case MidiEvent::NoteOff:
			break;
		case MidiEvent::SequenceNumber:
		case MidiEvent::Text:
		case MidiEvent::Instrument:
		case MidiEvent::Lyric:
		case MidiEvent::Marker:
		case MidiEvent::CuePoint:
		case MidiEvent::MidiPort:
		case MidiEvent::Tempo:
		case MidiEvent::SMPTE:
		case MidiEvent::TimeSignature:
		case MidiEvent::PolyAfterTouch:
		case MidiEvent::ControlerChange:
		case MidiEvent::PitchBend:
		case MidiEvent::ProgramChange:
		case MidiEvent::ChannelPressure:
		case MidiEvent::Unknown:
		case MidiEvent::EndOfTrack:
		default:
			break;
		}
	}
	
	return track;
}

bool Midi::saveFile(const QString& /*fileName*/, const TabSong& /*tabSong*/)
{
	return false;
}
