/*
 * CiderPress
 * Copyright (C) 2007 by faddenSoft, LLC.  All Rights Reserved.
 * See the file LICENSE for distribution terms.
 */
/*
 * GCR nibble image support for 3.5" disks.
 *
 * Each track has between 8 and 12 512-byte sectors.  The encoding is similar
 * to but different from that used on 5.25" disks.
 *
 * THOUGHT: this is currently designed for unpacking all blocks from a track.
 * We really ought to allow the user to view the track in nibble form, which
 * means reworking the interface to be more like the 5.25" nibble stuff.  We
 * should present it as a block interface rather than track/sector; the code
 * here can convert the block # to track/sector, and just provide a raw
 * interface for the nibble track viewer.
 */
#include "StdAfx.h"
#include "DiskImgPriv.h"

/*
Physical sector layout:

+00  self-sync 0xff pattern (36 10-bit bytes, or 45 8-bit bytes)
+36  addr prolog (0xd5 0xaa 0x96)
+39  6&2enc track number (0-79 mod 63)
+40  6&2enc sector number (0-N)
+41  6&2enc side (0x00 or 0x20, ORed with 0x01 for tracks >= 64)
+42  6&2enc format (0x22, 0x24, others?)
+43  6&2enc checksum (track ^ sector ^ side ^ format)
+44  addr epilog (0xde 0xaa)
+46  self-sync 0xff (6 10-bit bytes)
+52  data prolog (0xd5 0xaa 0xad)
+55  6&2enc sector number (another copy)
+56  6&2enc nibblized data (699 bytes)
+755 checksum, 3 bytes 6&2 encoded as 4 bytes
+759 data epilog (0xde 0xaa)
+761 0xff (pad byte)

Some sources say it starts with 42 10-bit self-sync bytes instead of 36.
*/

/*
 * Basic disk geometry.
 */
const int kCylindersPerDisk = 80;
const int kHeadsPerCylinder = 2;
const int kMaxSectorsPerTrack = 12;
const int kSectorSize35 = 524;      // 512 data bytes + 12 tag bytes
const int kTagBytesLen = 12;
const int kDataChecksumLen = 3;
const int kChunkSize35 = 175;       // ceil(524 / 3)
const int kOffsetToChecksum = 699;
const int kNibblizedOutputLen = (kOffsetToChecksum + 4);
const int kMaxDataReach = 48;       // should only be 6 bytes */

enum {
    kAddrProlog0 = 0xd5,
    kAddrProlog1 = 0xaa,
    kAddrProlog2 = 0x96,
    kAddrEpilog0 = 0xde,
    kAddrEpilog1 = 0xaa,

    kDataProlog0 = 0xd5,
    kDataProlog1 = 0xaa,
    kDataProlog2 = 0xad,
    kDataEpilog0 = 0xde,
    kDataEpilog1 = 0xaa,
};

/*
 * There are 12 sectors per track for the first 16 cylinders, 11 sectors
 * per track for the next 16, and so on until we're down to 8 per track.
 */
/*static*/ int DiskImg::SectorsPerTrack35(int cylinder)
{
    return kMaxSectorsPerTrack - (cylinder / 16);
}

/*
 * Convert cylinder/head/sector to a block number on a 3.5" disk.
 */
/*static*/ int DiskImg::CylHeadSect35ToBlock(int cyl, int head, int sect)
{
    int i, block;

    assert(cyl >= 0 && cyl < kCylindersPerDisk);
    assert(head >= 0 && head < kHeadsPerCylinder);
    assert(sect >= 0 && sect < SectorsPerTrack35(cyl));

    block = 0;
    for (i = 0; i < cyl; i++)
        block += SectorsPerTrack35(i) * kHeadsPerCylinder;
    if (head)
        block += SectorsPerTrack35(i);
    block += sect;

    //LOGI("Nib35: c/h/s %d/%d/%d --> block %d", cyl, head, sect, block);
    assert(block >= 0 && block < 1600);
    return block;
}

/*
 * Unpack a nibble track.
 *
 * "outputBuf" must be able to hold 512 * 12 sectors of decoded sector data.
 */
