/* i7090_chron.c: IBM 7090 Chrono clock on MT drive.

   Copyright (c) 2005-2016, Richard Cornwell

   Permission is hereby granted, free of charge, to any person obtaining a
   copy of this software and associated documentation files (the "Software"),
   to deal in the Software without restriction, including without limitation
   the rights to use, copy, modify, merge, publish, distribute, sublicense,
   and/or sell copies of the Software, and to permit persons to whom the
   Software is furnished to do so, subject to the following conditions:

   The above copyright notice and this permission notice shall be included in
   all copies or substantial portions of the Software.

   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
   IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
   FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
   RICHARD CORNWELL BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
   IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
   CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.


*/

#include "i7000_defs.h"
#include <time.h>

#ifdef NUM_DEVS_CHRON
#define BUFFSIZE        (12)

#define UNIT_MT(x)      UNIT_DISABLE | UNIT_ROABLE | \
                        UNIT_S_CHAN(x)

/* in u3 is device address */
/* in u4 is current buffer position */
/* in u5 */
#define MT_RDS          1
#define MT_RDSB         2
#define MT_SKIP         11      /* Do skip to end of record */
#define MT_CMDMSK   000017      /* Command being run */
#define MT_RDY      000020      /* Device is ready for command */
#define MT_IDLE     000040      /* Tape still in motion */
#define MT_EOR      000200      /* Hit end of record */
#define MT_ERR      000400      /* Device recieved error */
#define MT_BOT      001000      /* Unit at begining of tape */
#define MT_EOT      002000      /* Unit at end of tape */

uint32              chron_cmd(UNIT *, uint16, uint16);
t_stat              chron_srv(UNIT *);
t_stat              chron_reset(DEVICE *);
t_stat              set_addr(UNIT * uptr, int32 val, CONST char *cptr, void *desc);
t_stat              get_addr(FILE * st, UNIT *uptr, int32 v, CONST void *desc);
t_stat              chron_help(FILE *st, DEVICE *dptr, UNIT *uptr,
                             int32 flags, const char *ctxt);
const char          *chron_description (DEVICE *dptr);

/* One buffer per channel */
uint8               chron_buffer[BUFFSIZE];

UNIT                chron_unit[] = {
/* Controller 1 */
    {UDATA(&chron_srv, UNIT_MT(1) | UNIT_DIS, 0), 10},  /* 0 */
};

MTAB                chron_mod[] = {
    {MTAB_XTD | MTAB_VUN | MTAB_VALR, 0, "UNIT", "UNIT", &set_addr, &get_addr,
     NULL, "Chronoclock unit number"},
    {MTAB_XTD | MTAB_VUN | MTAB_VALR, 0, "CHAN", "CHAN", &set_chan, &get_chan,
     NULL, "Chronoclock channel"},
    {0}
};

DEVICE              chron_dev = {
    "CHRON", chron_unit, NULL, chron_mod,
    NUM_DEVS_CHRON, 8, 15, 1, 8, 8,
    NULL, NULL, &chron_reset, NULL, NULL, NULL,
    &chron_dib, DEV_DISABLE, 0, NULL,
    NULL, NULL, &chron_help, NULL, NULL, &chron_description
};

uint32 chron_cmd(UNIT * uptr, uint16 cmd, uint16 dev)
{
    int                 chan = UNIT_G_CHAN(uptr->flags);
    int                 time = 30;
    int                 unit = (dev & 017);

    /* Make sure valid drive number */
    if (unit != uptr->u3)
        return SCPE_NODEV;
    if (uptr->flags & UNIT_DIS)
        return SCPE_NODEV;

    /* Check if drive is ready to recieve a command */
    if ((uptr->u5 & MT_RDY) == 0) {
        /* Return indication if not ready and doing TRS */
        if (cmd == IO_TRS) {
            return SCPE_IOERR;
        } else
            return SCPE_BUSY;
    }
    uptr->u5 &= ~(MT_CMDMSK | MT_RDY);
    switch (cmd) {
    case IO_RDS:
        if (dev & 020)
            uptr->u5 |= MT_RDSB;
        else
            uptr->u5 |= MT_RDS;
        time = 100;
        chan_set_sel(chan, 0);
        chan_clear_status(chan);
        uptr->u6 = 0;
        break;
    case IO_WRS:
        /* Can't write to it so return error */
        return SCPE_IOERR;

    case IO_BSR: /* Nop, just set us back at begining */
    case IO_BSF: /* Nop, just set flag and leave */
        chan_set(chan, CHS_BOT);
        /* All nops, just return success */
    case IO_WEF:
    case IO_REW:
    case IO_RUN:
    case IO_SDL:
    case IO_SDH:
    case IO_TRS:
        return SCPE_OK;
    }
    sim_cancel(uptr);
    sim_activate(uptr, us_to_ticks(time));
    return SCPE_OK;
}

/* Chronolog clock */

/* Convert number (0-99) to BCD */

