/* i650_mt.c: IBM 650 Magnetic tape 

   Copyright (c) 2018, Roberto Sancho

   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 ni
   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
   ROBERTO SANCHO 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.

   Magnetic tapes are represented as a series of variable records
   of the form:

        32b byte count
        byte 0
        byte 1
        :
        byte n-2
        byte n-1
        32b byte count

   If the byte count is odd, the record is padded with an extra byte
   of junk.  File marks are represented by a byte count of 0.
*/

#include "i650_defs.h"
#include "sim_tape.h"

#define UNIT_MT         UNIT_ATTABLE | UNIT_ROABLE | UNIT_DISABLE

/* in u3 is tape medium length used on current position */
/* in u4 is tape medium max length (28800 for 2400 ft reel) */
/* in u5 holds the command being executed by tape unit */
#define MT_CMDMSK   0x00FF      /* Command being run */
#define MT_RDY      0x0100      /* Unit is ready for command */
#define MT_IND      0x0200      /* Unit has Indicator light on */

/* u6 holds the current buffer position */


/* Definitions */
uint32              mt_cmd(UNIT *, uint16, uint16);
t_stat              mt_srv(UNIT *);
void                mt_ini(UNIT *, t_bool);
t_stat              mt_reset(DEVICE *);
t_stat              mt_attach(UNIT *, CONST char *);
t_stat              mt_detach(UNIT *);
t_stat              mt_rew(UNIT * uptr, int32 val, CONST char *cptr,void *desc);
t_stat              mt_set_len (UNIT *uptr, int32 val, CONST char *cptr, void *desc);
t_stat              mt_show_len (FILE *st, UNIT *uptr, int32 val, CONST void *desc);
t_stat              mt_help (FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, const char *cptr);
const char          *mt_description (DEVICE *dptr);

UNIT                mt_unit[6] = {
    {UDATA(&mt_srv, UNIT_MT, 0), 0}, /* 0 */
    {UDATA(&mt_srv, UNIT_MT, 0), 0}, /* 1 */
    {UDATA(&mt_srv, UNIT_MT, 0), 0}, /* 2 */
    {UDATA(&mt_srv, UNIT_MT, 0), 0}, /* 3 */
    {UDATA(&mt_srv, UNIT_MT, 0), 0}, /* 4 */
    {UDATA(&mt_srv, UNIT_MT, 0), 0}, /* 5 */
};

MTAB                mt_mod[] = {
    { MTAB_XTD|MTAB_VUN, 0, "write enabled", "WRITEENABLED",  &set_writelock, &show_writelock,   NULL, "Write ring in place" },
    { MTAB_XTD|MTAB_VUN, 1, NULL,            "LOCKED",        &set_writelock, NULL,   NULL, "no Write ring in place" },
    {MTAB_XTD | MTAB_VUN, 0, "FORMAT", "FORMAT",              &sim_tape_set_fmt, &sim_tape_show_fmt, NULL, 
                                                                                "Set/Display tape format (SIMH, E11, TPC, P7B)"},
    {MTAB_XTD | MTAB_VUN, 0, "LENGTH", "LENGTH",              &mt_set_len, &mt_show_len, NULL,
                                                                                "Set tape medium length (50 to 10000 foot)" },
    {MTAB_XTD | MTAB_VUN, 0, NULL,     "REWIND",              &mt_rew, NULL, NULL, "Rewind tape"},
    {0}
};

DEVICE              mt_dev = {
    "MT", mt_unit, NULL, mt_mod,
    6, 8, 15, 1, 8, 8,
    NULL, NULL, &mt_reset, NULL, &mt_attach, &mt_detach,
    &mt_dib, DEV_DISABLE | DEV_DEBUG, 0, dev_debug,
    NULL, NULL, &mt_help, NULL, NULL, &mt_description
};

// IBM 652 Control Unit internal state
int LastTapeSelected  = -1;   // last tape selected. =0 to 5, -1=none yet
int LastTapeIndicator = 0;    // last tape operation has some indication to tell to program/operator
int bFastMode = 0;            // =1 for FAST operation

