#include "defs.h"
#include "protos.h"

extern char DeviceName[];
extern UBYTE S_Default[];

struct TagItem ModemProcTagList[] = {
    {   NP_Entry,       modem_task      },
    {   NP_Name,        DeviceName      },
    {   NP_Priority,    MODEM_TASK_PRI  },
    {   NP_StackSize,   MODEM_STACK_SIZE},
    {   TAG_DONE,       TAG_DONE        },
};

/*
 *  Creates and Initialises a Modem
 */
struct NullModem *CreateModem(ULONG modemnum)
{
struct NullModem *modem;

    if( modem = AllocMem(sizeof(struct NullModem), MEMF_PUBLIC | MEMF_CLEAR) ) {

        if( modem->nm_Unit[0] = AllocMem(sizeof(struct NullUnit), MEMF_PUBLIC | MEMF_CLEAR) ) {

            if( modem->nm_Unit[1] = AllocMem(sizeof(struct NullUnit), MEMF_PUBLIC | MEMF_CLEAR) ) {

                if( OpenDevice(TIMERNAME, UNIT_VBLANK, (struct IORequest *)&modem->nm_Timerequest, 0) == 0 ) {
                struct Library *DOSBase;

                    if( DOSBase = OpenLibrary("dos.library",36) ) {
                    struct Process *pr = CreateNewProc(ModemProcTagList);

                        CloseLibrary(DOSBase);

                        if( pr ) {
                        int i;

                            for( i = 0 ; i < 2 ; i++ ) {
                            struct NullUnit *unit = modem->nm_Unit[i];
                            int j;

                                unit->u_Readptr = unit->u_Buffer;
                                NewList((struct List *)&unit->u_Readlist);

                                unit->u_Writeptr = unit->u_Buffer;
                                NewList((struct List *)&unit->u_Writelist);

                                unit->u_Unitnum = i;
                                unit->u_Modem = modem;

                                for( j = 0 ; j < 64 ; j++ )
                                    unit->u_SReg[j] = S_Default[j];

                            }

                            pr->pr_Task.tc_UserData = modem;    /* So the modem Process can find its data */
                            modem->nm_Taskptr = &pr->pr_Task;   /* and so everybody else knows where to signal */

                            InitSemaphore(&modem->nm_Semaphore);

                            modem->nm_Modemnum = modemnum;
                            DevBase->b_Modem[modemnum] = modem;

                            DevBase->b_Lib.lib_OpenCnt++;       /* hold the device for the task */

                            return(modem);  /* gotit */
                        }
                    }
                    CloseDevice( (struct IORequest *)&modem->nm_Timerequest );
                }
                FreeMem(modem->nm_Unit[1], sizeof(struct NullUnit));
            }
            FreeMem(modem->nm_Unit[0], sizeof(struct NullUnit));
        }
        FreeMem(modem, sizeof(struct NullModem));
    }
    return(NULL);
}

/*
 *  open device (in a Forbid)
 *
 *      unitnum:    D0
 *      flags:      D1
 *      iob:        A1
 *
 */
struct NullBase *
DevOpen( __D0 ULONG unitnum,
         __D1 ULONG flags,
         __A1 struct IOExtSer *iob)
{
struct NullBase *db = DevBase;
ULONG modemnum = unitnum >> 1;

    db->b_Lib.lib_OpenCnt++;    /* fake opener so we don't get expunged */

    /*
     *  set fail conditions as default..
     */
    iob->IOSer.io_Error = IOERR_OPENFAIL;
    iob->IOSer.io_Device = (struct Device *)-1;     /* trashed */

    /*
     *  range check
     */
    if( modemnum < MODEM_UNITS ) {
    struct NullModem *modem = db->b_Modem[modemnum];

        if( modem || (modem = CreateModem(modemnum)) ) {
        struct NullUnit *unit = modem->nm_Unit[unitnum & 1];

            if( (unit->u_OpenCnt == 0) || (unit->u_Flags & SERF_SHARED & iob->io_SerFlags) ) {

                /* open the unit */
                if( unit->u_OpenCnt++ == 0 ) {
                    unit->u_Flags = iob->io_SerFlags & SERF_SHARED;
                    unit->u_SReg[SREG_STATE] = STATE_COMMAND;
                    unit->u_SReg[SREG_CMD_COUNT] = 0;
                }

                /* open the modem */
                modem->nm_OpenCnt++;

                /* open the device */
                db->b_Lib.lib_OpenCnt++;

                /* tell them all about it */
                iob->IOSer.io_Device = (struct Device *)db; /* restored */
                iob->IOSer.io_Unit = unit;
                iob->IOSer.io_Error = 0;
                iob->IOSer.io_Message.mn_Node.ln_Type = NT_REPLYMSG;

                /* prefs? pah ;-) */
                iob->io_CtlChar = SER_DEFAULT_CTLCHAR;
                iob->io_RBufLen = UNIT_BUF_SIZE;
                iob->io_Baud = 9600;

                dprintf(unit, 2, "OpenDevice\n");
            }
        }
    }

    db->b_Lib.lib_OpenCnt--;    /* end of fake opener */

    return(iob->IOSer.io_Error);
}


