/*
 *    Rosegarden MIDI Sequencer
 *
 *    File:           MidiSetupDlgs.c
 *
 *    Description:    Code to display and run dialogues from the MIDI menu on
 *                    the sequencer main window.
 *
 *    Author:         AJG
 *
 * History:
 * 
 * Update       Date            Programmer      Comments
 * ======       ====            ==========      ========
 * 001          28/04/94        AJG             File Created.
 * 002          29/05/96        rwb             USS Lite adjustments.
 *
 */

#include <MidiFile.h>
#include <MidiErrorHandler.h>

#include "MidiSetupDlgs.h"
#include "Globals.h"
#include "EventDlgs.h"
#include "Sequence.h"
#include "Main.h"
#include "Menu.h"

#include <ctype.h>
#include <Debug.h>


extern char *MidiPortName;
extern int MidiEventBufferSize;
Widget DeviceDlg;
Widget PatchDlg;
Widget PatchValueField[16];
Widget TrackingMenu;
extern int SynthEnable;
extern char *MidiDevice;
extern char *SynthDevice;
extern int MidiDev;
extern int SynthDev;
extern int Playing;
extern int SynthType;
extern void Midi_SeqAllNotesOff();
extern void Midi_SeqPlayFileCB();
extern Widget topLevel;
void Midi_DeviceCloseCB();

void Midi_GetDeviceInfo();
DeviceList Midi_DeviceQuery();

byte InitialPatches[16] = { 40, 41, 42, 43, 44, 45, 46 ,47, 48, 49,
                            50, 51, 52, 53, 54, 55 };
void Midi_PatchOKCB();

Boolean SetupSync;
Boolean MaintainTempo;

Widget SetupDlg;
Widget SetupEventBufSizeField;
Widget DevicePlay;
Widget SetupDeviceField;

void Midi_SetupDlg(void);

void Midi_SetupCB(Widget w, XtPointer a, XtPointer b)
{
BEGIN("Midi_SetupCB");

	Midi_SetupDlg();

END;
}

void Midi_SetupCancelCB(Widget w, XtPointer a, XtPointer b)
{
BEGIN("Midi_SetupCancelCB");

	YDestroyOptionMenu(TrackingMenu);
	XtDestroyWidget(SetupDlg);

END;
}

void Midi_SynthPlaybackCB(Widget w, XtPointer a, XtPointer b)
{
    Dimension ButtonWidth;

BEGIN("Midi_SynthPlaybackCB");

    SynthEnable = !SynthEnable;

    YGetValue(w, XtNwidth, &ButtonWidth);

    if ( SynthEnable )
    {
        YSetValue(w, XtNlabel, SYNTH_PLAYBACK);
    }
    else
    {
        YSetValue(w, XtNlabel, MIDI_PLAYBACK);
    }

    YSetValue(w, XtNwidth, ButtonWidth);
END;
}

void Midi_SetupSyncCB(Widget w, XtPointer a, XtPointer b)
{
BEGIN("Midi_SetupSyncCB");

	SetupSync = !SetupSync;

END;
}



void Midi_SetupMaintainTempoCB(Widget w, XtPointer a, XtPointer b)
{
BEGIN("Midi_SetupMaintainTempoCB");

	MaintainTempo = !MaintainTempo;

END;
}


/*
 * Function:     Midi_SetupOKCB
 *
 * Description:  Gets any return values from the Dialog.
 *
 */
