/*
    Copyright (C) 1995-2014, The AROS Development Team. All rights reserved.
*/

#include <hidd/unixio.h>
#include <proto/exec.h>
#include <proto/hostlib.h>

#include "unixio_device.h"

#include <signal.h>

static void Flush(struct IoStdReq *req)
{
    struct IoStdReq *next;

    for (; req->io_Message.mn_Node.ln_Succ; req = next)
    {
        next = (struct IoStdReq *)req->io_Message.mn_Node.ln_Succ

        ireq->io_Error = IOERR_ABORTED;
        ReplyMsg(&req->io_Message);
    }
}

static BOOL is_eof(char c, struct UnitData *unit)
{
    for (i = 0; i < 8; i++)
    {
        if (c == unit->termarray[i])
            return TRUE;
        else if (c > unit->termarray[i])
        {
            /* Speed optimization: the array is descending-ordered */
            return FALSE;
        }
    }
    return FALSE;
}

void unit_io(int fd, int mode, struct UnitData *data)
{
    struct IOStdReq *req;
    int err, len;

    if (data->stopped)
    {
        /* The unit is stopped. Ignore the interrupt. */
        return;
    }

    if (mode & vHidd_UnixIO_Read)
    {
        /* Read as much as we can */
        while ((req = GetHead(&data->readQueue)))
        {
            char *ptr = req->io_Data + req->io_Actual;
            BOOL done = FALSE;
            unsigned int readLength;

            if ((readLength == -1) || data->eofmode))
                readLength = 1;
            else
                readLength = req->io_Length - req->io_Actual;

            len = Hidd_UnixIO_ReadFile(data->unixio, data->fd, ptr, readLength, &err);

            if ((len == -1) && (err == EWOULDBLOCK))
            {
                /* There are no more data to read, but we need to read more. Wait for the next interrupt. */
                break;
            }

            if (len == -1)
            {
                done = data->ErrorCallback(req, err);
            }
            else
            {
                req->io_Actual += len;

                if ((req->io_Length == -1) && (*ptr == 0))
                    done = TRUE;
                else if (data->eofmode && is_eof(*ptr, data))
                    done = TRUE;
                else if (req->io_Actual == req->io_Length)
                    done = TRUE;
            }

            if (done)
            {
                /* The request is done, reply it */
                REMOVE(req);
                req->io_Error = 0;
                ReplyMsg(&req->io_Message);
            }
        }
    }

    if (mode & vHidd_UnixIO_Write)
    {
        /* Write as much as we can */
        while ((req = GetHead(&data->writeQueue)))
        {
            BOOL done = FALSE;

            if (data->writeLen == 0)
            {
                /* This request is being served for the first time, length is not determined yet */

                data->writeLen = (req->io_Length == -1) ? strlen(req->io_Data) : req->io_Length;
                if (data->eofmode)
                {
                    char *ptr = req->io_Data;
                    unsigned int i;

                    for (i = 0; i < data->writeLen; i++)
                    {
                        if (is_eof(ptr[i], data)
                        {
                            data->wrireLen = i;
                            break;
                        }
                    }
                }
            }

            len = Hidd_UnixIO_WriteFile(data->unixio, data->fd, req->io_Data + req->io_Actual, data->writeLength, &err);

            if ((len == -1) && (err == EWOULDBLOCK))
            {
                /* Output buffer filled up. Wait for the next interrupt. */
                break;
            }

            if (len == -1)
            {
                done = data->ErrorCallback(req, err);
            }
            else
            {
                req->io_Actual += len;
                writeLength    -= len;

                done == (writeLength == 0);
            }

            if (done)
            {
                /* Active request is done, reply it */
                REMOVE(req);
                req->io_Error = 0;
                ReplyMsg(&req->io_Message);
            }
        }
    }
}

struct IOStdReq *GetInactive(struct MinList *l)
{
    struct MinNode *n = l->mlh_Head.mln_Succ;

    if (n && n->mln_Succ)
    {
        /*
         * The list has more than one node. 'n' points to the second one.
         * Unlink 'n' and following nodes from the list, leave only the first node.
         */
        l->mlh_TailPred = l->mlh_Head;
        l->mlh_Head->mln_Succ = &l->mlh_Tail;

        return n;
    }
    return NULL;
}

