#include "project.h"

#pragma comment (lib,"Winmm.lib")

// -----------------------------------------------------------------------------
// Midi Out

HMIDIOUT hmo = NULL;

static  MMRESULT midi_out_close()
{
    if (hmo EQU NULL) return MMSYSERR_BADDEVICEID;
    return midiOutClose(hmo);
}

static MMRESULT midi_out_open(UINT uDeviceid)
{
    midi_out_close();
    return midiOutOpen(&hmo, uDeviceid, (DWORD_PTR)NULL, (DWORD_PTR)NULL, CALLBACK_NULL);
}

static MMRESULT midi_Out_Short_Msg(DWORD    dwMsg )
{
    if (hmo EQU NULL) return MMSYSERR_BADDEVICEID;
    return  midiOutShortMsg(hmo, dwMsg);
}
static MMRESULT midi_Out_Set_Volume (DWORD    dwVolume)
{
    if (hmo EQU NULL) return MMSYSERR_BADDEVICEID;
    return  midiOutSetVolume(hmo, dwVolume);
}

// -----------------------------------------------------------------------------
// Midi In

static LRESULT MM_MIM_ERROR_handler(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    addText((TCHAR*)L"MM_MIM_ERROR: \n");
    return msg_handled_by_application;
}

static LRESULT MM_MIM_OPEN_handler(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    addText((TCHAR*)L"MM_MIM_OPEN: ");
    debugIntn((myUInt)hWnd); debugSpaces(1);
    debugIntn((myUInt)message);debugSpaces(1);
    debugIntn((myUInt)wParam);debugSpaces(1);
    debugInt((myUInt)lParam);

    return msg_handled_by_application;
}

