/*
 * Copyright 2006, Haiku.
 * 
 * Copyright (c) 2002-2003 Matthijs Hollemans
 * Distributed under the terms of the MIT License.
 *
 * Authors:
 *		Matthijs Hollemans
 */

#include <stdlib.h>

#include "debug.h"
#include <MidiConsumer.h>
#include <MidiRoster.h>
#include "protocol.h"


int32 
_midi_event_thread(void* data)
{
	return ((BMidiLocalConsumer*) data)->EventThread();
}


BMidiLocalConsumer::BMidiLocalConsumer(const char* name)
	: BMidiConsumer(name)
{
	TRACE(("BMidiLocalConsumer::BMidiLocalConsumer"))

	fIsLocal = true;
	fRefCount = 1;
	fTimeout = (bigtime_t) -1;
	fTimeoutData = NULL;

	fPort = create_port(1, "MidiEventPort");
	fThread = spawn_thread(
		_midi_event_thread, "MidiEventThread", B_REAL_TIME_PRIORITY, this);
	resume_thread(fThread);

	BMidiRoster::MidiRoster()->CreateLocal(this);
}


BMidiLocalConsumer::~BMidiLocalConsumer()
{
	TRACE(("BMidiLocalConsumer::~BMidiLocalConsumer"))

	BMidiRoster::MidiRoster()->DeleteLocal(this);

	delete_port(fPort);

	status_t result;
	wait_for_thread(fThread, &result);
}


void 
BMidiLocalConsumer::SetLatency(bigtime_t latency_)
{
	if (latency_ < 0) {
		WARN("SetLatency() does not accept negative values");
		return;
	} else if (!IsValid()) {
		return;
	} else if (fLatency != latency_) {
		BMessage msg;
		msg.AddInt64("midi:latency", latency_);

		if (SendChangeRequest(&msg) == B_OK) {
			if (LockLooper()) {
				fLatency = latency_;
				UnlockLooper();
			}
		}
	}
}


int32 
BMidiLocalConsumer::GetProducerID()
{
	return fCurrentProducer;
}


void 
BMidiLocalConsumer::SetTimeout(bigtime_t when, void* data)
{
	fTimeout = when;
	fTimeoutData = data;
}


void 
BMidiLocalConsumer::Timeout(void* data)
{
	// Do nothing.
}


void 
BMidiLocalConsumer::Data(uchar* data, size_t length, bool atomic, bigtime_t time)
{
	if (atomic) {
		switch (data[0] & 0xF0) {
			case B_NOTE_OFF:
			{
				if (length == 3)
					NoteOff(data[0] & 0x0F, data[1], data[2], time);
				break;
			}

			case B_NOTE_ON:
			{
				if (length == 3)
					NoteOn(data[0] & 0x0F, data[1], data[2], time);
				break;
			}

			case B_KEY_PRESSURE:
			{
				if (length == 3)
					KeyPressure(data[0] & 0x0F, data[1], data[2], time);
				break;
			}

			case B_CONTROL_CHANGE:
			{
				if (length == 3)
					ControlChange(data[0] & 0x0F, data[1], data[2], time);
				break;
			}

			case B_PROGRAM_CHANGE:
			{
				if (length == 2)
					ProgramChange(data[0] & 0x0F, data[1], time);
				break;
			}

			case B_CHANNEL_PRESSURE:
			{
				if (length == 2)
					ChannelPressure(data[0] & 0x0F, data[1], time);
				break;
			}

			case B_PITCH_BEND:
			{
				if (length == 3)
					PitchBend(data[0] & 0x0F, data[1], data[2], time);
				break;
			}

			case 0xF0:
			{
				switch (data[0]) {
					case B_SYS_EX_START:
					{
						if (data[length - 1] == B_SYS_EX_END) {
							SystemExclusive(data + 1, length - 2, time);
						} else { // sysex-end is not required
							SystemExclusive(data + 1, length - 1, time);
						}
						break;
					}

					case B_TUNE_REQUEST:
					case B_SYS_EX_END:
					{
						if (length == 1) {
							SystemCommon(data[0], 0, 0, time);
						}
						break;
					}

					case B_CABLE_MESSAGE:
					case B_MIDI_TIME_CODE:
					case B_SONG_SELECT:
					{
						if (length == 2) {
							SystemCommon(data[0], data[1], 0, time);
						}
						break;
					}

					case B_SONG_POSITION:
					{
						if (length == 3) {
							SystemCommon(data[0], data[1], data[2], time);
						}
						break;
					}
	
					case B_TIMING_CLOCK:
					case B_START:
					case B_CONTINUE:
					case B_STOP:
					case B_ACTIVE_SENSING:
					{
						if (length == 1) {
							SystemRealTime(data[0], time);
						}
						break;
					}

					case B_SYSTEM_RESET:
					{
						if (length == 1) {
							SystemRealTime(data[0], time);
						} else if ((length == 6) && (data[1] == 0x51) 
								&& (data[2] == 0x03)) {
							int32 tempo = 
								(data[3] << 16) | (data[4] << 8) | data[5];

							TempoChange(60000000/tempo, time);
						}
					}
				}
				break;
			}
		}
	}
}


