#include "i_musicinterns.h"
#include "templates.h"
#include "doomdef.h"
#include "build.h"

extern DWORD midivolume;
extern UINT mididevice;

static const BYTE CtrlTranslate[15] =
{
	0,	// program change
	0,	// bank select
	1,	// modulation pot
	7,	// volume
	10, // pan pot
	11, // expression pot
	91, // reverb depth
	93, // chorus depth
	64, // sustain pedal
	67, // soft pedal
	120, // all sounds off
	123, // all notes off
	126, // mono
	127, // poly
	121, // reset all controllers
};

MUSSong2::MUSSong2 (const char *fn)
: MidiOut (0), PlayerThread (0),
  PauseEvent (0), ExitEvent (0), VolumeChangeEvent (0),
  MusBuffer (0), MusHeader (0)
{
	int h = kopen4load (fn, 0);
	if (h == -1)
		return;

	long len = kfilelength (h);
	MusHeader = (MUSHeader *)Malloc (len);
	kread (h, (void *)MusHeader, len);
	kclose (h);

	// Do some validation of the MUS file
	if (MusHeader->Magic != MAKE_ID('M','U','S','\x1a'))
		return;
	
	if (SHORT(MusHeader->NumChans) > 15)
		return;

	ExitEvent = CreateEvent (NULL, FALSE, FALSE, NULL);
	if (ExitEvent == NULL)
	{
		Printf (PRINT_BOLD, "Could not create exit event for MIDI playback\n");
		return;
	}
	VolumeChangeEvent = CreateEvent (NULL, FALSE, FALSE, NULL);
	if (VolumeChangeEvent == NULL)
	{
		Printf (PRINT_BOLD, "Could not create volume event for MIDI playback\n");
		return;
	}
	PauseEvent = CreateEvent (NULL, FALSE, FALSE, NULL);
	if (PauseEvent == NULL)
	{
		Printf (PRINT_BOLD, "Could not create pause event for MIDI playback\n");
	}

	MusBuffer = (BYTE *)MusHeader + SHORT(MusHeader->SongStart);
	MaxMusP = MIN<int> (SHORT(MusHeader->SongLen), len - SHORT(MusHeader->SongStart));
	MusP = 0;
}

MUSSong2::~MUSSong2 ()
{
	Stop ();

	if (PauseEvent != NULL)
	{
		CloseHandle (PauseEvent);
	}
	if (ExitEvent != NULL)
	{
		CloseHandle (ExitEvent);
	}
	if (VolumeChangeEvent != NULL)
	{
		CloseHandle (VolumeChangeEvent);
	}
	if (MusHeader != NULL)
	{
		free (MusHeader);
	}
}

bool MUSSong2::IsMIDI () const
{
	return true;
}

bool MUSSong2::IsValid () const
{
	return MusBuffer != 0;
}

void MUSSong2::Play (bool looping)
{
	DWORD tid;

	m_Status = STATE_Stopped;
	m_Looping = looping;

	if (MMSYSERR_NOERROR != midiOutOpen (&MidiOut, mididevice, 0, 0, CALLBACK_NULL))
	{
		Printf (PRINT_BOLD, "Could not open MIDI out device\n");
		return;
	}

	// Try two different methods for setting the stream to full volume.
	// Unfortunately, this isn't as reliable as it once was, which is a pity.
	// The real volume selection is done by setting the volume controller for
	// each channel. Because every General MIDI-compliant device must support
	// this controller, it is the most reliable means of setting the volume.

	VolumeWorks = (MMSYSERR_NOERROR == midiOutGetVolume (MidiOut, &SavedVolume));
	if (VolumeWorks)
	{
		VolumeWorks &= (MMSYSERR_NOERROR == midiOutSetVolume (MidiOut, 0xffffffff));
	}
	else
	{
		// Send the standard SysEx message for full master volume
		BYTE volmess[] = { 0xf0, 0x7f, 0x7f, 0x04, 0x01, 0x7f, 0x7f, 0xf7 };
		MIDIHDR hdr = { (LPSTR)volmess, sizeof(volmess), };

		if (MMSYSERR_NOERROR == midiOutPrepareHeader (MidiOut, &hdr, sizeof(hdr)))
		{
			midiOutLongMsg (MidiOut, &hdr, sizeof(hdr));
			while (MIDIERR_STILLPLAYING == midiOutUnprepareHeader (MidiOut, &hdr, sizeof(hdr)))
			{
				Sleep (10);
			}
		}
	}

	PlayerThread = CreateThread (NULL, 0, (LPTHREAD_START_ROUTINE)PlayerProc, this, 0, &tid);
	if (PlayerThread == NULL)
	{
		if (VolumeWorks)
		{
			midiOutSetVolume (MidiOut, SavedVolume);
		}
		midiOutClose (MidiOut);
		MidiOut = NULL;
	}

	m_Status = STATE_Playing;
}

void MUSSong2::Pause ()
{
	if (m_Status == STATE_Playing)
	{
		SetEvent (PauseEvent);
		m_Status = STATE_Paused;
	}
}

void MUSSong2::Resume ()
{
	if (m_Status == STATE_Paused)
	{
		SetEvent (PauseEvent);
		m_Status = STATE_Playing;
	}
}