/*
 *  close device (in a Forbid)
 *
 *      iob:    A1
 *
 *  returns the Segment pointer if the device is to be unloaded, NULL
 *  otherwise -- we always return NULL, because the task takes care of
 *  its own cleanup, and we can't unload until then
 */
APTR
DevClose(__A1 struct IOExtSer *iob)
{
struct NullBase *db = DevBase;
struct NullUnit *unit = (struct NullUnit *)iob->IOSer.io_Unit;
struct NullModem *modem = unit->u_Modem;

    /*
     *  make sure we don't take anything with this iob again
     */
    iob->IOSer.io_Unit = (struct Unit *)-1;
    iob->IOSer.io_Device = (struct Device *)-1;

    dprintf(unit,2,"CloseDevice\n");

    unit->u_OpenCnt--;
    modem->nm_OpenCnt--;
    db->b_Lib.lib_OpenCnt--;

    /*
     *  DTR hangup if the unit is empty
     */
    if( unit->u_OpenCnt == 0 ) {
        dprintf(unit,3,"DTR drop signalled\n");
        Signal(modem->nm_Taskptr, SIGBREAKF_CTRL_D);
    }

    /*
     *  and Zap the modem if nobody is left
     */
    if( modem->nm_OpenCnt == 0 ) {
        dprintf(unit,3,"Task Exit signal sent\n");
        Signal(modem->nm_Taskptr, SIGBREAKF_CTRL_E);
        db->b_Modem[modem->nm_Modemnum] = NULL;
    }

    return(NULL);
}


/*
 *  device Expunge (in a Forbid)
 *
 *  no arguments
 *
 *  returns segment passed to Init if we want to expunge, NULL otherwise
 *
 */
APTR
DevExpunge(void)
{
struct NullBase *db = DevBase;
APTR dseg = db->b_Segment;

    /*
     *  but not yet if we are still open!
     */
    if( db->b_Lib.lib_OpenCnt ) {
        return(NULL);
    }

    /*
     *  bye bye
     */
    Remove( &db->b_Lib.lib_Node );
    FreeMem((UBYTE *)db - db->b_Lib.lib_NegSize, db->b_Lib.lib_NegSize + db->b_Lib.lib_PosSize);
    return(dseg);
}

/*
 * device Reserved  no arguments, return NULL
 *                  (reserved for future use by C=)
 *
 */
ULONG
DevReserved(void)
{
    return(NULL);
}


/*
 *  device BeginIO
 *
 *  iob:    a1
 *
 */

void
DevBeginIO(__A1 struct IOExtSer *iob)
{
struct NullUnit *unit = (struct NullUnit *)iob->IOSer.io_Unit;
static struct IOExtSer *(*cmd_table[])(struct IOExtSer *) = {
    cmd_invalid,
    cmd_reset,
    cmd_read,
    cmd_write,
    cmd_update,
    cmd_clear,
    cmd_stop,
    cmd_start,
    cmd_flush,
    sdcmd_query,
    sdcmd_break,
    sdcmd_setparams,
    cmd_invalid,
    cmd_invalid,
    cmd_invalid,
    cmd_invalid,
    asdg_dtrcontrol,
};

    dprintf(unit, 3, "BeginIO (Cmd #%ld)\n",iob->IOSer.io_Command);

    iob->IOSer.io_Message.mn_Node.ln_Type = NT_MESSAGE;

    if( iob->IOSer.io_Command >= arraysize(cmd_table) ) {
        iob->IOSer.io_Error = IOERR_NOCMD;
    }
    else {
        iob->IOSer.io_Error = 0;
        iob->IOSer.io_Actual = 0;

        iob = (*cmd_table[iob->IOSer.io_Command])(iob); /* do the command */
    }

    if( iob ) /*  read & write return NULL if the request was queued */
        if( (iob->IOSer.io_Flags & IOF_QUICK) == 0 )
            ReplyMsg(&iob->IOSer.io_Message);
}


/*
 * device AbortIO
 *
 *   iob:    a1
 *
 */
ULONG
DevAbortIO(__A1 struct IOExtSer *iob)
{
struct NullUnit *unit = (struct NullUnit *)iob->IOSer.io_Unit;

    ObtainSemaphore(&unit->u_Modem->nm_Semaphore);

    /* we only abort if the message hasn't been done already.. */
    /* it could have completed while we were waiting for the Semaphore */

    if( iob->IOSer.io_Message.mn_Node.ln_Type != NT_REPLYMSG && (iob->IOSer.io_Flags & IOF_QUICK) == 0 ) {
        Remove(&iob->IOSer.io_Message.mn_Node);
        iob->IOSer.io_Error = IOERR_ABORTED;
        ReplyMsg(&iob->IOSer.io_Message);
        dprintf(unit, 1,"AbortIO (aborted)\n");
    }
    else {
        dprintf(unit, 1,"AbortIO (already complete)\n");
    }

    ReleaseSemaphore(&unit->u_Modem->nm_Semaphore);

    return(iob->IOSer.io_Error);
}