/*static*/ DIError DiskImg::UnpackNibbleTrack35(const uint8_t* nibbleBuf,
    long nibbleLen, uint8_t* outputBuf, int cyl, int head,
    LinearBitmap* pBadBlockMap)
{
    CircularBufferAccess buffer(nibbleBuf, nibbleLen);
    bool foundSector[kMaxSectorsPerTrack];
    uint8_t sectorBuf[kSectorSize35];
    uint8_t readSum[kDataChecksumLen];
    uint8_t calcSum[kDataChecksumLen];
    int i;

    memset(&foundSector, 0, sizeof(foundSector));

    i = 0;
    while (i < nibbleLen) {
        int sector;

        i = FindNextSector35(buffer, i, cyl, head, &sector);
        if (i < 0)
            break;

        assert(sector >= 0 && sector < SectorsPerTrack35(cyl));
        if (foundSector[sector]) {
            LOGI("Nib35: WARNING: found two copies of sect %d on cyl=%d head=%d",
                sector, cyl, head);
        } else {
            memset(sectorBuf, 0xa9, sizeof(sectorBuf));
            if (DecodeNibbleSector35(buffer, i, sectorBuf, readSum, calcSum))
            {
                /* successfully decoded sector, copy data & verify checksum */
                foundSector[sector] = true;
                memcpy(outputBuf + kBlockSize * sector,
                    sectorBuf + kTagBytesLen, kBlockSize);

                if (calcSum[0] != readSum[0] ||
                    calcSum[1] != readSum[1] ||
                    calcSum[2] != readSum[2])
                {
                    LOGI("Nib35: checksum mismatch: 0x%06x vs. 0x%06x",
                        calcSum[0] << 16 | calcSum[1] << 8 | calcSum[2],
                        readSum[0] << 16 | readSum[1] << 8 | readSum[2]);
                    LOGI("Nib35:  marking cyl=%d head=%d sect=%d (block=%d)",
                        cyl, head, sector,
                        CylHeadSect35ToBlock(cyl, head, sector));
                    pBadBlockMap->Set(CylHeadSect35ToBlock(cyl, head, sector));
                }
            }
        }
    }

    /*
     * Check to see if we have all our parts.  Anything missing sets
     * a flag in the "bad block" map.
     */
    for (i = SectorsPerTrack35(cyl)-1; i >= 0; i--) {
        if (!foundSector[i]) {
            LOGI("Nib35: didn't find cyl=%d head=%d sect=%d (block=%d)",
                cyl, head, i, CylHeadSect35ToBlock(cyl, head, i));
            pBadBlockMap->Set(CylHeadSect35ToBlock(cyl, head, i));
        }

        /*
        // DEBUG test
        if ((cyl == 0 || cyl == 12 || cyl == 79) &&
            (head == (cyl & 0x01)) &&
            (i == 1 || i == 7))
        {
            LOGI("DEBUG: setting bad %d/%d/%d (%d)",
                cyl, head, i, CylHeadSect35ToBlock(cyl, head, i));
            pBadBlockMap->Set(CylHeadSect35ToBlock(cyl, head, i));
        }
        */
    }

    return kDIErrNone;      // maybe return an error if nothing found?
}

/*
 * Returns the offset of the next sector, or -1 if we went off the end.
 */
