/* ======================================================================== */
/*  EMU-LINK FILE I/O                                                       */
/*                                                                          */
/*  This module implements the Emu-Link File I/O API.  The API provides     */
/*  the following Emu-Link hooks:                                           */
/*                                                                          */
/*      10  OPEN   ("filename", flags)  => fd in R2                         */
/*      11  CLOSE  (fd)                                                     */
/*      12  READ   (fd, buf, bytes)     => bytes read in R1                 */
/*      13  READ16 (fd, buf, words)     => words read in R1                 */
/*      14  WRITE  (fd, buf, bytes)     => bytes written in R1              */
/*      15  WRITE16(fd, buf, words)     => words written in R1              */
/*      16  LSEEK  (fd, offset, whence) => byte offset in file in R2:R1     */
/*      17  UNLINK ("filename")                                             */
/*      18  RENAME ("old", "new")                                           */
/*                                                                          */
/*                                                                          */
/*  These provide essentially the same API as C does.                       */
/*                                                                          */
/*  The File I/O API tries to prevent the program from reading and          */
/*  writing arbitary files in the system.  When initialized, the user       */
/*  passes a directory prefix, and the File I/O API tries to ensure all     */
/*  files get written under that directory.                                 */
/*                                                                          */
/*  See the "ELFI_xxxx" functions below for the detailed API.               */
/*                                                                          */
/*  TODO:  ELFI doesn't really support a robust errno.  For now, just -1    */
/*  gets returned to the Intellivision.                                     */
/*                                                                          */
/* ======================================================================== */


#include "config.h"
#include "periph/periph.h"
#include "cp1600/cp1600.h"
#include "cp1600/emu_link.h"
#include "elfi.h"
#include <ctype.h>

LOCAL int   fd_map[MAX_ELFI_FD];     /* map Intellivision fd to system fd */
LOCAL char *elfi_fname = NULL;
LOCAL char *elfi_fname_end;

LOCAL int elfi_open   (cp1600_t *, int *);
LOCAL int elfi_close  (cp1600_t *, int *);
LOCAL int elfi_read   (cp1600_t *, int *);
LOCAL int elfi_read16 (cp1600_t *, int *);
LOCAL int elfi_write  (cp1600_t *, int *);
LOCAL int elfi_write16(cp1600_t *, int *);
LOCAL int elfi_lseek  (cp1600_t *, int *);
LOCAL int elfi_unlink (cp1600_t *, int *);
LOCAL int elfi_rename (cp1600_t *, int *);

/* ======================================================================== */
/*  ELFI_INIT   Register all the Emu-Link APIs.                             */
/* ======================================================================== */
int elfi_init(const char *elfi_prefix)
{
    int i;
    int prefix_len = strlen(elfi_prefix);

    elfi_fname     = (char *)malloc(strlen(elfi_prefix) + MAX_ELFI_FNAME + 2);
    elfi_fname_end = elfi_fname + prefix_len;

    memcpy(elfi_fname, elfi_prefix, prefix_len);
    *elfi_fname_end++ = PATH_SEP;

    if (!elfi_fname)
    {
        fprintf(stderr, "elfi:  Out of memory\n");
        return -1;
    }

    for (i = 0; i < MAX_ELFI_FD; i++)
        fd_map[i] = 0;

    if (emu_link_register(elfi_open,    10) == 0 &&
        emu_link_register(elfi_close,   11) == 0 &&
        emu_link_register(elfi_read,    12) == 0 &&
        emu_link_register(elfi_read16,  13) == 0 &&
        emu_link_register(elfi_write,   14) == 0 &&
        emu_link_register(elfi_write16, 15) == 0 &&
        emu_link_register(elfi_lseek,   16) == 0 &&
        emu_link_register(elfi_unlink,  17) == 0 &&
        emu_link_register(elfi_rename,  18) == 0)
    {
        jzp_printf("elfi:  Emu-Link File I/O enabled and directed to %s\n",
                    elfi_prefix);
        return 0;
    }

    return -1;
}

/* ======================================================================== */
/*  ELFI_DTOR   Close any open files and reset the fd_map table.            */
/* ======================================================================== */
void elfi_dtor(void)
{
    int i;

    for (i = 0; i < MAX_ELFI_FD; i++)
    {
        if (fd_map[i] > 0)
            close(fd_map[i] - 1);

        fd_map[i] = 0;
    }

    CONDFREE(elfi_fname);
}

