/*
 *    Rosegarden MIDI Sequencer
 *
 *    File:           Record_LINUX.c
 *
 *    Description:    Sequencer Record functions.
 *
 *    Author:         AJG
 *
 *    History:
 *
 *    Update    Date            Programmer      Comments
 *    ======    ====            ==========      ========
 *    001       21/04/94        AJG             File Created.
 *    002       17/02/95        cc              Added #ifdefs for HAVE_Z8530
 *    003       24/05/95        cc              Archer Sully's SGI MIDI lib
 *                                                                       stuff
 *    004       10/05/96        rwb             Linuxified.
 *
 *
 *
 */

#undef POSIX_PLEASE
#undef _POSIX_SOURCE

#include "MidiSetupDlgs.h"
#include "Record.h"
#include "Sequence.h"

#include "Globals.h"
#include "Consts.h"

#include <MidiXInclude.h>
#include <MidiFile.h>
#include <MidiErrorHandler.h>
#include <Debug.h>
#include <MidiBHeap.h>
#include <MidiTrack.h>

#include "TrackList.h"

#include <signal.h>
#include <stdio.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/times.h>
#include <unistd.h>
#include <sys/signal.h>

long    RecordTempo         = 500000;
int     MidiEventBufferSize = 10000;

extern char *MidiPortName;
extern int Midi_OpenDevice();
extern int MidiDev;
EventList Midi_SeqConvertRawDataToEventList(void);

typedef struct
{
    unsigned long int Time;  /* USS Lite timing information */
    byte              Bytes[3];
} MIDIRawEvent, *MIDIRawEventBuffer;

MIDIRawEventBuffer IncomingEventBuf;
MIDIRawEventBuffer NextEventPtr;
Widget             StopRecordDlg;
Boolean            Recording;
int                MidiEventBufferTimer = 0;

void Midi_SeqRecord();
EventList Midi_SeqRecordTimingDefaults();

extern int seqfd;

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

    XtDestroyWidget(StopRecordDlg);

    Recording = False;

    /* clear down the widget and close the port */

    close(seqfd);
END;
}

void Midi_SeqReadTrack()
{
Widget     StopRecordButton;
XPoint     op;

BEGIN("Midi_SeqReadTrack");

    IncomingEventBuf = (MIDIRawEventBuffer)malloc(MidiEventBufferSize
                                                      * sizeof(MIDIRawEvent));

    if (!IncomingEventBuf)
    {
         Error(NON_FATAL_REPORT_TO_MSGBOX, "Unable to allocate event buffer.");
         END;
    }

    NextEventPtr = IncomingEventBuf;

    /* open the device (non blocking) - device returned to seqfd */
    if ( Midi_OpenDevice(O_RDWR|O_NONBLOCK, MidiPortName ) != True )
    {
#ifdef DEBUG
        fprintf(stderr,"midi device failed to open\n");
#endif
        return;
    }

    StopRecordDlg = XtCreatePopupShell("Stop Recording Dialogue",
                                transientShellWidgetClass, topLevel, NULL, 0);

    StopRecordButton = YCreateSurroundedWidget("STOP", commandWidgetClass,
                                   StopRecordDlg, SurroundShade, NoShade);

    XtAddCallback(StopRecordButton, XtNcallback, Midi_SeqStopRecordingCB,
                                                              NULL);

    op = YPlacePopupAndWarp(StopRecordDlg, XtGrabNonexclusive,
            StopRecordButton, StopRecordButton);

    YAssertDialogueActions(StopRecordDlg, StopRecordButton,
        StopRecordButton, NULL); 

    Recording = True;

    while(XtAppPending(appContext))
    {
        XtAppProcessEvent(appContext, XtIMAll);
    }

    Recording = True;

    Midi_SeqRecord();

    if ( Recording == True )
        Midi_SeqStopRecordingCB(NULL, NULL, NULL);

END;
}