/*static*/ int DiskImg::FindNextSector35(const CircularBufferAccess& buffer,
    int start, int cyl, int head, int* pSector)
{
    int end = buffer.GetSize();
    int i;

    for (i = start; i < end; i++) {
        bool foundAddr = false;

        if (buffer[i] == kAddrProlog0 &&
            buffer[i+1] == kAddrProlog1 &&
            buffer[i+2] == kAddrProlog2)
        {
            foundAddr = true;
        }

        if (foundAddr) {
            /* decode the address field */
            int trackNum, sectNum, side, format, checksum;

            trackNum = kInvDiskBytes62[buffer[i+3]];
            sectNum = kInvDiskBytes62[buffer[i+4]];
            side = kInvDiskBytes62[buffer[i+5]];
            format = kInvDiskBytes62[buffer[i+6]];
            checksum = kInvDiskBytes62[buffer[i+7]];
            if (trackNum == kInvInvalidValue ||
                sectNum == kInvInvalidValue ||
                side == kInvInvalidValue ||
                format == kInvInvalidValue ||
                checksum == kInvInvalidValue)
            {
                LOGI("Nib35: garbled address header found");
                continue;
            }
            //LOGI(" Nib35: got addr: track=%2d sect=%2d side=%d format=%d sum=0x%02x",
            //  trackNum, sectNum, side, format, checksum);
            if (side != ((head * 0x20) | (cyl >> 6))) {
                LOGI("Nib35: unexpected value for side: %d on cyl=%d head=%d",
                    side, cyl, head);
            }
            if (sectNum >= SectorsPerTrack35(cyl)) {
                LOGI("Nib35: invalid value for sector: %d (cyl=%d)",
                    sectNum, cyl);
                continue;
            }
            /* format seems to be 0x22 or 0x24 */
            if (checksum != (trackNum ^ sectNum ^ side ^ format)) {
                LOGI("Nib35: unexpected checksum: 0x%02x vs. 0x%02x",
                    checksum, trackNum ^ sectNum ^ side ^ format);
                continue;
            }

            /* check the epilog bytes */
            if (buffer[i+8] != kAddrEpilog0 ||
                buffer[i+9] != kAddrEpilog1)
            {
                LOGI("Nib35: invalid address epilog");
                /* maybe we allow this anyway? */
            }

            *pSector = sectNum;
            return i+10;        // move past address field
        }
    }

    return -1;
}

/*
 * Unpack a 524-byte sector from a 3.5" disk.  Start with "start" pointed
 * in the general vicinity of the data prolog bytes.
 *
 * "sectorBuf" must hold at least kSectorSize35 bytes.  It will be filled
 * with the decoded data.
 * "readChecksum" and "calcChecksum" must each hold at least kDataChecksumLen
 * bytes.  The former holds the checksum read from the sector, the latter
 * holds the checksum computed from the data.
 *
 * The 4 to 3 conversion is pretty straightforward.  The checksum is
 * a little crazy.
 *
 * Returns "true" if all goes well, "false" if there is a problem.  Does
 * not return false on a checksum mismatch -- it's up to the caller to
 * verify the checksum if desired.
 */