const char * TapeIndicatorStr[11] = { "OK",
                                      "WRITE PROTECTED", 
                                      "IO CHECK",
                                      "END OF FILE",
                                      "END OF TAPE",
                                      "LONG RECORD",
                                      "SHORT RECORD",
                                      "NO TAPE UNIT AT THIS ADDRESS",
                                      "NO REEL LOADED",
                                      "NOT READY", 
                                      "BAD CHAR"};
                                                             
// return 1 if tape unit n (0..5) is ready to receive a command
int mt_ready(int n)
{
    if ((n < 0) || (n > 5)) return 0;
    if (mt_unit[n].u5 & MT_RDY) return 1;
    return 0;
}

/* Rewind tape drive */
t_stat mt_rew(UNIT * uptr, int32 val, CONST char *cptr, void *desc)
{
    /* If drive is offline or not attached return not ready */
    if ((uptr->flags & UNIT_ATT) == 0)
        return SCPE_NOATT;
    uptr->u3 = 0;      // tape at begin of medium
    uptr->u5 = MT_RDY; // clear indicator flag, clear last command, set ready flag
    return sim_tape_rewind(uptr);
}

int mt_read_numeric_word(uint8 * buf, t_int64 * d, int * ZeroNeg)
{
    int i, neg; 
    char c;

    neg = 0;
    *d = 0;
    if (ZeroNeg != NULL) *ZeroNeg = 0;
    for (i=0;i<10;i++) {
        c = *buf++;
        if (i==9) { // is last word digit 
            if ((c >= '0') && (c <= '9')) return MT_IND_BADCHAR;          // last digit should have sign
            if (c == '?') c = '0';                                        // +0
            if ((c >= 'A') && (c <= 'I')) c = c - 'A' + '1';              // +1 to +9
            if ((c >= 'J') && (c <= 'R')) {c = c - 'J' + '1'; neg=1;}     // -1 to -9
            if (c == '!') {c = '0'; neg=1;}                               // -0
        } 
        if ((c < '0') || (c > '9')) return MT_IND_BADCHAR;
        *d = *d * 10 + (c - '0');
    }
    if (neg) *d = -*d;
    if (ZeroNeg != NULL) *ZeroNeg = ((neg) && (*d == 0)) ? 1:0;
    return 0;
}

int mt_read_alpha_word(uint8 * buf, t_int64 * d)
{
    int i, n; 
    char c;

    *d = 0;
    for (i=0;i<5;i++) {
        c = *buf++;
        n = ascii_to_NN(c);
        if ((n==0) && (c != ' ')) return MT_IND_BADCHAR;
        *d = *d * 100 + n;
    }
    return 0;
}