static TCHAR* midiNoteToText(int midiNote)
{
    switch (midiNote % 12)
    {
        case 0: return (TCHAR*)_TEXT("C "); break;
            case 1: return (TCHAR*)_TEXT("C#"); break;
            case 2: return (TCHAR*)_TEXT("D "); break;
            case 3: return (TCHAR*)_TEXT("Eb"); break;
            case 4: return (TCHAR*)_TEXT("E "); break;
            case 5: return (TCHAR*)_TEXT("F "); break;
            case 6: return (TCHAR*)_TEXT("F#"); break;
            case 7: return (TCHAR*)_TEXT("G "); break;
            case 8: return (TCHAR*)_TEXT("Ab"); break;
            case 9: return (TCHAR*)_TEXT("A "); break;
            case 10: return (TCHAR*)_TEXT("Bb"); break;
            case 11: return (TCHAR*)_TEXT("B "); break;
        }
        return (TCHAR*)_TEXT("? ");
    }

    static LRESULT MM_MIM_DATA_handler(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
    {
        // this is purely for fun, echo the MIDI IN message to the default MIDI out device.
        midi_Out_Short_Msg((DWORD)(lParam )  );
 
        //  reduce the note to 0..11  i.e. ignore the octave.   Where  Note = 0 , 12 , 24,36.. = C
        int note = (int)(((  (lParam  )  BAND 0x0ff00) / 0x100) % 12);
        //  Note  C = 0
        //        C# = 1
        // ..
        //        B = 11

        int MsgType = (int)(lParam BAND 0x00000f0);
        int chnNbr = (int)(lParam BAND 0x000000f);
        int MsgB = (int)(lParam BAND 0x0007f00) / 0x0100;
        int midiNote = (int)(((lParam)BAND 0x0ff00) / 0x100);

        int MsgC = (int)(lParam BAND 0x07f0000) / 0x010000;

        switch (MsgType)
        {
            case 0x80:
            swprintf_s(txtBuffer, szTxtBuffer, _TEXT("Channel %02i   Note %03i Off  %ws  Vel %03i\n"), chnNbr, MsgB, midiNoteToText(midiNote), MsgC);	addText(txtBuffer);
            break;

        case 0x90:
            swprintf_s(txtBuffer, szTxtBuffer, _TEXT("Channel %02i   Note %03i On   %ws  Vel %03i\n"), chnNbr, MsgB, midiNoteToText(midiNote), MsgC);	addText(txtBuffer);
            break;

        case 0xB0:
            swprintf_s(txtBuffer, szTxtBuffer, _TEXT("Channel %02i   Control %03i   %ws  Val %03i\n"), chnNbr, MsgB, _TEXT("  "), MsgC);	addText(txtBuffer);
            if (MsgB EQU 0x01)
            {
                // purely for fun, use controller nbr 1 to set the instrument for this channel
                midi_Out_Short_Msg( (MsgC * 0x0100)  + 0xc0 + chnNbr);
            }
            break;

        case 0xE0:
            swprintf_s(txtBuffer, szTxtBuffer, _TEXT("Channel %02i   Pitch Blend %05i  \n"), chnNbr, MsgC * 0x07f + MsgB);	addText(txtBuffer);
            break;

        default:
            swprintf_s(txtBuffer, szTxtBuffer, L" Data: 0x%06x \n", (int)(lParam));	addText(txtBuffer);
            break;
    }

    return msg_handled_by_application;
}
static LRESULT MM_MIM_LONGDATA_handler(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    addText((TCHAR*)L"MM_MIM_LONGDATA: \n");
    return msg_handled_by_application;
}

HMIDIIN hmi = NULL;

static void midi_in_close()
{
    if (hmi NEQU NULL)	(void) midiInClose(hmi);
    hmi = NULL;
}

static LRESULT midi_menu_openN(UINT uDeviceID)
{
    midi_in_close();

    UINT nbrMidInDevs = midiInGetNumDevs();
    if (!(uDeviceID < nbrMidInDevs))
    {
        addText((TCHAR*)L"Failure: midi_menu_open  invalid device index");
        return msg_handled_by_application;
    }

    MMRESULT mRes = midiInOpen
    ( &hmi
    , uDeviceID
    , (DWORD_PTR)appHWND
    , 0
    , CALLBACK_WINDOW
    );

    if (mRes NEQU MMSYSERR_NOERROR)
    {
        addText((TCHAR*)L"Failure: midi_menu_open  error code: ");	debugInt(mRes);
    }
    else
    {
        mRes = midiInStart(hmi);
    }

    return msg_handled_by_application;
}

static void midi_device_select(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam, SOBDATA* sData)
{
    // get the identity of the clicked MENUV object
    // Careful: it can be checked or unchecked
    myUInt WasCheck = MENUV.GetState((MENUVOBJECT*)sData);

    // uncheck ALL MENUVs that belong to the same MENUH
    MENUV.UnStateAll((MENUVOBJECT*)sData);
     
    // we had previously associated each of these MENUV with specific MIDI IN device identit
    // Get the device id of the selected MENUV (was stored in the SOBs internal UserData variable)

    myUInt uDeviceID = MENUV.GetUserData((MENUVOBJECT*)sData);
    {
        // if the clicked MENUV object WAS checked then deselected it
        if (WasCheck)
        {
            midi_in_close();
            MENUV.SetState((MENUVOBJECT*)sData, 0);
        }
        else
        {
            // the clicked MENUV object WAS unchecked, so selected it
            midi_menu_openN((UINT)uDeviceID);
            MENUV.SetState((MENUVOBJECT*)sData, 1);
        }
    }
}

static void midi_sources(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam, SOBDATA* sData)
{
   static myUInt midiListId = -1;
   
    //  rebuild the MIDI IN SOURCE(S) menu each time
    //  that a horizontal menu element is selected

    // ---------------------------------------------------------------
    // see if we have an internal list of connected MIDI-In devices

    if (midiListId EQU - 1)  midiListId = lists_list_new();
    if (midiListId EQU - 1)  return;

    lists_list_allow_duplicates(midiListId, 0);  // NO duplicates!

    // ---------------------------------------------------------------
    // we should have been triggered when a HORIZONTAL MENU item was selected
    // so work out what the sobId was

    // we are going to recreate the HORIZONTAL MENU because something (may have) changed.
    // See which, if any, device is currently selected

    MENUVOBJECT* selObj =(MENUVOBJECT*)  MENUH.GetChecked(sData);

    myUInt  TextKey = 0;
    if (selObj NEQU NULL)
    {
        TextKey = MurmurOAAT64(MENUV.GetTitle(selObj) );
    }

    // ---------------------------------------------------------------
    // empty this HORIZONTAL MENU of its children

    MENUH.DeleteChildren(sData);

    // ---------------------------------------------------------------
    // new OPEN the horizontal menu, i.e. make it the target parent for future
    // vertical menu elements
    MENUH.Open(sData);

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

    MIDIINCAPS micacps;
    UINT uDeviceID;
    MMRESULT mmyRes;
    UINT nbrMidInDevs = midiInGetNumDevs();

    if ( !nbrMidInDevs ) (void) MENUV.Create((TCHAR*)_TEXT("No Midi-In devices"));

    for (uDeviceID = 0; uDeviceID < nbrMidInDevs; uDeviceID++)
    {
        // for each MIDI IN device 

        mmyRes = midiInGetDevCaps(uDeviceID, &micacps, sizeof(MIDIINCAPS));
        if (mmyRes  EQU MMSYSERR_NOERROR)
        {
            //  create a vertical menu element, and give it a callback / event handler
            MENUV.SetEventHandler(MENUV.Create((TCHAR*)micacps.szPname), (EVENTHANDLER)midi_device_select);
            
            // associate this menu element with the MIDI IN device index
            MENUV.SetUserData(MENUV.LastWidget(), uDeviceID);

            // if this MIDI IN device name is the same as the device name that was 
            // previously selected
            if (TextKey EQU MurmurOAAT64(micacps.szPname))
            {
                // if yes, then set this menu element to the selected state
                MENUV.SetState(MENUV.LastWidget(), 1);
            }
        }
    }
}

void midi_init(SOBDATA * menuBar )
{
    // -----------------------------------
    // tell this application's Window's message hander to associate the following
    // message number(s) with a callback/event handler

    (void)register_msg_handler(MM_MIM_ERROR,    & MM_MIM_ERROR_handler);

    // a MM_MIM_OPEN hander is not strictly needed
    // (void)register_msg_handler(MM_MIM_OPEN,     &MM_MIM_OPEN_handler);

    (void)register_msg_handler(MM_MIM_DATA,     &MM_MIM_DATA_handler);
    (void)register_msg_handler(MM_MIM_LONGDATA, &MM_MIM_LONGDATA_handler);

    // -----------------------------------
    // Open the menubar (created earlier) i.e. make it the parent Screen OBJect (SOB) 
    // for following children SOBS.

    MENUBAR.Open( SOBOBJ menuBar);

    // create a horizonal menu for the current parent SOB
    // It is  automatically made the new current parent SOB 
    MENUH.Create((TCHAR*)L"Midi-In Sources");

    // give this MENUH SOB a callback / event handler
    // It is activated when the user clicks on the element with the mouse left-button
    MENUH.SetEventHandler(MENUH.LastWidget(), (EVENTHANDLER)midi_sources);

    // -----------------------------------
    // this is for fun, to echo the incoming Midi msg to the default Midi out device
    midi_out_open(0);             // open the default MIDI out device 
    midi_Out_Set_Volume(-1);      // set the volume to max
    midi_Out_Short_Msg(0x0003c0); // select an instrument (0--127), 3 seems okay
}

void codecontrol_midi(SYS_STATE state)
{
    switch (state)
    {
        case SS_POWER_UP:
            break;

        case SS_POWER_DOWN:
            midi_in_close();
            midi_out_close();
            break;
        case SS_RESTART:
            break;
    }
}