void Midi_SetupOKCB(Widget w, XtPointer a, XtPointer b)
{
char *EventBufStr;
int   EventBufSize;
char *DeviceStr;

BEGIN("Midi_SetupOKCB");

    YGetValue(SetupEventBufSizeField, XtNstring, &EventBufStr);

    EventBufSize = atoi(EventBufStr);

    if (EventBufSize < 1) 
    {
        YQuery(SetupDlg, "Invalid Event Buffer Size", 1, 0, 0, "Continue",
                                                                       NULL);
        END;
    }

    MidiEventBufferSize = EventBufSize;



    YGetValue(SetupDeviceField, XtNstring, &DeviceStr);

    if ( !Midi_DeviceQuery(DeviceStr) )
    {
        YQuery(SetupDlg, "Device Invalid or Busy", 1, 0, 0, "Continue", NULL);
        END;
    }

    YDestroyOptionMenu(TrackingMenu);
    XtDestroyWidget(SetupDlg);

END;
}

	
void Midi_SetupDlg(void)
{
    Widget SetupPane;
    Widget SetupTopBox;
    Widget SetupLabel;

    Widget SetupForm;
    Widget SetupPortNameLabel;
    Widget SetupEventBufSizeLabel;
    Widget SetupBottomBox;
    Widget SetupOK;
    Widget SetupCancel;
    Widget SetupHelp = 0;
    Widget SetupSynthButton;
    Widget SetupSynthNameLabel;
    Widget SetupSynthNameField;
    Widget SetupDeviceLabel;
    Widget SetupMidiPortField;
    Dimension ButtonWidth;
    Dimension LabelWidth;

    XPoint op;
    char   TextBuffer[32];

BEGIN("Midi_SetupDlg");

    SetupDlg = XtCreatePopupShell("Midi Setup", transientShellWidgetClass, 
                                     topLevel, NULL, 0);

    SetupPane = YCreateWidget("Midi Setup Pane", panedWidgetClass, SetupDlg);

    SetupTopBox = YCreateShadedWidget("Midi Setup Title Box", boxWidgetClass,
                                              SetupPane, MediumShade);

    SetupLabel = YCreateLabel("Configure MIDI Setup", SetupTopBox);

	
    SetupForm = YCreateShadedWidget("Midi Setup Form", formWidgetClass,
                                              SetupPane, LightShade);
    SetupDeviceLabel = YCreateLabel("Device : ", SetupForm);
    SetupSynthNameLabel = YCreateLabel("Synthesiser : ", SetupForm);

    /* we're using the MidiPort as the Device i.e. /dev/sequencer */
    SetupDeviceField = YCreateSurroundedWidget("Device Setup Field",
                           asciiTextWidgetClass, SetupForm, NoShade, NoShade);

    if ( SynthDev >= 0 )
        sprintf(TextBuffer, "%s (%d)", SynthDevice, SynthDev);
    else
        sprintf(TextBuffer, "%s", SynthDevice);
 
    SetupSynthNameField = YCreateLabel ( TextBuffer, SetupForm);

    if ( MidiDev >= 0 )
        sprintf(TextBuffer, "%s (%d)", MidiDevice, MidiDev);
    else
        sprintf(TextBuffer, "%s", MidiDevice);
        
    SetupMidiPortField = YCreateLabel ( TextBuffer, SetupForm);

    SetupPortNameLabel = YCreateLabel("Midi Port:", SetupForm);

    /* set temporary maximum width and change as needed */
    SetupSynthButton = YCreateCommand(MIDI_PLAYBACK, SetupForm);

    YGetValue(SetupSynthButton, XtNwidth, &ButtonWidth);

    if ( SynthEnable )
    {
        YSetValue(SetupSynthButton, XtNlabel, SYNTH_PLAYBACK);
    }

    YSetValue(SetupSynthButton, XtNwidth, ButtonWidth);

    SetupEventBufSizeLabel = YCreateLabel("Event Buffer Size:", SetupForm);

    SetupEventBufSizeField = YCreateSurroundedWidget("Event Buffer Size",
                           asciiTextWidgetClass, SetupForm, NoShade, NoShade);

    TrackingMenu = Midi_PlayTrackingMenuButton(SetupForm);

    SetupBottomBox = YCreateShadedWidget("Midi Setup Button Box",
                           boxWidgetClass, SetupPane, MediumShade);

    SetupOK     = YCreateCommand("OK", SetupBottomBox);
    SetupCancel = YCreateCommand("Cancel", SetupBottomBox);

    if (appData.interlockWindow)
    {
        SetupHelp   = YCreateCommand("Help", SetupBottomBox);
        XtAddCallback(SetupHelp,   XtNcallback, Midi_HelpCallback,
                                               "Midi - MIDI Setup");
    }


    XtAddCallback(SetupOK, XtNcallback, Midi_SetupOKCB, NULL);
    XtAddCallback(SetupCancel, XtNcallback, Midi_SetupCancelCB, NULL);

    XtAddCallback(SetupSynthButton, XtNcallback, Midi_SynthPlaybackCB, NULL);

/************************************/
/* Format the form widget contents. */
/************************************/

    YSetValue(XtParent(SetupEventBufSizeLabel), XtNfromVert,
                                         XtParent(SetupPortNameLabel));
    YSetValue(XtParent(SetupEventBufSizeField), XtNfromHoriz,
                                         XtParent(SetupEventBufSizeLabel));
    YSetValue(XtParent(SetupEventBufSizeField), XtNfromVert,
                                         XtParent(SetupPortNameLabel));

    YSetValue(XtParent(TrackingMenu), XtNfromVert,
	      XtParent(SetupEventBufSizeLabel));

    YSetValue(XtParent(SetupDeviceLabel), XtNfromVert,
                                             XtParent(SetupSynthButton));

    YSetValue(XtParent(SetupDeviceField), XtNfromVert,
                                             XtParent(SetupSynthButton));

    YSetValue(XtParent(SetupDeviceField), XtNfromHoriz,
                                             XtParent(SetupDeviceLabel));

    YSetValue(XtParent(SetupSynthNameLabel), XtNfromVert,
                                             XtParent(SetupDeviceLabel));

    YSetValue(XtParent(SetupSynthNameField), XtNfromHoriz,
                                             XtParent(SetupSynthNameLabel));

    YSetValue(XtParent(SetupSynthNameField), XtNfromVert,
                                             XtParent(SetupDeviceField));

    YSetValue(XtParent(SetupPortNameLabel), XtNfromVert,
                                             XtParent(SetupSynthNameLabel));

    YSetValue(XtParent(SetupMidiPortField), XtNfromHoriz,
                                             XtParent(SetupPortNameLabel));

    YSetValue(XtParent(SetupMidiPortField), XtNfromVert,
                                             XtParent(SetupSynthNameField));

    YSetValue(SetupEventBufSizeField, XtNeditType, XawtextEdit);
    sprintf(TextBuffer, "%d", MidiEventBufferSize);
    YSetValue(SetupEventBufSizeField, XtNstring, TextBuffer);

    YSetValue(SetupDeviceField, XtNeditType, XawtextEdit);
    sprintf(TextBuffer, "%s", MidiPortName);
    YSetValue(SetupDeviceField, XtNstring, TextBuffer);
    
    YGetValue(SetupDeviceField, XtNwidth, &LabelWidth);
    YSetValue(SetupDeviceField, XtNwidth, ((int)LabelWidth * 2 ));
    YSetValue(SetupEventBufSizeField, XtNwidth, ((int)LabelWidth * 2 ));
    YSetValue(SetupMidiPortField, XtNwidth, ((int)LabelWidth * 2 ));
    YSetValue(SetupSynthNameField, XtNwidth, ((int)LabelWidth * 2 ));

    YSetValue(SetupEventBufSizeLabel, XtNjustify, XtJustifyLeft);
    YSetValue(SetupDeviceLabel, XtNjustify, XtJustifyLeft);
    YSetValue(SetupPortNameLabel, XtNjustify, XtJustifyLeft);
    YSetValue(SetupSynthNameLabel, XtNjustify, XtJustifyLeft);

    YGetValue(SetupEventBufSizeLabel, XtNwidth, &LabelWidth);
    YSetValue(SetupDeviceLabel, XtNwidth, LabelWidth);
    YSetValue(SetupPortNameLabel, XtNwidth, LabelWidth);
    YSetValue(SetupSynthNameLabel, XtNwidth, LabelWidth);

    op = YPlacePopupAndWarp(SetupDlg, XtGrabNonexclusive, SetupOK, SetupCancel);

    YFixOptionMenuLabel(TrackingMenu);
    YAssertDialogueActions(SetupDlg, SetupOK, SetupCancel, SetupHelp);
END;
}