int mt_transfer_tape_rec_to_IAS(uint8 * buf, t_mtrlnt reclen, char mode)
{
    int n,ic,r, ZeroNeg;
    t_int64 d, CtrlWord;
    char s[6];
    t_mtrlnt expected_reclen; 
    
    if (mode == 'N') {
        // numeric mode
        expected_reclen = (60 - IAS_TimingRing) * 10; // record len expected
        // does expected record len match read record from tape?
        if (expected_reclen != reclen) {
            return (reclen > expected_reclen) ? MT_IND_LONG_REC : MT_IND_SHORT_REC;
        }
        // yes, record length match -> load IAS with tape record data
        ic = 0;
        while (1) {
            // read numeric word from tape
            r = mt_read_numeric_word(&buf[ic], &d, &ZeroNeg);
            if (r) return r;
            ic += 10;
            // store into IAS
            IAS[IAS_TimingRing] = d;
            IAS_NegativeZeroFlag[IAS_TimingRing] = ZeroNeg;
            sim_debug(DEBUG_DETAIL, &cpu_dev, "... Tape to IAS %04d: %06d%04d%c '%s'\n", 
                        IAS_TimingRing+9000, printfw(d,ZeroNeg), 
                        word_to_ascii(s, 1, 5, d));
            // incr IAS_TimingRing, exit if arrived to end of IAS
            IAS_TimingRing = (IAS_TimingRing + 1) % 60;
            if (IAS_TimingRing == 0) break;
        }
        return 0;
    }
    // alphabetic mode
    // check tape record size limits
    if (reclen < 10 + 9*5) return MT_IND_SHORT_REC;
    if (reclen > 10 + 9*10) return MT_IND_LONG_REC;
    ic = 0;
    while(1) {
        // get control word
        if (ic + 10 > (int)reclen) return MT_IND_SHORT_REC;
        r = mt_read_numeric_word(&buf[ic], &CtrlWord, NULL);
        if (r) return r;
        ic += 10;
        // store it in IAS[nnn9]
        n = (IAS_TimingRing / 10) * 10 + 9;
        IAS[n] = CtrlWord;
        IAS_NegativeZeroFlag[n] = 0;
        // load rest of words
        for (n=0;n<9;n++) {
            if ((CtrlWord % 10) != 8) {
                // read a numeric word form tape
                if (ic + 10 > (int)reclen) return MT_IND_SHORT_REC;
                r = mt_read_numeric_word(&buf[ic], &d, &ZeroNeg);
                if (r) return r;
                ic += 10;
            } else {
                // read alphanumeric word from tape
                if (ic + 5 > (int)reclen) return MT_IND_SHORT_REC;
                r = mt_read_alpha_word(&buf[ic], &d); ZeroNeg=0;
                if (r) return r;
                ic += 5;
            }
            CtrlWord = CtrlWord / 10;
            // store into IAS
            IAS[IAS_TimingRing] = d;
            IAS_NegativeZeroFlag[IAS_TimingRing] = ZeroNeg;
            sim_debug(DEBUG_DETAIL, &cpu_dev, "... Tape to IAS %04d: %06d%04d%c '%s'\n", 
                        IAS_TimingRing+9000, printfw(d,ZeroNeg), 
                        word_to_ascii(s, 1, 5, d));
            // incr IAS_TimingRing, exit if arrived to end of IAS
            IAS_TimingRing = (IAS_TimingRing + 1) % 60;
            if (IAS_TimingRing == 0) return MT_IND_LONG_REC;
        }
        IAS_TimingRing = (IAS_TimingRing + 1) % 60; // skip control word
        if ((IAS_TimingRing == 0) && (ic != reclen)) return MT_IND_LONG_REC;
        if (ic == reclen) {
            if (IAS_TimingRing != 0) return MT_IND_SHORT_REC;
            break;
        }
    }
    return 0;
}

void mt_write_numeric_word(uint8 * buf, t_int64 d, int ZeroNeg)
{
    int i, neg; 
    char c;

    neg = 0;
    if (d < 0) {neg=1; d=-d;}
    if (ZeroNeg) neg=1;
    for (i=0;i<10;i++) {
        c = Shift_Digits(&d,1) + '0';
        if (i==9) {
            if (neg==0) { // last digit has sign
                if (c == '0') c = '?';                                        // +0
                if ((c >= '1') && (c <= '9')) c = c - '1' + 'A';              // +1 to +9
            } else {
                if ((c >= '1') && (c <= '9')) {c = c - '1' + 'J';}            // -1 to -9
                if (c == '0') {c = '!';}                                      // -0
            }
        } 
        *buf++ = c;
    }
}

void mt_write_alpha_word(uint8 * buf, t_int64 d)
{
    int i, n; 
    char c;

    for (i=0;i<5;i++) {
        n = Shift_Digits(&d,2);
        c = mem_to_ascii[n];
        *buf++ = c;
    }
}