/*static*/ bool DiskImg::DecodeNibbleSector35(const CircularBufferAccess& buffer,
    int start, uint8_t* sectorBuf, uint8_t* readChecksum,
    uint8_t* calcChecksum)
{
    const int kMaxDataReach35 = 48;       // fairly arbitrary
    uint8_t* sectorBufStart = sectorBuf;
    uint8_t part0[kChunkSize35], part1[kChunkSize35], part2[kChunkSize35];
    unsigned int chk0, chk1, chk2;
    uint8_t val, nib0, nib1, nib2, twos;
    int i, off;

    /*
     * Find the start of the actual data.  Adjust "start" to point at it.
     */
    for (off = start; off < start + kMaxDataReach35; off++) {
        if (buffer[off] == kDataProlog0 &&
            buffer[off+1] == kDataProlog1 &&
            buffer[off+2] == kDataProlog2)
        {
            start = off + 4;    // 3 prolog bytes + sector number
            break;
        }
    }
    if (off == start + kMaxDataReach35) {
        LOGI("nib25: could not find start of data field");
        return false;
    }

    /*
     * Assemble 8-bit bytes from 6&2 encoded values.
     */
    off = start;
    for (i = 0; i < kChunkSize35; i++) {
        twos = kInvDiskBytes62[buffer[off++]];
        nib0 = kInvDiskBytes62[buffer[off++]];
        nib1 = kInvDiskBytes62[buffer[off++]];
        if (i != kChunkSize35-1)
            nib2 = kInvDiskBytes62[buffer[off++]];
        else
            nib2 = 0;

        if (twos == kInvInvalidValue ||
            nib0 == kInvInvalidValue ||
            nib1 == kInvInvalidValue ||
            nib2 == kInvInvalidValue)
        {
            // junk found
            LOGI("Nib25: found invalid disk byte in sector data at %d",
                off - start);
            LOGI("       (one of 0x%02x 0x%02x 0x%02x 0x%02x)",
                buffer[off-4], buffer[off-3], buffer[off-2], buffer[off-1]);
            return false;
            //if (twos == kInvInvalidValue)
            //  twos = 0;
            //if (nib0 == kInvInvalidValue)
            //  nib0 = 0;
            //if (nib1 == kInvInvalidValue)
            //  nib1 = 0;
            //if (nib2 == kInvInvalidValue)
            //  nib2 = 0;
        }

        part0[i] = nib0 | ((twos << 2) & 0xc0);
        part1[i] = nib1 | ((twos << 4) & 0xc0);
        part2[i] = nib2 | ((twos << 6) & 0xc0);
    }
    assert(off == start + kOffsetToChecksum);

    chk0 = chk1 = chk2 = 0;
    i = 0;
    while (true) {
        chk0 = (chk0 & 0xff) << 1;
        if (chk0 & 0x0100)
            chk0++;

        val = part0[i] ^ chk0;
        chk2 += val;
        if (chk0 & 0x0100) {
            chk2++;
            chk0 &= 0xff;
        }
        *sectorBuf++ = val;

        val = part1[i] ^ chk2;
        chk1 += val;
        if (chk2 > 0xff) {
            chk1++;
            chk2 &= 0xff;
        }
        *sectorBuf++ = val;

        if (sectorBuf - sectorBufStart == 524)
            break;

        val = part2[i] ^ chk1;
        chk0 += val;
        if (chk1 > 0xff) {
            chk0++;
            chk1 &= 0xff;
        }
        *sectorBuf++ = val;

        i++;
        assert(i < kChunkSize35);
        //LOGI("i = %d, diff=%d", i, sectorBuf - sectorBufStart);
    }

    calcChecksum[0] = chk0;
    calcChecksum[1] = chk1;
    calcChecksum[2] = chk2;

    if (!UnpackChecksum35(buffer, off, readChecksum)) {
        LOGI("Nib35: failure reading checksum");
        readChecksum[0] = calcChecksum[0] ^ 0xff;   // force a failure
        return false;
    }
    off += 4;       // skip past checksum bytes

    if (buffer[off] != kDataEpilog0 || buffer[off+1] != kDataEpilog1) {
        LOGI("nib25: WARNING: data epilog not found");
        // allow it, if the checksum matches
    }

//#define TEST_ENC_35
#ifdef TEST_ENC_35
    {
        uint8_t nibBuf[kNibblizedOutputLen];
        memset(nibBuf, 0xcc, sizeof(nibBuf));

        /* encode what we just decoded */
        EncodeNibbleSector35(sectorBufStart, nibBuf);
        /* compare it to the original */
        for (i = 0; i < kNibblizedOutputLen; i++) {
            if (buffer[start + i] != nibBuf[i]) {
                /*
                 * The very last "twos" entry may have undefined bits when
                 * written by a real drive.  Peel it apart and ignore the
                 * two flaky bits.
                 */
                if (i == 696) {
                    uint8_t val1, val2;
                    val1 = kInvDiskBytes62[buffer[start + i]];
                    val2 = kInvDiskBytes62[nibBuf[i]];
                    if ((val1 & 0xfc) != (val2 & 0xfc)) {
                        LOGI("Nib35 DEBUG: output differs at byte %d"
                              " (0x%02x vs 0x%02x / 0x%02x vs 0x%02x)",
                            i, buffer[start+i], nibBuf[i], val1, val2);
                    }
                } else {
                    // note: checksum is 699-702
                    LOGI("Nib35 DEBUG: output differs at byte %d (0x%02x vs 0x%02x)",
                        i, buffer[start+i], nibBuf[i]);
                }
            }
        }
    }
#endif /*TEST_ENC_35*/

    return true;
}