void MUSSong2::Stop ()
{
	if (PlayerThread)
	{
		SetEvent (ExitEvent);
		WaitForSingleObject (PlayerThread, INFINITE);
		CloseHandle (PlayerThread);
		PlayerThread = NULL;
	}
	if (MidiOut)
	{
		midiOutReset (MidiOut);
		if (VolumeWorks)
		{
			midiOutSetVolume (MidiOut, SavedVolume);
		}
		midiOutClose (MidiOut);
		MidiOut = NULL;
	}
}

bool MUSSong2::IsPlaying ()
{
	return m_Status != STATE_Stopped;
}

void MUSSong2::SetVolume (float volume)
{
	SetEvent (VolumeChangeEvent);
}

DWORD WINAPI MUSSong2::PlayerProc (LPVOID lpParameter)
{
	MUSSong2 *song = (MUSSong2 *)lpParameter;
	HANDLE events[2] = { song->ExitEvent, song->PauseEvent };
	bool waited;
	int i;

	SetThreadPriority (GetCurrentThread (), THREAD_PRIORITY_TIME_CRITICAL);

	for (i = 0; i < 16; ++i)
	{
		song->LastVelocity[i] = 64;
		song->ChannelVolumes[i] = 127;
	}

	song->OutputVolume (midivolume & 0xffff);

	do
	{
		waited = false;
		song->MusP = 0;

		while (song->SendCommand () == SEND_WAIT)
		{
			DWORD time = 0;
			BYTE t;
			do
			{
				t = song->MusBuffer[song->MusP++];
				time = (time << 7) | (t & 127);
			}
			while (t & 128);

			waited = true;

			// Wait for the exit or pause event or the next note
			switch (WaitForMultipleObjects (2, events, FALSE, time * 1000 / 140))
			{
			case WAIT_OBJECT_0:
				song->m_Status = STATE_Stopped;
				return 0;

			case WAIT_OBJECT_0+1:
				// Go paused
				song->OutputVolume (0);
				// Wait for the exit or pause event
				if (WAIT_OBJECT_0 == WaitForMultipleObjects (2, events, FALSE, INFINITE))
				{
					song->m_Status = STATE_Stopped;
					return 0;
				}
				song->OutputVolume (midivolume & 0xffff);
			}

			// Check if the volume needs changing
			if (WAIT_OBJECT_0 == WaitForSingleObject (song->VolumeChangeEvent, 0))
			{
				song->OutputVolume (midivolume & 0xffff);
			}
		}
	}
	while (waited && song->m_Looping);

	song->m_Status = STATE_Stopped;
	return 0;
}

void MUSSong2::OutputVolume (DWORD volume)
{
	for (int i = 0; i < 16; ++i)
	{
		BYTE courseVol = (BYTE)(((ChannelVolumes[i]+1) * volume) >> 16);
		midiOutShortMsg (MidiOut, i | MIDI_CTRLCHANGE | (7<<8) | (courseVol<<16));
	}
}

int MUSSong2::SendCommand ()
{
	BYTE event = 0;

	if (MusP >= MaxMusP)
		return SEND_DONE;

	while (MusP < MaxMusP && (event & 0x70) != MUS_SCOREEND)
	{
		BYTE mid1, mid2;
		BYTE channel;
		BYTE t, status;
		
		event = MusBuffer[MusP++];
		
		if ((event & 0x70) != MUS_SCOREEND)
		{
			t = MusBuffer[MusP++];
		}
		channel = event & 15;

		// Map MUS channels to MIDI channels
		if (channel == 15)
		{
			channel = 9;
		}
		else if (channel >= 9)
		{
			channel = channel + 1;
		}

		status = channel;

		switch (event & 0x70)
		{
		case MUS_NOTEOFF:
			status |= MIDI_NOTEOFF;
			mid1 = t;
			mid2 = 64;
			break;
			
		case MUS_NOTEON:
			status |= MIDI_NOTEON;
			mid1 = t & 127;
			if (t & 128)
			{
				LastVelocity[channel] = MusBuffer[MusP++];;
			}
			mid2 = LastVelocity[channel];
			break;
			
		case MUS_PITCHBEND:
			status |= MIDI_PITCHBEND;
			mid1 = (t & 1) << 6;
			mid2 = (t >> 1) & 127;
			break;
			
		case MUS_SYSEVENT:
			status |= MIDI_CTRLCHANGE;
			mid1 = CtrlTranslate[t];
			mid2 = t == 12 ? SHORT(MusHeader->NumChans) : 0;
			break;
			
		case MUS_CTRLCHANGE:
			if (t == 0)
			{ // program change
				status |= MIDI_PRGMCHANGE;
				mid1 = MusBuffer[MusP++];
				mid2 = 0;
			}
			else
			{
				status |= MIDI_CTRLCHANGE;
				mid1 = CtrlTranslate[t];
				mid2 = MusBuffer[MusP++];

				// Some device don't support master volume
				// (e.g. the Audigy's software MIDI synth--but not its two hardware ones),
				// so assume none of them do and scale channel volumes manually.
				if (mid1 == 7)
				{
					ChannelVolumes[channel] = mid2;
					mid2 = (BYTE)(((mid2 + 1) * (midivolume & 0xffff)) >> 16);
				}
			}
			break;
			
		case MUS_SCOREEND:
		default:
			return SEND_DONE;
			break;
		}

		if (MMSYSERR_NOERROR != midiOutShortMsg (MidiOut, status | (mid1 << 8) | (mid2 << 16)))
		{
			return SEND_DONE;
		}
		if (event & 128)
		{
			return SEND_WAIT;
		}
	}

	return SEND_DONE;
}
