/*
 * 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;

	//WMSG4("Nib35: c/h/s %d/%d/%d --> block %d\n", 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 unsigned char* nibbleBuf,
	long nibbleLen, unsigned char* outputBuf, int cyl, int head,
	LinearBitmap* pBadBlockMap)
{
    CircularBufferAccess buffer(nibbleBuf, nibbleLen);
	bool foundSector[kMaxSectorsPerTrack];
	unsigned char sectorBuf[kSectorSize35];
	unsigned char readSum[kDataChecksumLen];
	unsigned char 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]) {
			WMSG3("Nib35: WARNING: found two copies of sect %d on cyl=%d head=%d\n",
				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])
				{
					WMSG2("Nib35: checksum mismatch: 0x%06x vs. 0x%06x\n",
						calcSum[0] << 16 | calcSum[1] << 8 | calcSum[2],
						readSum[0] << 16 | readSum[1] << 8 | readSum[2]);
					WMSG4("Nib35:  marking cyl=%d head=%d sect=%d (block=%d)\n",
						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]) {
			WMSG4("Nib35: didn't find cyl=%d head=%d sect=%d (block=%d)\n",
				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))
		{
			WMSG4("DEBUG: setting bad %d/%d/%d (%d)\n",
				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)
			{
				WMSG0("Nib35: garbled address header found\n");
				continue;
			}
			//WMSG5(" Nib35: got addr: track=%2d sect=%2d side=%d format=%d sum=0x%02x\n",
			//	trackNum, sectNum, side, format, checksum);
			if (side != ((head * 0x20) | (cyl >> 6))) {
				WMSG3("Nib35: unexpected value for side: %d on cyl=%d head=%d\n",
					side, cyl, head);
			}
			if (sectNum >= SectorsPerTrack35(cyl)) {
				WMSG2("Nib35: invalid value for sector: %d (cyl=%d)\n",
					sectNum, cyl);
				continue;
			}
			/* format seems to be 0x22 or 0x24 */
			if (checksum != (trackNum ^ sectNum ^ side ^ format)) {
				WMSG2("Nib35: unexpected checksum: 0x%02x vs. 0x%02x\n",
					checksum, trackNum ^ sectNum ^ side ^ format);
				continue;
			}

			/* check the epilog bytes */
			if (buffer[i+8] != kAddrEpilog0 ||
				buffer[i+9] != kAddrEpilog1)
			{
				WMSG0("Nib35: invalid address epilog\n");
				/* 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,
	unsigned char* sectorBuf, unsigned char* readChecksum,
	unsigned char* calcChecksum)
{
    const int kMaxDataReach35 = 48;       // fairly arbitrary
	unsigned char* sectorBufStart = sectorBuf;
	unsigned char part0[kChunkSize35], part1[kChunkSize35], part2[kChunkSize35];
	unsigned int chk0, chk1, chk2;
	unsigned char 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) {
		WMSG0("nib25: could not find start of data field\n");
		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
			WMSG1("Nib25: found invalid disk byte in sector data at %d\n",
				off - start);
			WMSG4("       (one of 0x%02x 0x%02x 0x%02x 0x%02x)\n",
				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);
		//WMSG2("i = %d, diff=%d\n", i, sectorBuf - sectorBufStart);
	}

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

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

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

//#define TEST_ENC_35
#ifdef TEST_ENC_35
	{
		unsigned char 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) {
					unsigned char val1, val2;
					val1 = kInvDiskBytes62[buffer[start + i]];
					val2 = kInvDiskBytes62[nibBuf[i]];
					if ((val1 & 0xfc) != (val2 & 0xfc)) {
						WMSG5("Nib35 DEBUG: output differs at byte %d"
							  " (0x%02x vs 0x%02x / 0x%02x vs 0x%02x)\n",
							i, buffer[start+i], nibBuf[i], val1, val2);
					}
				} else {
					// note: checksum is 699-702
					WMSG3("Nib35 DEBUG: output differs at byte %d (0x%02x vs 0x%02x)\n",
						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,
	unsigned char* checksumBuf)
{
	unsigned char 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)
	{
		WMSG0("nib25: found invalid disk byte in checksum\n");
		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 unsigned char* sectorData,
	unsigned char* outBuf)
{
	const unsigned char* sectorDataStart = sectorData;
	unsigned char* outBufStart = outBuf;
	unsigned char part0[kChunkSize35], part1[kChunkSize35], part2[kChunkSize35];
	unsigned int chk0, chk1, chk2;
	unsigned char 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);
}