void mt_transfer_IAS_to_tape_rec(uint8 * buf, t_mtrlnt * reclen, char mode)
{
    int n,ic,ZeroNeg;
    t_int64 d, CtrlWord;
    char s[6];

    if (mode == 'N') {
        // numeric mode
        ic = 0;
        while (1) {
            // read IAS
            d = IAS[IAS_TimingRing];
            ZeroNeg = IAS_NegativeZeroFlag[IAS_TimingRing];
            sim_debug(DEBUG_DETAIL, &cpu_dev, "... IAS %04d to Tape: %06d%04d%c '%s'\n", 
                        IAS_TimingRing+9000, printfw(d,ZeroNeg), 
                        word_to_ascii(s, 1, 5, d));
            // write numeric to tape buf
            mt_write_numeric_word(&buf[ic], d, ZeroNeg);
            ic += 10;
            // incr IAS_TimingRing, exit if arrived to end of IAS
            IAS_TimingRing = (IAS_TimingRing + 1) % 60;
            if (IAS_TimingRing == 0) break;
        }
        *reclen = (t_mtrlnt) ic;
        return;
    }
    // alphabetic mode
    ic = 0;
    while(1) {
        // get control word form IAS[nnn9]
        n = (IAS_TimingRing / 10) * 10 + 9;
        CtrlWord = IAS[n];
        // write control word in tape buf
        mt_write_numeric_word(&buf[ic], CtrlWord, 0);
        ic += 10;
        // write rest of words
        for (n=0;n<9;n++) {
            // read from IAS
            d = IAS[IAS_TimingRing];
            ZeroNeg = IAS_NegativeZeroFlag[IAS_TimingRing];
            if ((CtrlWord % 10) != 8) {
                // write a numeric word to tape buf
                mt_write_numeric_word(&buf[ic], d, ZeroNeg);
                ic += 10;
            } else {
                // write alphanumeric word to tape buf
                mt_write_alpha_word(&buf[ic], d); 
                ic += 5;
            }
            CtrlWord = CtrlWord / 10;
            // incr IAS_TimingRing, exit if arrived to end of IAS
            IAS_TimingRing = (IAS_TimingRing + 1) % 60;
            if (IAS_TimingRing == 0) break;
        }
        if (IAS_TimingRing == 0) break;
        IAS_TimingRing = (IAS_TimingRing + 1) % 60; // skip control word
        if (IAS_TimingRing == 0) break;
    }
    *reclen = (t_mtrlnt) ic;
}