static void
bcd_2d(int n, uint8 * b2)
{
    uint8               d1, d2;

    d1 = n / 10;
    d2 = n % 10;
    *b2++ = d1;
    *b2 = d2;
}

void
chron_read_buff(UNIT * uptr, int cmd)
{
    time_t              curtim;
    struct tm          *tptr;
    int                 ms;

    uptr->u6 = 0;               /* Set to no data */

    curtim = time(NULL);        /* get time */
    tptr = localtime(&curtim);  /* decompose */
    if (tptr == NULL)
        return;                 /* error? */

    ms = sim_os_msec() % 1000;
    ms /= 100;

    /* Convert and fill buffer */
    bcd_2d(tptr->tm_mon + 1, &chron_buffer[0]);
    bcd_2d(tptr->tm_mday, &chron_buffer[2]);
    bcd_2d(tptr->tm_hour, &chron_buffer[4]);
    bcd_2d(tptr->tm_min, &chron_buffer[6]);
    bcd_2d(tptr->tm_sec,  &chron_buffer[8]);
    bcd_2d(ms,  &chron_buffer[10]);
    return;
}

t_stat chron_srv(UNIT * uptr)
{
    int                 chan = UNIT_G_CHAN(uptr->flags);
    int                 cmd = uptr->u5 & MT_CMDMSK;

    /* Channel has disconnected, abort current read. */
    if ((uptr->u5 & MT_RDY) == 0 && chan_stat(chan, DEV_DISCO)) {
        uptr->u5 &= ~MT_CMDMSK;
        if (cmd == MT_RDS || cmd == MT_RDSB) {
            uptr->u6 = 0;
        }
        uptr->u5 |= MT_RDY;
        chan_clear(chan, DEV_WEOR|DEV_SEL);
        return SCPE_OK;
    }

    switch (uptr->u5 & MT_CMDMSK) {
    case 0:                     /* No command, stop tape */
        uptr->u5 |= MT_RDY;     /* Ready since command is done */
        break;

    case MT_SKIP:               /* Record skip done, enable tape drive */
        sim_activate(uptr, us_to_ticks(500));
        break;

    case MT_RDS:
    case MT_RDSB:
        if (uptr->u6 == 0)
            chron_read_buff(uptr, cmd);
        switch (chan_write_char(chan, &chron_buffer[uptr->u6],
                           (uptr->u6 == (BUFFSIZE-1)) ? DEV_REOR : 0)) {
        case DATA_OK:
            uptr->u6++;
            sim_activate(uptr, us_to_ticks(100));
            break;

        case END_RECORD:
        case TIME_ERROR:
            uptr->u5 &= ~MT_CMDMSK;
            uptr->u5 |= MT_SKIP;
            sim_activate(uptr, us_to_ticks(100));
            uptr->u6 = 0;       /* Force read next record */
            break;
        }

    }
    return SCPE_OK;
}

t_stat
chron_reset(DEVICE * dptr)
{
    chron_unit[0].u5 = MT_RDY;
    return SCPE_OK;
}

/* Sets the address of the chrono clock */
t_stat
set_addr(UNIT * uptr, int32 val, CONST char *cptr, void *desc)
{
    int                 i;

    if (cptr == NULL)
        return SCPE_ARG;
    if (uptr == NULL)
        return SCPE_IERR;
    if (uptr->flags & UNIT_ATT)
        return SCPE_ALATT;
    i = 0;
    while (*cptr != '\0') {
        if (*cptr < '0' || *cptr > '9')
            return SCPE_ARG;
        i = (i * 10) + (*cptr++) - '0';
    }
    if (i < 0 || i > 10)
        return SCPE_ARG;
    uptr->u3 = i;
    return SCPE_OK;
}

t_stat
get_addr(FILE * st, UNIT * uptr, int32 v, CONST void *desc)
{
    if (uptr == NULL)
        return SCPE_IERR;
    fprintf(st, "Unit=%d", uptr->u3);
    return SCPE_OK;
}

t_stat
chron_help (FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, const char *cptr)
{
  fprintf (st, "Chronoclock\n\n");
  fprintf (st, "The Chronoclock replaces one of your tape drives, and is\n");
  fprintf (st, "for CTSS operation\n\n");
  fprintf (st, "   sim> SET %s ENABLE     to enable chronoclock\n", dptr->name);
  fprintf (st, "   sim> SET %s UNIT=#     sets unit to override [0-9]\n\n", dptr->name);
  help_set_chan_type(st, dptr, "Chrono clock");
  fprintf (st, "You must disable the corrosponding tape drive in order for\n");
  fprintf (st, "the chronoclook to be seen. The chronoclock replaces one of\n");
  fprintf (st, "your tape drives, and by reading the tape drive, it will\n");
  fprintf (st, "return a short record with the current date and time, no year\n");
  fprintf (st, "is returned\n");

  fprint_set_help (st, dptr) ;
  fprint_show_help (st, dptr) ;
  return SCPE_OK;
}

const char *
chron_description (DEVICE *dptr)
{
   return "Chronoclock";
}


#endif