/*
 * Unpack the 6&2 encoded 3-byte checksum at the end of a sector.
 *
 * "offset" should point to the first byte of the checksum.
 *
 * Returns "true" if all goes well, "false" otherwise.
 */
/*static*/ bool DiskImg::UnpackChecksum35(const CircularBufferAccess& buffer,
    int offset, uint8_t* checksumBuf)
{
    uint8_t nib0, nib1, nib2, twos;
    
    twos = kInvDiskBytes62[buffer[offset++]];
    nib2 = kInvDiskBytes62[buffer[offset++]];
    nib1 = kInvDiskBytes62[buffer[offset++]];
    nib0 = kInvDiskBytes62[buffer[offset++]];

    if (twos == kInvInvalidValue ||
        nib0 == kInvInvalidValue ||
        nib1 == kInvInvalidValue ||
        nib2 == kInvInvalidValue)
    {
        LOGI("nib25: found invalid disk byte in checksum");
        return false;
    }

    checksumBuf[0] = nib0 | ((twos << 6) & 0xc0);
    checksumBuf[1] = nib1 | ((twos << 4) & 0xc0);
    checksumBuf[2] = nib2 | ((twos << 2) & 0xc0);
    return true;
}

/*
 * Encode 524 bytes of sector data into 699 bytes of 6&2 nibblized data
 * plus a 4-byte checksum.
 *
 * "outBuf" must be able to hold kNibblizedOutputLen bytes.
 */
/*static*/ void DiskImg::EncodeNibbleSector35(const uint8_t* sectorData,
    uint8_t* outBuf)
{
    const uint8_t* sectorDataStart = sectorData;
    uint8_t* outBufStart = outBuf;
    uint8_t part0[kChunkSize35], part1[kChunkSize35], part2[kChunkSize35];
    unsigned int chk0, chk1, chk2;
    uint8_t val, twos;
    int i;

    /*
     * Compute checksum and split the input into 3 pieces.
     */
    i = 0;
    chk0 = chk1 = chk2 = 0;
    while (true) {
        chk0 = (chk0 & 0xff) << 1;
        if (chk0 & 0x0100)
            chk0++;

        val = *sectorData++;
        chk2 += val;
        if (chk0 & 0x0100) {
            chk2++;
            chk0 &= 0xff;
        }
        part0[i] = (val ^ chk0) & 0xff;

        val = *sectorData++;
        chk1 += val;
        if (chk2 > 0xff) {
            chk1++;
            chk2 &= 0xff;
        }
        part1[i] = (val ^ chk2) & 0xff;

        if (sectorData - sectorDataStart == 524)
            break;

        val = *sectorData++;
        chk0 += val;
        if (chk1 > 0xff) {
            chk0++;
            chk1 &= 0xff;
        }
        part2[i] = (val ^ chk1) & 0xff;
        i++;
    }
    part2[kChunkSize35-1] = 0;  // gets merged into the "twos"

    assert(i == kChunkSize35-1);

    /*
     * Output the nibble data.
     */
    for (i = 0; i < kChunkSize35; i++) {
        twos =  ((part0[i] & 0xc0) >> 2) |
                ((part1[i] & 0xc0) >> 4) |
                ((part2[i] & 0xc0) >> 6);

        *outBuf++ = kDiskBytes62[twos];
        *outBuf++ = kDiskBytes62[part0[i] & 0x3f];
        *outBuf++ = kDiskBytes62[part1[i] & 0x3f];
        if (i != kChunkSize35 -1)
            *outBuf++ = kDiskBytes62[part2[i] & 0x3f];
    }

    /*
     * Output the checksum.
     */
    twos = ((chk0 & 0xc0) >> 6) | ((chk1 & 0xc0) >> 4) | ((chk2 & 0xc0) >> 2);
    *outBuf++ = kDiskBytes62[twos];
    *outBuf++ = kDiskBytes62[chk2 & 0x3f];
    *outBuf++ = kDiskBytes62[chk1 & 0x3f];
    *outBuf++ = kDiskBytes62[chk0 & 0x3f];

    assert(outBuf - outBufStart == kNibblizedOutputLen);
}