/* Start off a mag tape command */
uint32 mt_cmd(UNIT * uptr, uint16 cmd, uint16 fast)
{
    DEVICE             *dptr = find_dev_from_unit(uptr);
    int                 unit = uptr - &mt_unit[0];
    int                 i, ic, time;
    t_stat              r;   
    uint8               buf[1024];
    char                cbuf[100];
    t_mtrlnt            reclen;

    time = 0;
    /* Make sure valid drive number */
    if ((unit > 5) || (unit < 0)) return STOP_ADDR;
    // init IBM 652 Control Unit internal registers
    LastTapeSelected  = unit;
    LastTapeIndicator = 0;
    bFastMode = fast;
    /* If tape unit disabled return error */
    if (uptr->flags & UNIT_DIS) {
        sim_debug(DEBUG_EXP, dptr, "Tape %d: command %02d attempted on disabled tape\n", unit, cmd);
        LastTapeIndicator = MT_IND_DIS;
        // not stated in manual: what happends if command to non existant tape?
        // option 1 -> cpu halt
        // option 2 -> tape indictor flag set (used this)
        return SCPE_OK;
    }
    /* If tape has no file attached return error */
    if ((uptr->flags & UNIT_ATT) == 0) {
        sim_debug(DEBUG_EXP, dptr, "Tape %d: command %02d attempted on tape without file attached\n", unit, cmd);
        LastTapeIndicator = MT_IND_NOATT;
        uptr->u5 |= MT_IND; // turn on tape indicator light to signal to operator the faulting tape
        return SCPE_OK;
    }
    uptr->u5 &= ~(MT_CMDMSK | MT_RDY | MT_IND); // remove last command sent to tape, remove ready flag, remove tape indicator flag
    uptr->u5 |= cmd; // set current command in execution
    switch (cmd) {
    case OP_RTC:
    case OP_RTA:
    case OP_RTN:
        sim_debug(DEBUG_DATA, dptr, "Tape unit %d: init read\n", unit);
        // actual simulated tape read
        reclen = 0;
        r = sim_tape_rdrecf(uptr, buf, &reclen, sizeof(buf));
        // calc tape pos:
        // each char uses 0,005 inches. at the end of record the IRG (inter gap record) uses 3/4 inchs (0.75)
        // scaled x1000 to use integer values
        uptr->u3 += (int32) ((reclen * 0.005 + 0.75) * 1000);
        // process result conditions
        if (r == MTSE_TMK) {
            sim_debug(DEBUG_EXP, dptr, "Tape unit %d: tape mark sensed\n", unit);
            LastTapeIndicator = MT_IND_EOF;
            uptr->u5 |= MT_IND; 
        } else if ((r == MTSE_EOM) || (uptr->u3 > uptr->u4*1000)) {
            sim_debug(DEBUG_EXP, dptr, "Tape unit %d: end of tape sensed\n", unit);
            LastTapeIndicator = MT_IND_EOT;
            uptr->u5 |= MT_IND; 
        } else if (r == MTSE_RECE) {
            // record header contains error flag
            sim_debug(DEBUG_EXP, dptr, "Tape unit %d: longitudinal or vertical check error\n", unit);
            LastTapeIndicator = MT_IND_IOCHECK;
            uptr->u5 |= MT_IND; 
        } else if (r != MTSE_OK) {
            sim_debug(DEBUG_EXP, dptr, "Tape unit %d: read error %d\n", unit, r);
            return STOP_IO;
        }
        // debug output: display buf as 50 chars per line
        sim_debug(DEBUG_DETAIL, dptr, "Read record (%d chars) from tape:\n", (int) reclen);
        ic = 0;
        while (1) {
            for (i=0;i<50;i++) {
                cbuf[i] = 0;
                if (ic == reclen) break;
                cbuf[i] = buf[ic++];
            }
            sim_debug(DEBUG_DETAIL, dptr, "... '%s'\n", cbuf);
            if (ic == reclen) break;
        }
        // calc wordcount time needed to finish tape operation
        time = msec_to_wordtime(11 + reclen * 0.068); 
        // transfer read data to IAS
        if ((cmd != OP_RTC) && (LastTapeIndicator == 0)) {            
            LastTapeIndicator = mt_transfer_tape_rec_to_IAS(buf, reclen, (cmd == OP_RTN) ? 'N':'A');
            if (LastTapeIndicator) {
                sim_debug(DEBUG_EXP, dptr, "Tape unit %d: decode error %s\n", unit, TapeIndicatorStr[LastTapeIndicator]); 
                uptr->u5 |= MT_IND; 
            }
        }
        break;
    case OP_WTM:
    case OP_WTA:
    case OP_WTN:
        sim_debug(DEBUG_CMD, dptr, "Tape unit %d: init write\n", unit);
        if (cmd == OP_WTM) {
            r = sim_tape_wrtmk(uptr);
            // calc tape pos:
            uptr->u3 += (int32) ((1 * 0.005 + 0.75) * 1000); // Tape Mark is 1 word long
            reclen=1;
            sim_debug(DEBUG_DETAIL, dptr, "Write Tape Mark\n");
        } else {
            sim_debug(DEBUG_DETAIL, dptr, "IAS TimingRing is %d\n", IAS_TimingRing+9000);
            mt_transfer_IAS_to_tape_rec(buf, &reclen, (cmd == OP_WTN) ? 'N':'A');
            // actual simulated tape write
            r = sim_tape_wrrecf(uptr, buf, reclen);
            // calc tape pos:
            uptr->u3 += (int32) ((reclen * 0.005 + 0.75) * 1000);
            // debug output: display buf as 50 chars per line
            sim_debug(DEBUG_DETAIL, dptr, "Write record (%d chars) to tape:\n", (int) reclen);
            ic = 0;
            while (1) {
                for (i=0;i<50;i++) {
                    cbuf[i] = 0;
                    if (ic == reclen) break;
                    cbuf[i] = buf[ic++];
                }
                sim_debug(DEBUG_DETAIL, dptr, "... '%s'\n", cbuf);
                if (ic == reclen) break;
            }
            sim_debug(DEBUG_DETAIL, dptr, "     IAS TimingRing is %d\n", IAS_TimingRing+9000);
        }
        // process result conditions
        if (r == MTSE_WRP) {
            LastTapeIndicator = MT_IND_WRT_PROT;
            uptr->u5 |= MT_IND; 
        } else if ((r == MTSE_EOM) || (uptr->u3 > uptr->u4*1000)) {
            LastTapeIndicator = MT_IND_EOT;
            uptr->u5 |= MT_IND; 
        } else if (r != MTSE_OK) {
            sim_debug(DEBUG_EXP, dptr, "Tape unit %d: write error %d\n", unit, r);
            return STOP_IO;
        }
        // calc wordcount time needed
        time = msec_to_wordtime(11 + reclen * 0.068); // time to remove Tape Control interlock
        break;
    case OP_BST:
    case OP_RWD:
        /* Check if at load point, quick return if so */
        if (sim_tape_bot(uptr)) {
            sim_debug(DEBUG_CMD, dptr, "Tape unit %d: at BOT\n", unit);
            uptr->u5 |= MT_RDY;
            uptr->u3 = 0;
            return SCPE_OK;
        }
        if (cmd == OP_RWD) {
            sim_debug(DEBUG_CMD, dptr, "Tape unit %d: init rewind\n", unit);
            sim_tape_rewind(uptr);
            uptr->u3 = 0;
            time = msec_to_wordtime(35); // 35 msec to remove Tape Control interlock
        }
        if (cmd == OP_BST) {
            sim_debug(DEBUG_CMD, dptr, "Tape unit %d: init backstep record\n", unit);
            r = sim_tape_sprecr(uptr, &reclen);
            if ((r != MTSE_OK) && (r != MTSE_TMK)) {
                return r;
            }
            uptr->u3 -= (int32) ((reclen * 0.005 + 0.75) * 1000);
            time = msec_to_wordtime(38.5 + reclen * 0.068); // time to remove Tape Control interlock
        }
        break;
    default:
        sim_debug(DEBUG_EXP, dptr, "Tape %d: unknown command %02d\n", unit, cmd);
        // should never occurs. just to catch it if so.
    }
    if (bFastMode) time = 0;
    sim_cancel(uptr);
    sim_activate(uptr, time);
    return SCPE_OK_INPROGRESS;
}