void 
BMidiLocalConsumer::NoteOff(uchar channel, uchar note, uchar velocity, bigtime_t time)
{
	// Do nothing.
}


void 
BMidiLocalConsumer::NoteOn(uchar channel, uchar note, uchar velocity, bigtime_t time)
{
	// Do nothing.
}


void 
BMidiLocalConsumer::KeyPressure(uchar channel, uchar note, uchar pressure, bigtime_t time)
{
	// Do nothing.
}


void 
BMidiLocalConsumer::ControlChange(uchar channel, uchar controlNumber, uchar controlValue, bigtime_t time)
{
	// Do nothing.
}


void 
BMidiLocalConsumer::ProgramChange(uchar channel, uchar programNumber, bigtime_t time)
{
	// Do nothing.
}


void BMidiLocalConsumer::ChannelPressure(uchar channel, uchar pressure, bigtime_t time)
{
	// Do nothing.
}


void 
BMidiLocalConsumer::PitchBend(uchar channel, uchar lsb, uchar msb, bigtime_t time)
{
	// Do nothing.
}


void 
BMidiLocalConsumer::SystemExclusive(
	void* data, size_t length, bigtime_t time)
{
	// Do nothing.
}


void 
BMidiLocalConsumer::SystemCommon(
	uchar statusByte, uchar data1, uchar data2, bigtime_t time)
{
	// Do nothing.
}


void 
BMidiLocalConsumer::SystemRealTime(uchar statusByte, bigtime_t time)
{
	// Do nothing.
}


void 
BMidiLocalConsumer::TempoChange(int32 beatsPerMinute, bigtime_t time)
{
	// Do nothing.
}


void 
BMidiLocalConsumer::AllNotesOff(bool justChannel, bigtime_t time)
{
	// Do nothing.
}


void BMidiLocalConsumer::_Reserved1() { }
void BMidiLocalConsumer::_Reserved2() { }
void BMidiLocalConsumer::_Reserved3() { }
void BMidiLocalConsumer::_Reserved4() { }
void BMidiLocalConsumer::_Reserved5() { }
void BMidiLocalConsumer::_Reserved6() { }
void BMidiLocalConsumer::_Reserved7() { }
void BMidiLocalConsumer::_Reserved8() { }


int32 
BMidiLocalConsumer::EventThread()
{
	int32 msg_code;
	ssize_t msg_size;
	ssize_t buf_size = 100;
	uint8* buffer = (uint8*) malloc(buf_size);

	while (true) {
		if (fTimeout == (bigtime_t) -1) {
			msg_size = port_buffer_size(fPort);
		} else { // have timeout
			msg_size = port_buffer_size_etc(fPort, B_ABSOLUTE_TIMEOUT, fTimeout);
			if (msg_size == B_TIMED_OUT) {
				Timeout(fTimeoutData);
				fTimeout = (bigtime_t) -1;
				fTimeoutData = NULL;
				continue;
			}
		}

		if (msg_size < 0) 
			break;  // error reading port

		if (msg_size > buf_size) {
			uint8* tmp_buffer = (uint8*) realloc(buffer, msg_size);
			if (tmp_buffer == NULL)
				break; // error in realloc()
			buffer = tmp_buffer;
			buf_size = msg_size;
		}

		read_port(fPort, &msg_code, buffer, msg_size);

		if (msg_size > 20) { // minimum valid size
			#ifdef DEBUG
			printf("*** received: ");
			for (int32 t = 0; t < msg_size; ++t) {
				printf("%02X, ", ((uint8*) buffer)[t]);
			}
			printf("\n");
			#endif

			fCurrentProducer = *((uint32*)    (buffer +  0));
			int32 targetId  = *((uint32*)    (buffer +  4));
			bigtime_t time  = *((bigtime_t*) (buffer +  8));
			bool atomic     = *((bool*)      (buffer + 16));

			if (targetId == fId) { // only if we are the destination
				Data((uchar*) (buffer + 20), msg_size - 20, atomic, time);
			}
		}
	}

	free(buffer);
	return 0;
}