/* ======================================================================== */
/*  GET_FNAME   Convert the filename to a string and make sure it's sane.   */
/* ======================================================================== */
LOCAL int get_fname(cp1600_t *cp, uint_32 addr)
{
    int i;
    uint_16 ch = 0;

    /* -------------------------------------------------------------------- */
    /*  Disallow everything but alphanumerics, '-', '_' and '.'.            */
    /* -------------------------------------------------------------------- */
    for (i = 0; i < MAX_ELFI_FNAME; i++)
    {
        if (addr + i > 0xFFFF)  /* don't wrap end-of-memory */
            return -1;

        elfi_fname_end[i] = ch = CP1600_RD(cp, addr + i);

        if (ch == 0)            /* break on NUL */
            break;

        if (ch > 0x7F)          /* Don't trust isalnum for > 0x7F */
            return -1;

        if (!isalnum(ch)  &&
            ch != '-'     &&
            ch != '_'     &&
            (i == 0 || ch != '.'))  /* no leading '.' */
            return -1;
    }

    /* -------------------------------------------------------------------- */
    /*  Make sure it was NUL terminated.                                    */
    /* -------------------------------------------------------------------- */
    if (ch)
        return -1;

    return 0;
}

#define FAIL                do { *fail = 1; return -1; } while (0)
#define SUCCESS             do { *fail = 0; return  0; } while (0)
#define IFD_TO_FD           do {                                        \
                                int ifd = cp->r[2];                     \
                                if (ifd >= MAX_ELFI_FD) FAIL;           \
                                if ((fd = fd_map[ifd] - 1) < 0) FAIL;   \
                            } while (0);


/* ======================================================================== */
/*  ELFI_OPEN       Open a new file on behalf of the Intellivision.         */
/*                                                                          */
/*  INPUTS:                                                                 */
/*      R1  Constant:  10                                                   */
/*      R2  Pointer to ASCIIZ filename.                                     */
/*      R3  Flags.  Flags must come from the following set, ORed together:  */
/*                                                                          */
/*                  O_RDONLY   (1 << 0)                                     */
/*                  O_WRONLY   (2 << 0)                                     */
/*                  O_RDWR     (3 << 0)                                     */
/*                  O_APPEND   (1 << 2)                                     */
/*                  O_CREAT    (1 << 3)                                     */
/*                  O_EXCL     (1 << 4)                                     */
/*                  O_TRUNC    (1 << 5)                                     */
/*                                                                          */
/*  OUTPUTS:                                                                */
/*      C   Clear on success, set on failure                                */
/*      R0  errno on failure                                                */
/*      R2  File descriptor on success                                      */
/*                                                                          */
/* ======================================================================== */
LOCAL int elfi_open   (cp1600_t *cp, int *fail)
{
    int flags, iflags;
    int fd, ifd;

    /* -------------------------------------------------------------------- */
    /*  Allocate an Intellivision file descriptor slot.                     */
    /* -------------------------------------------------------------------- */
    for (ifd = 0; ifd < MAX_ELFI_FD; ifd++)
        if (!fd_map[ifd])
            break;

    if (ifd == MAX_ELFI_FD) FAIL;

    /* -------------------------------------------------------------------- */
    /*  Get the filename from the target.                                   */
    /* -------------------------------------------------------------------- */
    if (get_fname(cp, cp->r[2])) FAIL;

    /* -------------------------------------------------------------------- */
    /*  Remap the target's flags to the host OS.                            */
    /* -------------------------------------------------------------------- */
    iflags = cp->r[3];
    if (iflags & (-(1u << 6))) FAIL;
    if ((iflags & ELFI_O_RDWR) == 0) FAIL;

    flags = ((iflags & ELFI_O_RDWR)    == ELFI_O_RDWR   ? O_RDWR   : 0)
          | ((iflags & ELFI_O_RDWR)    == ELFI_O_RDONLY ? O_RDONLY : 0)
          | ((iflags & ELFI_O_RDWR)    == ELFI_O_WRONLY ? O_WRONLY : 0)
          | ((iflags & ELFI_O_APPEND)  == ELFI_O_APPEND ? O_APPEND : 0)
          | ((iflags & ELFI_O_CREAT)   == ELFI_O_CREAT  ? O_CREAT  : 0)
          | ((iflags & ELFI_O_EXCL)    == ELFI_O_EXCL   ? O_EXCL   : 0)
          | ((iflags & ELFI_O_TRUNC)   == ELFI_O_TRUNC  ? O_TRUNC  : 0);

#ifdef O_BINARY
    flags |= O_BINARY;
#endif

    /* -------------------------------------------------------------------- */
    /*  Ok, let's try to open this file, shall we?                          */
    /* -------------------------------------------------------------------- */
    fd = open(elfi_fname, flags, 0666);     /* Let user modify via umask    */

    if (fd < 0) FAIL;

    fd_map[ifd] = fd + 1;

    /* -------------------------------------------------------------------- */
    /*  Success:  Return the Intellivision file descriptor.                 */
    /* -------------------------------------------------------------------- */
    cp->r[2] = ifd;

    SUCCESS;
}