/*
 * Function:     Midi_SeqStopPlayingCB
 *
 * Description:  Clear down all the output channels and then close the port.
 *
 */
void Midi_SeqStopPlayingCB(Widget w, XtPointer a, XtPointer b)
{
BEGIN("Midi_SeqStopPlayingCB");

    if ( Playing )
    {
        /* halt the timer */
        SEQ_STOP_TIMER();

        /* clear all MIDI channels */
        Midi_SeqAllNotesOff();
        SEQ_DUMPBUF();

        /* free the midi port */
        close(seqfd);

        Playing = False;
    }

    Midi_PlayTrackingClose();
    XUndefineCursor(display, XtWindow(topLevel));
    XSync(display, False);

END;
}


/*
 * Function:     Midi_NewDeviceList
 *
 * Description:  Dbly Linked Lists constructor for the
 *               returning of synth and midi device info.
 *
 */
DeviceList
Midi_NewDeviceList( DeviceInformation NewDevice, int Type )
{
    DeviceList newlist;

    BEGIN("MidiNewDeviceList");

    newlist = (DeviceList) NewList (sizeof(DeviceListElement));
    newlist->Port = NewDevice;
    newlist->Type = Type;

   RETURN_PTR(newlist);
}



/*
 * Function:     Midi_DeviceCloseCB
 *
 * Description:  
 *
 */
