/*
 * Copyright (c) 2002-2003 Matthijs Hollemans
 * 
 * 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, sublicense, 
 * and/or sell copies of the Software, and to permit persons to whom the 
 * Software is furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in 
 * all copies or substantial portions of the Software.
 * 
 * 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. IN NO EVENT SHALL THE 
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 
 * DEALINGS IN THE SOFTWARE.
 */

#include <stdlib.h>

#include "debug.h"
#include "MidiConsumer.h"
#include "MidiProducer.h"
#include "MidiRoster.h"
#include "protocol.h"

//------------------------------------------------------------------------------

BMidiLocalProducer::BMidiLocalProducer(const char* name)
	: BMidiProducer(name)
{
	TRACE(("BMidiLocalProducer::BMidiLocalProducer"))

	isLocal = true;
	refCount = 1;

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

//------------------------------------------------------------------------------

BMidiLocalProducer::~BMidiLocalProducer()
{
	TRACE(("BMidiLocalProducer::~BMidiLocalProducer"))

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

//------------------------------------------------------------------------------

void BMidiLocalProducer::Connected(BMidiConsumer* cons)
{
	ASSERT(cons != NULL)
	TRACE(("Connected() %ld to %ld", ID(), cons->ID()))

	// Do nothing.
}

//------------------------------------------------------------------------------

void BMidiLocalProducer::Disconnected(BMidiConsumer* cons)
{
	ASSERT(cons != NULL)
	TRACE(("Disconnected() %ld from %ld", ID(), cons->ID()))

	// Do nothing.
}

//------------------------------------------------------------------------------

void BMidiLocalProducer::SprayData(
	void* data, size_t length, bool atomic, bigtime_t time) const
{
	SprayEvent(data, length, atomic, time);
}

//------------------------------------------------------------------------------

void BMidiLocalProducer::SprayNoteOff(
	uchar channel, uchar note, uchar velocity, bigtime_t time) const
{
	if (channel < 16)
	{
		uchar data[3];
		data[0] = B_NOTE_OFF + channel;
		data[1] = note;
		data[2] = velocity;

		SprayEvent(&data, 3, true, time);
	}
	else
	{
		debugger("invalid MIDI channel");
	}
}

//------------------------------------------------------------------------------

void BMidiLocalProducer::SprayNoteOn(
	uchar channel, uchar note, uchar velocity, bigtime_t time) const
{
	if (channel < 16)
	{
		uchar data[3];
		data[0] = B_NOTE_ON + channel;
		data[1] = note;
		data[2] = velocity;

		SprayEvent(&data, 3, true, time);
	}
	else
	{
		debugger("invalid MIDI channel");
	}
}

//------------------------------------------------------------------------------

void BMidiLocalProducer::SprayKeyPressure(
	uchar channel, uchar note, uchar pressure, bigtime_t time) const
{
	if (channel < 16)
	{
		uchar data[3];
		data[0] = B_KEY_PRESSURE + channel;
		data[1] = note;
		data[2] = pressure;

		SprayEvent(&data, 3, true, time);
	}
	else
	{
		debugger("invalid MIDI channel");
	}
}

//------------------------------------------------------------------------------

void BMidiLocalProducer::SprayControlChange(
	uchar channel, uchar controlNumber, uchar controlValue, 
	bigtime_t time) const
{
	if (channel < 16)
	{
		uchar data[3];
		data[0] = B_CONTROL_CHANGE + channel;
		data[1] = controlNumber;
		data[2] = controlValue;

		SprayEvent(&data, 3, true, time);
	}
	else
	{
		debugger("invalid MIDI channel");
	}
}

//------------------------------------------------------------------------------

void BMidiLocalProducer::SprayProgramChange(
	uchar channel, uchar programNumber, bigtime_t time) const
{
	if (channel < 16)
	{
		uchar data[2];
		data[0] = B_PROGRAM_CHANGE + channel;
		data[1] = programNumber;

		SprayEvent(&data, 2, true, time);
	}
	else
	{
		debugger("invalid MIDI channel");
	}
}

//------------------------------------------------------------------------------

void BMidiLocalProducer::SprayChannelPressure(
	uchar channel, uchar pressure, bigtime_t time) const
{
	if (channel < 16)
	{
		uchar data[2];
		data[0] = B_CHANNEL_PRESSURE + channel;
		data[1] = pressure;

		SprayEvent(&data, 2, true, time);
	}
	else
	{
		debugger("invalid MIDI channel");
	}
}

//------------------------------------------------------------------------------

void BMidiLocalProducer::SprayPitchBend(
	uchar channel, uchar lsb, uchar msb, bigtime_t time) const
{
	if (channel < 16)
	{
		uchar data[3];
		data[0] = B_PITCH_BEND + channel;
		data[1] = lsb;
		data[2] = msb;

		SprayEvent(&data, 3, true, time);
	}
	else
	{
		debugger("invalid MIDI channel");
	}
}

//------------------------------------------------------------------------------

void BMidiLocalProducer::SpraySystemExclusive(
	void* data, size_t length, bigtime_t time) const
{
	SprayEvent(data, length, true, time, true);
}

//------------------------------------------------------------------------------

void BMidiLocalProducer::SpraySystemCommon(
	uchar status, uchar data1, uchar data2, bigtime_t time) const
{
	size_t len;
	uchar data[3];
	data[0] = status;
	data[1] = data1;
	data[2] = data2;

	switch (status)
	{
		case B_TUNE_REQUEST:
		case B_SYS_EX_END:
			len = 1; break;

		case B_CABLE_MESSAGE:
		case B_MIDI_TIME_CODE:
		case B_SONG_SELECT:
			len = 2; break;

		case B_SONG_POSITION:
			len = 3; break;

		default:
			debugger("invalid system common status");
	}

	SprayEvent(&data, len, true, time);
}

//------------------------------------------------------------------------------

void BMidiLocalProducer::SpraySystemRealTime(
	uchar status, bigtime_t time) const
{
	if (status >= B_TIMING_CLOCK)
	{
		SprayEvent(&status, 1, true, time);
	}
	else
	{
		debugger("invalid real time status");
	}
}

//------------------------------------------------------------------------------

void BMidiLocalProducer::SprayTempoChange(
	int32 beatsPerMinute, bigtime_t time) const
{
	int32 tempo = 60000000 / beatsPerMinute;

	uchar data[6];
	data[0] = 0xFF;
	data[1] = 0x51;
	data[2] = 0x03;
	data[3] = tempo >> 16;
	data[4] = tempo >> 8;
	data[5] = tempo;

	SprayEvent(&data, 6, true, time);
}

//------------------------------------------------------------------------------

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

//------------------------------------------------------------------------------

void BMidiLocalProducer::SprayEvent(
	const void* data, size_t length, bool atomic, bigtime_t time,
	bool sysex) const
{
	if (LockProducer())
	{
		if (CountConsumers() > 0)
		{
			// We don't just send the MIDI event data to all connected
			// consumers, we also send a header. The header contains our
			// ID (4 bytes), the consumer's ID (4 bytes), the performance
			// time (8 bytes), whether the data is atomic (1 byte), and
			// padding (3 bytes). The MIDI event data follows the header.

			size_t buf_size = 20 + length;
			if (sysex) { buf_size += 2; }   // add 0xF0 and 0xF7 markers

			uint8* buffer = (uint8*) malloc(buf_size);
			if (buffer != NULL)
			{
				*((uint32*)    (buffer +  0)) = id;
				*((bigtime_t*) (buffer +  8)) = time;
				*((uint32*)    (buffer + 16)) = 0;
				*((bool*)      (buffer + 16)) = atomic;

				if (sysex)
				{
					*((uint8*) (buffer + 20)) = B_SYS_EX_START;
					if (data != NULL)
					{
						memcpy(buffer + 21, data, length);
					}
					*((uint8*) (buffer + buf_size - 1)) = B_SYS_EX_END;
				}
				else if (data != NULL)
				{
					memcpy(buffer + 20, data, length);
				}

				for (int32 t = 0; t < CountConsumers(); ++t)
				{
					BMidiConsumer* cons = ConsumerAt(t);
					*((uint32*) (buffer + 4)) = cons->id;

					#ifdef DEBUG
					printf("*** spraying: ");
					for (int32 t = 0; t < buf_size; ++t)
					{
						printf("%02X, ", buffer[t]);
					}
					printf("\n");
					#endif

					write_port(cons->port, 0, buffer, buf_size);
				}				

				free(buffer);
			}
		}

		UnlockProducer();
	}
}

//------------------------------------------------------------------------------