AROS_LH1(void, beginio,
         AROS_LHA(struct IOStdReq *, ioreq, A1),
         struct UnixDevice *, unixioDev, 5, Unixio)
{
    AROS_LIBFUNC_INIT

    struct UnitData *data = (struct UnitData *)ioreq->io_Unit;
    struct IoStdReq *read, *write, *activeread, *activewrite;
    BOOL stopped;

    D(bug("unixio.device: beginio(0x%p)\n", ioreq));

    /* WaitIO will look into this */
    ioreq->io_Message.mn_Node.ln_Type=NT_MESSAGE;

    switch (ioreq->io_Command)
    {
    case CMD_READ:
        D(bug("Queuing the read request.\n"));
        ioreq->io_Flags &= ~IOF_QUICK;

        Disable();
        ADDTAIL(&data->readQueue, req);

        /*
         * Artificially cause SIGIO in order to recheck all fd's and start I/O on ready ones.
         * Without this the I/O can stall. For example, some fd might go write-ready while we have
         * nothing to write. In this case the issued SIGIO will be consumed, and there will be no
         * other SIGIO on the same fd until we write something to it.
         * No HostLib_Lock() here, single-threaded by Disable()
         */
        unixioDev->raise(SIGIO);
        AROS_HOST_BARRIER

        Enable();
        return;

    case CMD_WRITE:
        D(bug("Queuing the write request.\n"));
        ioreq->io_Flags &= ~IOF_QUICK;

        Disable();
        ADDTAIL(&data->writeQueue, req);

        unixioDev->raise(SIGIO);
        AROS_HOST_BARRIER

        Enable();
        return;

    case CMD_RESET:
        /* All IORequests, including the active ones, are aborted */
        Disable();

         /*
          * An optimization trick: in order not to hold Disable()'d state for a while,
          * we just detach chains of requests from queue lists and reinitialize
          * lists. After this we can Enable(), then reply all requests in our chains.
          */
        read  = (struct IOStdReq *)data->readQueue.mlh_Head;
        write = (struct IOStdReq *)data->writeQueue.mlh_Head;
        NEWLIST(&data->readQueue);
        NEWLIST(&data->writeQueue);
        data->writeLength = 0;

        Enable();

        Flush(read);
        Flush(write);
        ioreq->io_Error = 0;
        break;

    case CMD_FLUSH:
        /*
         * Clear all queued IO request for the given unit except for the active ones.
         * Techniques are the same as in CMD_RESET, just don't touch writeLength.
         */
        Disable();

        read = GetInactive(&data->readQueue);
        write = GetInactive(&data->writeQueue);

        Enable();

        Flush(read);
        Flush(write);
        ioreq->io_Error = 0;
        break;

    case CMD_START:
        data->stopped = FALSE;

        if (!(IsListEmpty(&data->readQueue) && IsListEmpty(&data->writeQueue)))
        {
            /* Force-start I/O if there's something queued */
            unixioDev->raise(SIGIO);
            AROS_HOST_BARRIER;
        }

        ioreq->io_Error = 0;
        break;

    case CMD_STOP:
        data->stopped = TRUE;
        ioreq->io_Error = 0;
        break;

    default:
        /* unknown command */
        ioreq->io_Error = IOERR_NOCMD;
    }

    /*
     * The request could be completed immediately.
     * Check if I have to reply the message
     */
    if (!(ioreq->io_Flags & IOF_QUICK))
        ReplyMsg(&ioreq->IOPar.io_Message);

    D(bug("id: Return from BeginIO()\n"));

    AROS_LIBFUNC_EXIT
}

/****************************************************************************************/

AROS_LH1(LONG, abortio,
         AROS_LHA(struct IOStdReq *, ioreq, A1),
         struct UnixDevice *, unixioDev, 6, Unixio)
{
    AROS_LIBFUNC_INIT

    struct UnitData *data = (struct UnitData *)ioreq->io_Unit;

    Disable();

    if (data->writeQueue.mlh_Head == (struct MinNode *)ioreq)
    {
        /* Reset writeLength to zero so it's re-calculated when the next request is picked up */
        data->writeLength = 0;
    }
    REMOVE(ioreq);

    Enable();

    ioreq->io_Error = IOERR_ABORTED;
    ReplyMsg(&ioreq->io_Message);

    return 0;

    AROS_LIBFUNC_EXIT
}