void
Midi_DeviceCloseCB(Widget w, XtPointer a, XtPointer b)
{
    BEGIN("Midi_DeviceCloseCB");

    /* close down playing */
    Midi_SeqStopPlayingCB(NULL, NULL, NULL);
  
    Midi_LeaveMenuMode(PlaybackMode);
    Midi_EnterMenuMode(NotPlayingMode);

    END;
}


/*
 * Function:     Midi_OpenDevice
 *
 * Description:  Opens a device according to filtering, returns
 *               any problems directly and bails out.
 *
 */
int
Midi_OpenDevice(int Sense, char *Device)
{
#ifdef DEBUG
    char        ErrBuff[128];
#endif

    BEGIN("Midi_OpenDevice");

    if ( ( seqfd = open ( Device, Sense, 0 ) ) == -1 )
    {

#ifdef DEBUG
        switch (errno)
        {
            case ENOENT:
                sprintf(ErrBuff, "\"%s\" : Device File Missing\n", Device);
                break;

            case ENODEV:
                sprintf(ErrBuff, "\"%s\" : Device Driver Not Installed\n",
                                                     Device);
                break;

            case ENXIO:
                sprintf(ErrBuff, "\"%s\" : No Hardware detected\n", Device);
                break;

            case EBUSY:
                sprintf(ErrBuff, "Device \"%s\" is busy\n", Device);
                break;

            default:
                sprintf(ErrBuff, "\"%s\" : Cannot Open Device\n", Device);
                break;
        }
        Error(NON_FATAL_REPORT_TO_MSGBOX, ErrBuff);
#endif
        RETURN_BOOL(False);
    }

    RETURN_BOOL(True);
}



/*
 *
 * for the moment borrowing a very cut down fm patch
 * loading algorithm (and patch file ) from playmidi
 * and adagio just so that we can make some noise.
 *
 */
void
Midi_LoadFMPatchSet()
{
    struct sbi_instrument Instrument;
    int i,j;
    int PatchFile;
    byte Buffer[60];
    int DataSize;
    int VoiceSize;

    BEGIN("Midi_LoadFMPatchSet");

    /* if no FM synth then don't bother loading */
    if ( SynthType != SYNTH_TYPE_FM )
        return;

    Instrument.device = SynthDev;
    Instrument.key = FM_PATCH;
    VoiceSize = 52;
    DataSize = 11;

    PatchFile = open(appData.midiFmPatchFile, O_RDONLY, 0);

    if ( PatchFile == -1 )
    {
        Error(NON_FATAL_REPORT_TO_MSGBOX,
                                "Cannot open Synthesiser Patch File\n");
        return;
    }

    if ( Midi_OpenDevice(O_WRONLY, MidiPortName) != True )
        return;

    for ( i = 0; i < 128; i++ )
    {

        if (read(PatchFile, Buffer, VoiceSize) != VoiceSize)
        {
            Error(NON_FATAL_REPORT_TO_MSGBOX,
                               "Short FM Patch File - Aborting Patch Load\n");
            return;
        }

        Instrument.channel = i;

        Buffer[46] = (Buffer[46] & 0xcf) | 0x30;  /* provide two channel
                                                     output it seems */

        for ( j = 0; j < 32; j++ )
            Instrument.operators[j] = (j < DataSize) ? Buffer[36 + j] : 0;

        SEQ_WRPATCH(&Instrument,sizeof(Instrument));
    }
    close(PatchFile);

    close(seqfd);
END;
}
 