/* ======================================================================== */
/*  ELFI_CLOSE      Close the requested file.                               */
/*                                                                          */
/*  INPUTS:                                                                 */
/*      R1  Constant:  11                                                   */
/*      R2  File descriptor                                                 */
/*                                                                          */
/*  OUTPUTS:                                                                */
/*      C   Clear on success, set on failure                                */
/*      R0  errno on failure                                                */
/* ======================================================================== */
LOCAL int elfi_close  (cp1600_t *cp, int *fail)
{
    int fd;

    IFD_TO_FD;

    fd_map[cp->r[2]] = 0;  /* deallocate FD */

    if (close(fd) != 0) FAIL;

    SUCCESS;
}


/* ======================================================================== */
/*  ELFI_READ       Read from the file into a buffer as bytes.              */
/*  ELFI_READ16     Read from the file into a buffer as big endian words.   */
/*                                                                          */
/*  The READ API reads in bytes, populating the lower 8 bits of the         */
/*  specified locations.  The READ16 API reads in words in big-endian,      */
/*  populating all 16 bits of the specified locations, if said locations    */
/*  are in 16-bit RAM.                                                      */
/*                                                                          */
/*  The buffer gets written to memory in the same manner as if the CPU      */
/*  wrote it, with all the same restrictions therein (ie. you can't         */
/*  overwrite ROM or violate STIC access windows).  The writes take zero    */
/*  time, however.                                                          */
/*                                                                          */
/*  INPUTS:                                                                 */
/*      R1  Constant:  12 (READ) or 13 (READ16)                             */
/*      R2  File descriptor                                                 */
/*      R3  Pointer to buffer                                               */
/*      R4  Number of bytes (READ) or words (READ16) to read.               */
/*                                                                          */
/*  OUTPUTS:                                                                */
/*      C   Clear on success, set on failure                                */
/*      R0  errno on failure                                                */
/*      R1  Number of bytes/words read                                      */
/*      @R3 Data read                                                       */
/*                                                                          */
/* ======================================================================== */
LOCAL int elfi_read   (cp1600_t *cp, int *fail)
{
    int fd, bytes, addr, total = 0, r;
    uint_8 b;

    /* -------------------------------------------------------------------- */
    /*  Get arguments.                                                      */
    /* -------------------------------------------------------------------- */
    IFD_TO_FD;
    addr  = cp->r[3];
    bytes = cp->r[4];

    /* -------------------------------------------------------------------- */
    /*  For now, do this slow and stupid, because it's also easy and not    */
    /*  performance critical in the least.                                  */
    /* -------------------------------------------------------------------- */
    while (bytes--)
    {
        cp->r[1] = total;
        if ((r = read(fd, &b, 1)) == 1)
            CP1600_WR(cp, (addr + total++) & 0xFFFF, b & 0xFF);
        else if (r == 0) break;
        else             { cp->r[1] = total; FAIL; }
    }

    cp->r[1] = total;
    SUCCESS;
}

