/*
 * MPT_MIDI.cpp
 * ------------
 * Purpose: MIDI Input handling code.
 * Notes  : (currently none)
 * Authors: OpenMPT Devs
 * The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
 */


#include "stdafx.h"
#include <mmsystem.h>
#include "Mainfrm.h"
#include "InputHandler.h"
#include "Dlsbank.h"
#include "../soundlib/MIDIEvents.h"


OPENMPT_NAMESPACE_BEGIN


#ifdef MPT_ALL_LOGGING
#define MPTMIDI_RECORDLOG
#endif


// Midi Input globals
HMIDIIN CMainFrame::shMidiIn = nullptr;

//Get Midi message(dwParam1), apply MIDI settings having effect on volume, and return
//the volume value [0, 256]. In addition value -1 is used as 'use default value'-indicator.
int CMainFrame::ApplyVolumeRelatedSettings(const DWORD &dwParam1, const BYTE midivolume)
{
	int nVol = MIDIEvents::GetDataByte2FromEvent(dwParam1);
	if(TrackerSettings::Instance().m_dwMidiSetup & MIDISETUP_RECORDVELOCITY)
	{
		nVol = (CDLSBank::DLSMidiVolumeToLinear(nVol)+255) >> 8;
		nVol *= TrackerSettings::Instance().midiVelocityAmp / 100;
		Limit(nVol, 1, 256);
		if(TrackerSettings::Instance().m_dwMidiSetup & MIDISETUP_MIDIVOL_TO_NOTEVOL)
			nVol = static_cast<int>((midivolume / 127.0) * nVol);
	} else
	{
		// Case: No velocity record.
		if(TrackerSettings::Instance().m_dwMidiSetup & MIDISETUP_MIDIVOL_TO_NOTEVOL)
			nVol = 4*((midivolume+1)/2);
		else //Use default volume
			nVol = -1;
	}

	return nVol;
}


void ApplyTransposeKeyboardSetting(CMainFrame &rMainFrm, uint32 &dwParam1)
{
	if ( (TrackerSettings::Instance().m_dwMidiSetup & MIDISETUP_TRANSPOSEKEYBOARD)
		&& (MIDIEvents::GetChannelFromEvent(dwParam1) != 9) )
	{
		int nTranspose = rMainFrm.GetBaseOctave() - 4;
		if (nTranspose)
		{
			int note = MIDIEvents::GetDataByte1FromEvent(dwParam1);
			if (note < 0x80)
			{
				note += nTranspose * 12;
				Limit(note, 0, NOTE_MAX - NOTE_MIN);

				dwParam1 &= 0xffff00ff;

				dwParam1 |= (note << 8);
			}
		}
	}
}


/////////////////////////////////////////////////////////////////////////////
// MMSYSTEM Midi Record

void CALLBACK MidiInCallBack(HMIDIIN, UINT wMsg, DWORD_PTR, DWORD_PTR dwParam1, DWORD_PTR dwParam2)
{
	CMainFrame *pMainFrm = CMainFrame::GetMainFrame();
	HWND hWndMidi;

	if (!pMainFrm) return;
#ifdef MPTMIDI_RECORDLOG
	DWORD dwMidiStatus = dwParam1 & 0xFF;
	DWORD dwMidiByte1 = (dwParam1 >> 8) & 0xFF;
	DWORD dwMidiByte2 = (dwParam1 >> 16) & 0xFF;
	DWORD dwTimeStamp = (DWORD)dwParam2;
	MPT_LOG_GLOBAL(LogDebug, "MIDI", MPT_UFORMAT("time={}ms status={} data={}.{}")(mpt::ufmt::dec(dwTimeStamp), mpt::ufmt::HEX0<2>(dwMidiStatus), mpt::ufmt::HEX0<2>(dwMidiByte1), mpt::ufmt::HEX0<2>(dwMidiByte2)));
#endif

	hWndMidi = pMainFrm->GetMidiRecordWnd();
	if(wMsg == MIM_DATA || wMsg == MIM_MOREDATA)
	{
		uint32 data = static_cast<uint32>(dwParam1);
		if(::IsWindow(hWndMidi))
		{
			switch(MIDIEvents::GetTypeFromEvent(data))
			{
			case MIDIEvents::evNoteOff:	// Note Off
			case MIDIEvents::evNoteOn:	// Note On
				ApplyTransposeKeyboardSetting(*pMainFrm, data);
				[[fallthrough]];
			default:
				if(::PostMessage(hWndMidi, WM_MOD_MIDIMSG, data, dwParam2))
					return;	// Message has been handled
				break;
			}
		}
		// Pass MIDI to keyboard handler
		CMainFrame::GetInputHandler()->HandleMIDIMessage(kCtxAllContexts, data);
	} else if(wMsg == MIM_LONGDATA)
	{
		// SysEx...
	} else if (wMsg == MIM_CLOSE)
	{
		// midiInClose will trigger this, but also disconnecting a USB MIDI device (although delayed, seems to be coupled to calling something like midiInGetNumDevs).
		// In the latter case, we need to inform the UI.
		if(CMainFrame::shMidiIn != nullptr)
		{
			pMainFrm->SendMessage(WM_COMMAND, ID_MIDI_RECORD);
		}
	}
}


bool CMainFrame::midiOpenDevice(bool showSettings)
{
	if (shMidiIn) return true;
	
	if (midiInOpen(&shMidiIn, TrackerSettings::Instance().GetCurrentMIDIDevice(), (DWORD_PTR)MidiInCallBack, 0, CALLBACK_FUNCTION) != MMSYSERR_NOERROR)
	{
		shMidiIn = nullptr;

		// Show MIDI configuration on fail.
		if(showSettings)
		{
			CMainFrame::m_nLastOptionsPage = OPTIONS_PAGE_MIDI;
			CMainFrame::GetMainFrame()->OnViewOptions();
		}

		// Let's see if the user updated the settings.
		if(midiInOpen(&shMidiIn, TrackerSettings::Instance().GetCurrentMIDIDevice(), (DWORD_PTR)MidiInCallBack, 0, CALLBACK_FUNCTION) != MMSYSERR_NOERROR)
		{
			shMidiIn = nullptr;
			return false;
		}
	}
	midiInStart(shMidiIn);
	return true;
}


void CMainFrame::midiCloseDevice()
{
	if (shMidiIn)
	{
		// Prevent infinite loop in MIM_CLOSE
		auto handle = shMidiIn;
		shMidiIn = nullptr;
		midiInClose(handle);
	}
}


void CMainFrame::OnMidiRecord()
{
	if (shMidiIn)
	{
		midiCloseDevice();
	} else
	{
		midiOpenDevice();
	}
}


void CMainFrame::OnUpdateMidiRecord(CCmdUI *pCmdUI)
{
	if (pCmdUI) pCmdUI->SetCheck((shMidiIn) ? TRUE : FALSE);
}


OPENMPT_NAMESPACE_END