/*
 * Still unstable - no checking for length or integrity of fields
 * 
 */
void
Midi_InitialPatchesDlg(void)
{
    Widget PatchPane;
    Widget PatchTopBox;
    Widget PatchLabel;
    Widget PatchForm;
    Widget PatchBottomBox;
    Widget PatchOK;
    Widget PatchValueLabel[16];
    XPoint op;
    Dimension LabelWidth;

    char TextBuffer[32];
    char Patches[16][3];
    int i;

    PatchDlg = XtCreatePopupShell("Patch Setup", transientShellWidgetClass, 
                                     topLevel, NULL, 0);

    PatchPane = YCreateWidget("Patch Setup Pane", panedWidgetClass, PatchDlg);

    PatchTopBox = YCreateShadedWidget("Patch Setup Title Box", boxWidgetClass,
                                              PatchPane, MediumShade);

    PatchLabel = YCreateLabel("Initial External MIDI Patches", PatchTopBox);

	
    PatchForm = YCreateShadedWidget("Patch Setup Form", formWidgetClass,
                                              PatchPane, LightShade);
    for ( i = 0; i < 16; i++ )
    {
        sprintf(TextBuffer,"MIDI Channel %d",i);
        PatchValueLabel[i] = YCreateLabel(TextBuffer, PatchForm);

        sprintf(TextBuffer,"MIDI Patch Channel %d",i);
        PatchValueField[i] = YCreateSurroundedWidget(TextBuffer,
                     asciiTextWidgetClass, PatchForm, NoShade, NoShade);

        if ( i > 0 )
        {
            YSetValue(XtParent(PatchValueField[i]),XtNfromVert,
                                XtParent(PatchValueField[i-1]));

            YSetValue(XtParent(PatchValueLabel[i]),XtNfromVert,
                                XtParent(PatchValueLabel[i-1]));
        }

        YSetValue(PatchValueLabel[i], XtNjustify, XtJustifyLeft);

        YSetValue(XtParent(PatchValueField[i]),XtNfromHoriz,
                             XtParent(PatchValueLabel[i]));

        /* write the patch values */
        sprintf(Patches[i],"%d",InitialPatches[i]);
        YSetValue(PatchValueField[i], XtNeditType, XawtextEdit);
        YSetValue(PatchValueField[i], XtNstring, Patches[i]);
    }

    /* reset all the label widths to the last (widest) */
    YGetValue(PatchValueLabel[15], XtNwidth, &LabelWidth);
    for ( i = 0; i <15; i++ )
        YSetValue(PatchValueLabel[i], XtNwidth, LabelWidth);

    PatchBottomBox = YCreateShadedWidget("Patch Setup Button Box",
                           boxWidgetClass, PatchPane, MediumShade);
                           
    PatchOK = YCreateCommand("OK", PatchBottomBox);

    XtAddCallback(PatchOK, XtNcallback, Midi_PatchOKCB, NULL);

    op = YPlacePopupAndWarp(PatchDlg, XtGrabNonexclusive, PatchOK, PatchOK);

    YAssertDialogueActions(PatchDlg, PatchOK, PatchOK, PatchOK);

}


/*
 * Function:     Midi_GetDeviceInfo
 *
 * Description:  Currently crudely gets _a_ member of the returned
 *               device list to use for Synth and Midi.
 *
 */