LOCAL int elfi_read16 (cp1600_t *cp, int *fail)
{
    int fd, words, addr, total = 0, r0, r1;
    uint_8 b0, b1;
    uint_16 wo;

    /* -------------------------------------------------------------------- */
    /*  Get arguments.                                                      */
    /* -------------------------------------------------------------------- */
    IFD_TO_FD;
    addr  = cp->r[3];
    words = cp->r[4];

    /* -------------------------------------------------------------------- */
    /*  For now, do this slow and stupid, because it's also easy and not    */
    /*  performance critical in the least.                                  */
    /* -------------------------------------------------------------------- */
    while (words--)
    {
        if ((r0 = read(fd, &b0, 1)) == 1 &&
            (r1 = read(fd, &b1, 1)) == 1)
        {
            wo = ((uint_16)b0 << 8) | b1;
            CP1600_WR(cp, (addr + total++) & 0xFFFF, wo);
        }
        else if (r0 == 0) break;
        else              { cp->r[1] = total; FAIL; }
    }

    cp->r[1] = total;
    SUCCESS;
}


/* ======================================================================== */
/*  ELFI_WRITE, ELFI_WRITE16                                                */
/*                                                                          */
/*  The WRITE API writes out bytes, taking them from the lower 8 bits of    */
/*  the specified locations.  The WRITE16 API writes out words in big-      */
/*  endian, writing out the full 16-bit value read from the specified       */
/*  locations.                                                              */
/*                                                                          */
/*  The buffer gets read from memory in the same manner as if the CPU       */
/*  read it, with all the same restrictions therein (ie. you can't          */
/*  violate STIC access windows).  The reads take zero time, however.       */
/*                                                                          */
/*  INPUTS:                                                                 */
/*      R1  Constant:  14 (WRITE) or 15 (WRITE16)                           */
/*      R2  File descriptor                                                 */
/*      R3  Pointer to buffer                                               */
/*      R4  Number of bytes (WRITE) or words (WRITE16) to write             */
/*                                                                          */
/*  OUTPUTS                                                                 */
/*      C   Clear on success, set on failure                                */
/*      R0  errno on failure                                                */
/*      R1  Number of bytes/words written                                   */
/*                                                                          */
/* ======================================================================== */
LOCAL int elfi_write  (cp1600_t *cp, int *fail)
{
    int fd, bytes, addr, total = 0, w;
    uint_8 b;

    /* -------------------------------------------------------------------- */
    /*  Get arguments.                                                      */
    /* -------------------------------------------------------------------- */
    IFD_TO_FD;
    addr  = cp->r[3];
    bytes = cp->r[4];

    if (addr + bytes > 0xFFFF) { cp->r[1] = 0; FAIL; }

    /* -------------------------------------------------------------------- */
    /*  For now, do this slow and stupid, because it's also easy and not    */
    /*  performance critical in the least.                                  */
    /* -------------------------------------------------------------------- */
    while (bytes--)
    {
        b = CP1600_RD(cp, (addr + total++)) & 0xFF;
        w = write(fd, &b, 1);
        cp->r[1] = total;

        if (w == 1) continue;
        if (w == 0) break;
        FAIL;
    }

    SUCCESS;
}

LOCAL int elfi_write16(cp1600_t *cp, int *fail)
{
    int fd, words, addr, total = 0, w;
    uint_8  b[2];
    uint_16 wo;

    /* -------------------------------------------------------------------- */
    /*  Get arguments.                                                      */
    /* -------------------------------------------------------------------- */
    IFD_TO_FD;
    addr  = cp->r[3];
    words = cp->r[4];

    if (addr + words > 0xFFFF) { cp->r[1] = 0; FAIL; }

    /* -------------------------------------------------------------------- */
    /*  For now, do this slow and stupid, because it's also easy and not    */
    /*  performance critical in the least.                                  */
    /* -------------------------------------------------------------------- */
    while (words--)
    {
        wo   = CP1600_RD(cp, (addr + total++));
        b[0] = (wo >> 8) & 0xFF;
        b[1] = (wo >> 0) & 0xFF;
        w    = write(fd, b, 2);
        cp->r[1] = total;

        if (w == 2) continue;
        if (w == 0) break;
        FAIL;
    }

    SUCCESS;
}