/* Handle processing of tape requests. */
t_stat mt_srv(UNIT * uptr)
{
    DEVICE             *dptr = find_dev_from_unit(uptr);
    int                 unit = (uptr - dptr->units);
    int                 cmd = uptr->u5 & MT_CMDMSK;
    int                 time;

    switch (cmd) {
    case OP_RTC:
    case OP_RTA:
    case OP_RTN:
    case OP_WTM:
    case OP_WTA:
    case OP_WTN:
        if (InterLockCount[IL_Tape]) {
            // remove Tape Control Interlock
            InterLockCount[IL_Tape] = 0;
            sim_debug(DEBUG_CMD, dptr, "Tape unit %d: free TCI interlock\n", unit);
        }
        if (InterLockCount[IL_IAS]) {
            // remove IAS Interlock
            InterLockCount[IL_IAS] = 0;
            sim_debug(DEBUG_CMD, dptr, "Tape unit %d: free IAS interlock\n", unit);
        }
        // command finished
        goto tape_done;
        break;
    case OP_BST:
    case OP_RWD:
        if (InterLockCount[IL_Tape]) {
            // remove Tape Control Interlock
            InterLockCount[IL_Tape] = 0;
            sim_debug(DEBUG_CMD, dptr, "Tape unit %d: free TCI interlock\n", unit);
            // calculate end of backspace/rew time
            if (cmd == OP_BST) {
                time = msec_to_wordtime(38.5 + 22); 
            } else {
                // max time to rew is 1.2 minutes.
                // get a rought aprox on % medium used (not exacta as not taking into account Hi/low speed rew)
                time = (int) ((uptr->u3 / (uptr->u4*1000.0))*1.2*60+1);  // number of seconds
                time = msec_to_wordtime(time * 1000);         // number of word times
            }
            if (bFastMode) time = 0;
            sim_cancel(uptr);
            sim_activate(uptr, time);
        } else {
            // command finished
            goto tape_done;
        }
        break;
    default: 
        return SCPE_ARG; // should never occurs. just to catch it if so.
    tape_done:
        sim_debug(DEBUG_CMD, dptr, "Tape unit %d: ready\n", unit);
        sim_debug(DEBUG_DETAIL, &cpu_dev, "... Tape %d done, used %4.2f%% of medium\n", 
            unit,
            (uptr->u3 / (uptr->u4*1000.0))*100.0
            );
        // set unit ready to accept new commands
        uptr->u5 |= MT_RDY;
        break;
    }
    return SCPE_OK;
}