void
Midi_GetDeviceInfo(void)
{
    DeviceList Current;
    DeviceList Devices;
BEGIN("Midi_GetDeviceInfo");

    /* initialise all device parameters as "missing" */
    MidiDev = -1;
    SynthDev = -1;
    MidiDevice = XtNewString("No Midi Device");
    SynthDevice = XtNewString("No Synthesiser");

    if ( ! ( Devices = Midi_DeviceQuery(MidiPortName) ) )
    {
        /* no devices found - return devices as unavailable */
        YQuery(topLevel, "Sequencer Device Invalid or Busy", 1, 0, 0,
                                                        "Continue", NULL);
        MidiPortName=XtNewString("/dev/null");
        return;
    }

    /* return synth name and device number plus
       midi port name and device number */

    Current = (DeviceList) LIST_First( (List) Devices );

    while(Current)
    {
        if ( Current->Type == MIDI_DEVICE )
        {
            MidiDev = Current->Port.Midi.device;
            MidiDevice = XtNewString(Current->Port.Midi.name);
        }
        if ( Current->Type == SYNTH_DEVICE )
        {
            SynthDev = Current->Port.Synth.device;
            SynthDevice = XtNewString(Current->Port.Synth.name);

            /* hack in a device cheat here for the moment
               just so we can see (later0 if we've got an FM
               synth for patch loading */

            SynthType = (Current->Port.Synth.synth_type);
        }
        Current = (DeviceList) Next(Current);
    }

END;
}


/*
 * Function:     Midi_DeviceQuery
 *
 * Description:  Opens device and queries for all Midi ports
 *               and available synths.  If everything
 *
 */
DeviceList Midi_DeviceQuery(char *Device)
{
    int midi_devs = 0;
    int synth_devs = 0;
    DeviceInformation TempDevice;
    DeviceList Devices = NULL,
               CurrentDev = NULL;

    int i;

BEGIN("Midi_QueryDevice");

    /* open device */
    if ( Midi_OpenDevice(O_WRONLY, Device) != True )
        return NULL;

    /* call to see if there are any synths on-line */
    ioctl(seqfd, SNDCTL_SEQ_NRSYNTHS, &synth_devs);

    /* see if we have any MIDI devices available */
    ioctl(seqfd, SNDCTL_SEQ_NRMIDIS, &midi_devs);

    if ( (!midi_devs) && (!synth_devs) )
    {
#ifdef DEBUG
        Error(NON_FATAL_REPORT_TO_MSGBOX,"No Configurable Devices on Port\n");
#endif
        close(seqfd);
        return NULL;
    }

    /* construct the devices return list */
    for ( i = 0; i < synth_devs; i++ )
    {
        /* initialise the structure */
        TempDevice.Synth.device = i;

        ioctl(seqfd, SNDCTL_SYNTH_INFO, &(TempDevice.Synth));

        if ( Devices == NULL )
        {
            Devices = (DeviceList)Midi_NewDeviceList(TempDevice, SYNTH_DEVICE);
        }
        else
        {
             /* append */
            CurrentDev = (DeviceList)Midi_NewDeviceList(TempDevice,
                                                            SYNTH_DEVICE);
            LIST_Insert ( (List) Devices, (List) CurrentDev );
        }
    }

    for ( i = 0; i < midi_devs; i++ )
    {
        TempDevice.Midi.device = i;
        ioctl(seqfd, SNDCTL_MIDI_INFO, &(TempDevice.Midi));

        if ( Devices == NULL )
        {
            Devices = (DeviceList)Midi_NewDeviceList(TempDevice, MIDI_DEVICE);
        }
        else
        {
            /* again append */
            CurrentDev = (DeviceList)Midi_NewDeviceList(TempDevice,
                                                                MIDI_DEVICE);
            LIST_Insert ( (List) Devices, (List) CurrentDev );
        }
    }

    /* finished gathering information - close the port down */
    close(seqfd);

    /* return a stack of pertinent data to the calling function */
    RETURN_PTR(Devices);
}


void
Midi_PatchOKCB(Widget w, XtPointer a, XtPointer b)
{
    int i;
    char *Patches;
    BEGIN("Midi_PatchOKCB");

    /* write the patches back from their temporary strings */
    for ( i = 0; i < 16; i++ )
    {
        YGetValue(PatchValueField[i], XtNstring, &Patches);
        InitialPatches[i] = (byte) atoi(Patches);
        if ( InitialPatches[i] > 127 )
        {
            YQuery(PatchDlg, "Invalid Patch Number", 1, 0, 0, "Continue",
                                                                       NULL);
            END;
        }
    }

    XtDestroyWidget(PatchDlg);

END;
}