/* ======================================================================== */
/*  ELFI_LSEEK                                                              */
/*                                                                          */
/*  Changes the offset within the file, and returns the new offset.         */
/*  Offsets are 32 bits, signed.                                            */
/*                                                                          */
/*  INPUTS:                                                                 */
/*      R1  Constant:  16 (LSEEK)                                           */
/*      R2  File descriptor                                                 */
/*      R3  Lower 16 bits of signed offset                                  */
/*      R4  Upper 16 bits of signed offset                                  */
/*      R5  "Whence":  0 == SEEK_SET, 1 == SEEK_CUR, 2 == SEEK_END          */
/*                                                                          */
/*  OUTPUTS:                                                                */
/*      C   Clear on success, set on failure                                */
/*      R0  errno on failure                                                */
/*      R1  Lower 16 bits of new file offset                                */
/*      R2  Upper 16 bits of new file offset                                */
/*                                                                          */
/* ======================================================================== */
LOCAL int elfi_lseek  (cp1600_t *cp, int *fail)
{
    int fd, ofs, whence, fpos;

    /* -------------------------------------------------------------------- */
    /*  Get arguments.                                                      */
    /* -------------------------------------------------------------------- */
    IFD_TO_FD;
    ofs = cp->r[3] | ((int)cp->r[4] << 16);

    switch (cp->r[5])
    {
        case ELFI_SEEK_SET: whence = SEEK_SET; break;
        case ELFI_SEEK_CUR: whence = SEEK_CUR; break;
        case ELFI_SEEK_END: whence = SEEK_END; break;
        default:            FAIL;
    }

    /* -------------------------------------------------------------------- */
    /*  Seek it.                                                            */
    /* -------------------------------------------------------------------- */
    fpos = lseek(fd, ofs, whence);

    if (fpos == -1) FAIL;

    cp->r[1] = (fpos >>  0) & 0xFFFF;
    cp->r[2] = (fpos >> 16) & 0xFFFF;

    SUCCESS;
}

/* ======================================================================== */
/*  ELFI_UNLINK                                                             */
/*                                                                          */
/*  Tries to unlink (ie. remove) a file from the file system.  No check is  */
/*  made to determine if the file is currently open.  Behavior is defined   */
/*  by jzIntv's host OS.                                                    */
/*                                                                          */
/*  INPUTS:                                                                 */
/*      R1  Constant:  17 (UNLINK)                                          */
/*      R2  Pointer to ASCIIZ file name                                     */
/*                                                                          */
/*  OUTPUTS:                                                                */
/*      C   Clear on success, set on failure                                */
/*      R0  errno on failure                                                */
/* ======================================================================== */
LOCAL int elfi_unlink (cp1600_t *cp, int *fail)
{
    if (get_fname(cp, cp->r[2])) FAIL;
    if (unlink(elfi_fname) != 0) FAIL;

    SUCCESS;
}

/* ======================================================================== */
/*  ELFI_RENAME                                                             */
/*                                                                          */
/*  Tries to rename a file in the file system.  No check is made to         */
/*  determine if the file is currently open.  Behavior is defined by        */
/*  jzIntv's host OS.                                                       */
/*                                                                          */
/*  INPUTS:                                                                 */
/*      R1  Constant:  18 (RENAME)                                          */
/*      R2  Pointer to ASCIIZ file name for old name                        */
/*      R3  Pointer to ASCIIZ file name for new name                        */
/*                                                                          */
/*  OUTPUTS:                                                                */
/*      C   Clear on success, set on failure                                */
/*      R0  errno on failure                                                */
/* ======================================================================== */
LOCAL int elfi_rename (cp1600_t *cp, int *fail)
{
    char *old_fname;

    if (get_fname(cp, cp->r[2]))            FAIL;
    if (!(old_fname = strdup(elfi_fname)))  FAIL;
    if (get_fname(cp, cp->r[3]))            { free(old_fname); FAIL; }
    if (rename(old_fname, elfi_fname) != 0) { free(old_fname); FAIL; }

    free(old_fname);
    SUCCESS;
}

/* ======================================================================== */
/*  This program is free software; you can redistribute it and/or modify    */
/*  it under the terms of the GNU General Public License as published by    */
/*  the Free Software Foundation; either version 2 of the License, or       */
/*  (at your option) any later version.                                     */
/*                                                                          */
/*  This program is distributed in the hope that it will be useful,         */
/*  but WITHOUT ANY WARRANTY; without even the implied warranty of          */
/*  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU       */
/*  General Public License for more details.                                */
/*                                                                          */
/*  You should have received a copy of the GNU General Public License       */
/*  along with this program; if not, write to the Free Software             */
/*  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.               */
/* ======================================================================== */
/*                 Copyright (c) 2011-+Inf, Joseph Zbiciak                  */
/* ======================================================================== */