void mt_ini(UNIT * uptr, t_bool f)
{
    if (uptr->flags & UNIT_ATT) {
        uptr->u5 = MT_RDY;
    } else {
        uptr->u5 = 0;
    }
    uptr->u3 = 0;
    if (uptr->u4 == 0) uptr->u4 = 28800; // default 2400 ft reel; 1 foot = 12 inches; 2400 ft = 28800 inches
} 

t_stat mt_reset(DEVICE * dptr)
{
    int i;
    for (i = 0; i < 6; i++) {
        mt_ini(&mt_unit[i], 0);
    }
    return SCPE_OK;
}

t_stat mt_attach(UNIT * uptr, CONST char *file)
{
    t_stat              r;

    if ((r = sim_tape_attach(uptr, file)) != SCPE_OK)
        return r;
    uptr->u3 = 0;
    uptr->u5 = MT_RDY;
    return SCPE_OK;
}

t_stat mt_detach(UNIT * uptr)
{
    uptr->u3 = 0;
    uptr->u5 = 0;
    sim_cancel(uptr); // cancel any pending command
    return sim_tape_detach(uptr);
}

/* Set tape length */

t_stat mt_set_len (UNIT *uptr, int32 val, CONST char *cptr, void *desc)
{
    int len;
    t_stat r;

    if ((cptr == NULL) || (*cptr == 0))  return SCPE_ARG;
    len = (int) get_uint (cptr, 10, 10000, &r);
    if (r != SCPE_OK) return SCPE_ARG;
    if (len < 50) return SCPE_ARG;
    uptr->u4 = 28800 * len / 2400;
    return SCPE_OK;
}

/* Show tape length */

t_stat mt_show_len (FILE *st, UNIT *uptr, int32 val, CONST void *desc)
{
    fprintf (st, "length %d foot", uptr->u4 * 2400 / 28800);
    return SCPE_OK;
}


t_stat
mt_help(FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, const char *cptr)
{
   fprintf (st, "%s\n\n", mt_description(dptr));
   fprintf (st, "The magnetic tape assumes that all tapes are 7 track\n");
   fprintf (st, "with valid parity. Tapes are assumed to be 200 characters per\n");
   fprintf (st, "inch. \n\n");
   sim_tape_attach_help (st, dptr, uptr, flag, cptr);
   fprint_set_help(st, dptr);
   fprint_show_help(st, dptr);
   return SCPE_OK;
}

const char *
mt_description(DEVICE *dptr)
{
   return "IBM 727 Magnetic tape unit";
}