void
Midi_SeqRecord(void)
{
    byte             LastStatus   = 0x90;
    byte             NumOperands  = 0;
    byte             OperandsLeft = 0;

    unsigned char    InBytes[4];
    int              out, pass;

    unsigned long int StartTime = 0;

BEGIN("Midi_SeqRecord");

    while(Recording)
    {
        while (XtAppPending(appContext))
            XtAppProcessEvent(appContext, XtIMAll);

        /* read the midi port - this is a non blocking descriptor.
           ensures that any read failues are ignored by the for loop */

        if ( ( out = read(seqfd, &InBytes, sizeof(InBytes)) ) < 0 )
            out = 0;

        /* ensure that we read all pending "read" events */
        for ( pass = 0; pass < (out/sizeof(InBytes)); pass++)
        {
            /* check the API's returned event type */
            switch ( InBytes[0] )
            {
                case SEQ_WAIT:
                   /* for the moment we'll just start recording from
                      a normalised "zero" */
                    if ( StartTime == 0 )
                    {
                        StartTime = (((InBytes[3]<<16)|(InBytes[2]<<8)|
                                          InBytes[1]) - 40 );
                    }

                    NextEventPtr->Time = ((InBytes[3]<<16)|(InBytes[2]<<8)|
                                          InBytes[1]) - StartTime;

                    break;

                case SEQ_ECHO:
                    /* no echo events yet defined */
                    fprintf(stderr,"ECHO EVENT\n");
                    break;

                case SEQ_MIDIPUTC:
                    if (IsStatusByte(InBytes[1]))
                    {
                        if (MessageType(InBytes[1]) == MIDI_SYSTEM_MSG)
                        {
                            NumOperands = 0; /* no timing info */
                            fprintf(stderr, "SYSTEM MESSAGE\n");
                        }
                        else if (MessageType(InBytes[1]) == MIDI_CTRL_CHANGE ||
                                   MessageType(InBytes[1]) == MIDI_PROG_CHANGE)
                        {
                            NumOperands = 1;
                        }
                        else
                        {
                            NumOperands = 2;
                        }
                        LastStatus = InBytes[1];
                        OperandsLeft = NumOperands;
                    }

                    if (OperandsLeft)
                    {
                        NextEventPtr->Bytes[0] = LastStatus;

                        if (!IsStatusByte(InBytes[1]))
                        {
                            NextEventPtr->Bytes[(OperandsLeft == 2) ? 1 : 2 ]
                                    = InBytes[1];

                            --OperandsLeft;
                        }

                        if ( !OperandsLeft )
                        {
#ifdef DEBUG
                            fprintf(stderr, "MIDI EVENT : %ld %x %x %x\n",
                                        NextEventPtr->Time,
                                        (int)NextEventPtr->Bytes[0],
                                        (int)NextEventPtr->Bytes[1],
                                        (int)NextEventPtr->Bytes[2]);
#endif /* DEBUG */
                            ++NextEventPtr;

                            /* if no status byte then just collect another
                               event of the same type */
                            if (!IsStatusByte(InBytes[1]))
                            {
                                OperandsLeft = 2;
                            }
                        }
                    }
                break;
            }
        }
    }

    Midi_TrackListAddTrack(Midi_SeqConvertRawDataToEventList());

END;
}


EventList Midi_SeqConvertRawDataToEventList()
{
    EventList          NewTrack, RunningPtr;
    MIDIEventStruct    EventBuffer;
    MIDIRawEventBuffer EventPtr;

    BEGIN("Midi_SeqConvertRawDataToEventList");

    NewTrack   = NULL;
    RunningPtr = NULL;

    EventPtr = IncomingEventBuf;

    if (MIDIHeaderBuffer.Format == MIDI_NO_FILE_LOADED) 
    {
        Midi_TrackListSetupDefaults();
    }
    while (EventPtr != NextEventPtr)
    {
        EventBuffer.DeltaTime = (((float)(EventPtr->Time * 10000L) /
                        RecordTempo)) * MIDIHeaderBuffer.Timing.Division;

        EventBuffer.EventCode = EventPtr->Bytes[0];

        EventBuffer.EventData.NoteOn.Note     = EventPtr->Bytes[1];
        EventBuffer.EventData.NoteOn.Velocity = EventPtr->Bytes[2];

        if (!NewTrack)
        {
            NewTrack = Midi_EventCreateList(&EventBuffer, True);
            RunningPtr = NewTrack;
        }
        else
        {
            RunningPtr = (EventList)Nconc(RunningPtr, Midi_EventCreateList
                                                        (&EventBuffer, True));
        }
        ++EventPtr;
    }

    EventBuffer.EventCode = MIDI_FILE_META_EVENT;
    EventBuffer.EventData.MetaEvent.MetaEventCode = MIDI_END_OF_TRACK;
    EventBuffer.EventData.MetaEvent.NBytes = 0;

    if (!NewTrack)
    {
        NewTrack = Midi_EventCreateList(&EventBuffer, True);
        RunningPtr = NewTrack;
    }
    else
    {
        RunningPtr = (EventList)Nconc(RunningPtr, Midi_EventCreateList
                                                        (&EventBuffer, True));
    }

    NewTrack = Midi_SeqRecordTimingDefaults(&NewTrack);

    Midi_TrackConvertToOnePointRepresentation(NewTrack);

    RETURN_PTR(NewTrack);
}


/* adds default timing information if either no tracks
   are already available or any of them don't already
   have any timing information */

EventList
Midi_SeqRecordTimingDefaults(EventList *RunningPtr)
{
    int AddTimingInfo = True;
    int i;

    BEGIN("Midi_SeqRecordTimingDefaults");

    for (i = 0; i < MIDIHeaderBuffer.NumTracks; ++i)
    {
        if(Midi_TrackFilterByEvent(MIDITracks[i], MidiSetTempoEventMask))
            AddTimingInfo = False;
    }

    if (AddTimingInfo || (!MIDIHeaderBuffer.NumTracks))
    {
        Midi_TrackListSetupDefaults(); /* MIDI_NO_FILE_LOADED won't 
                                          catch record + deleted tracks */

        (*RunningPtr) = (EventList)Insert(Midi_EventCreateList
           (Midi_EventCreateTempoEvt(0, 120), False), *RunningPtr);

        (*RunningPtr) = (EventList)Insert(Midi_EventCreateList
          (Midi_EventCreateTextEvt (MIDI_TEXT_MARKER, 0,
           "Created by the Rosegarden sequencer"), False), *RunningPtr);

        /* doesn't work for some reason */
        (*RunningPtr) = (EventList)Insert(
           (Midi_EventCreateTextEvt(MIDI_TRACK_NAME, 0,
                 (String)XtNewString("Track 1")), False), *RunningPtr);

    }

    RETURN_PTR(*RunningPtr);
}
