/*
 * CiderPress
 * Copyright (C) 2007 by faddenSoft, LLC.  All Rights Reserved.
 * See the file LICENSE for distribution terms.
 */
/*
 * Multiple conversions for double-hi-res graphics.
 */
#include "StdAfx.h"
#include "DoubleHiRes.h"
#include "HiRes.h"

/*
 * The screen layout is described in detail in Apple //e technote #3.
 *
 * Summary: pixel data is byte-interleaved between main and auxilliary
 * 8K pages.  Seven pixels of each byte contribute to the image; the high
 * bit of each byte is ignored.  There are 16 possible colors, which match
 * up with the 16 lo-res colors.
 *
 * The interference patterns caused by adjacent colors are extremely
 * difficult to model accurately, especially considering that RGB and
 * composite outputs seem to be different.
 */

/*
 * Decide whether or not we want to handle this file.
 */
void
ReformatDHR::Examine(ReformatHolder* pHolder)
{
	ReformatHolder::ReformatApplies applies = ReformatHolder::kApplicNot;
	long fileLen = pHolder->GetSourceLen(ReformatHolder::kPartData);
	long fileType = pHolder->GetFileType();
	long auxType = pHolder->GetAuxType();
	long dhrAlg;
	bool relaxed;

	relaxed = pHolder->GetOption(ReformatHolder::kOptRelaxGfxTypeCheck) != 0;

	if ((fileType == kTypeFOT && auxType < 0x4000 && fileLen == 16384) ||
		(relaxed &&
			(
			(fileType == kTypeBIN && fileLen == 16376) ||
			(fileType == kTypeBIN && fileLen == 16380) ||
			(fileType == kTypeBIN && fileLen == 16384)
			)
		))
	{
		applies = ReformatHolder::kApplicProbably;
	}

	pHolder->SetApplic(ReformatHolder::kReformatDHR_Latched, applies,
		ReformatHolder::kApplicNot, ReformatHolder::kApplicNot);
	pHolder->SetApplic(ReformatHolder::kReformatDHR_BW, applies,
		ReformatHolder::kApplicNot, ReformatHolder::kApplicNot);
	pHolder->SetApplic(ReformatHolder::kReformatDHR_Plain140, applies,
		ReformatHolder::kApplicNot, ReformatHolder::kApplicNot);
	pHolder->SetApplic(ReformatHolder::kReformatDHR_Window, applies,
		ReformatHolder::kApplicNot, ReformatHolder::kApplicNot);

	/*
	 * Set the "preferred" flag on one option.
	 */
	dhrAlg = pHolder->GetOption(ReformatHolder::kOptDHRAlgorithm);

	switch ((Algorithms) dhrAlg) {
	case kDHRLatched:
		pHolder->SetApplicPreferred(ReformatHolder::kReformatDHR_Latched);
		break;
	case kDHRBlackWhite:
		pHolder->SetApplicPreferred(ReformatHolder::kReformatDHR_BW);
		break;
	case kDHRPlain140:
		pHolder->SetApplicPreferred(ReformatHolder::kReformatDHR_Plain140);
		break;
	case kDHRWindow:
		pHolder->SetApplicPreferred(ReformatHolder::kReformatDHR_Window);
		break;
	default:
		WMSG1("GLITCH: DHR algorithm %d not recognized\n", dhrAlg);
		break;
	}
}

/*
 * Convert a Double-Hi-Res image to a bitmap.
 */
int
ReformatDHR::Process(const ReformatHolder* pHolder,
	ReformatHolder::ReformatID id, ReformatHolder::ReformatPart part,
	ReformatOutput* pOutput)
{
	MyDIBitmap* pDib;
	const unsigned char* srcBuf = pHolder->GetSourceBuf(part);
	long srcLen = pHolder->GetSourceLen(part);
	int retval = -1;

	switch (id) {
	case ReformatHolder::kReformatDHR_Latched:
		fAlgorithm = kDHRLatched;
		break;
	case ReformatHolder::kReformatDHR_BW:
		fAlgorithm = kDHRBlackWhite;
		break;
	case ReformatHolder::kReformatDHR_Plain140:
		fAlgorithm = kDHRPlain140;
		break;
	case ReformatHolder::kReformatDHR_Window:
		fAlgorithm = kDHRWindow;
		break;
	default:
		WMSG1("GLITCH: bad id %d\n", id);
		fAlgorithm = kDHRLatched;
		break;
	}

	if (srcLen > kExpectedSize || srcLen < kExpectedSize-8) {
		WMSG2(" DHR file is not ~%d bytes long (got %d)\n",
			kExpectedSize, srcLen);
		goto bail;
	}

	/* line layout is same as standard hires */
	ReformatHiRes::InitLineOffset(fLineOffset);
	InitColorLookup();

	pDib = DHRScreenToBitmap(srcBuf);
	if (pDib == nil)
		goto bail;

	SetResultBuffer(pOutput, pDib);
	retval = 0;

bail:
	return retval;
}

/*
 * Initialize the 4-bit-window color lookup table.
 */
void
ReformatDHR::InitColorLookup(void)
{
	for (int ii = 0; ii < 4; ii++) {
		for (int jj = 0; jj < kNumDHRColors; jj++) {
			int num = jj;
			for (int kk = 0; kk < ii; kk++) {
				if (num & 0x01)
					num |= 0x10;
				num >>= 1;
				num &= 0x0f;
			}
			fColorLookup[ii][jj] = num;
		}
	}

	return;
}

/*
 * Convert a buffer of double-hires data to a 16-color DIB.
 */
MyDIBitmap*
ReformatDHR::DHRScreenToBitmap(const unsigned char* buf)
{
	MyDIBitmap* pDib = new MyDIBitmap;
	unsigned char* outBuf;
	const int kMaxLook = 4;		// padding to adjust for lookbehind/lookahead
	int pixelBits[kMaxLook+kPixelsPerLine+kMaxLook];	// 560 mono pixels
	unsigned int colorBuf[kOutputWidth];		// 560 color pixels
	int line;

	/* color map */
	enum {
		kColorBlack = 0,		// 0000
		kColorRed,				// 0001
		kColorBrown,			// 0010
		kColorOrange,			// 0011 hcolor=5
		kColorDarkGreen,		// 0100
		kColorGrey1,			// 0101
		kColorGreen,			// 0110 hcolor=1
		kColorYellow,			// 0111
		kColorDarkBlue,			// 1000
		kColorPurple,			// 1001 hcolor=2
		kColorGrey2,			// 1010
		kColorPink,				// 1011
		kColorMediumBlue,		// 1100 hcolor=6
		kColorLightBlue,		// 1101
		kColorAqua,				// 1110
		kColorWhite,			// 1110
		kNumColors
	};
	RGBQUAD colorConv[kNumColors];
	colorConv[0] =  fPalette[kPaletteBlack];
	colorConv[1] =  fPalette[kPaletteRed];
	colorConv[2] =  fPalette[kPaletteBrown];
	colorConv[3] =  fPalette[kPaletteOrange];
	colorConv[4] =  fPalette[kPaletteDarkGreen];
	colorConv[5] =  fPalette[kPaletteDarkGrey];
	colorConv[6] =  fPalette[kPaletteGreen];
	colorConv[7] =  fPalette[kPaletteYellow];
	colorConv[8] =  fPalette[kPaletteDarkBlue];
	colorConv[9] =  fPalette[kPalettePurple];
	colorConv[10] = fPalette[kPaletteLightGrey];
	colorConv[11] = fPalette[kPalettePink];
	colorConv[12] = fPalette[kPaletteMediumBlue];
	colorConv[13] = fPalette[kPaletteLightBlue];
	colorConv[14] = fPalette[kPaletteAqua];
	colorConv[15] = fPalette[kPaletteWhite];

	ASSERT(kNumColors == kPaletteSize);
	ASSERT(kNumDHRColors == kNumColors);

	ASSERT(kOutputWidth == kPixelsPerLine);

	if (pDib == nil)
		goto bail;

	outBuf = (unsigned char*) pDib->Create(kOutputWidth, kOutputHeight,
									4, kNumColors);
	if (outBuf == nil) {
		delete pDib;
		pDib = nil;
		goto bail;
	}
	pDib->SetColorTable(colorConv);


	/*
	 * Run through the lines.
	 *
	 * This is reasonably inefficient, since I'm splitting things into
	 * their constituent bits and then, for the most part, just stuffing
	 * them right back together.  Someday, if fatally bored, this should
	 * be optimized.
	 */
	for (line = 0; line < kNumLines; line++) {
		const unsigned char* lineData = buf + fLineOffset[line];
		int* bitPtr = pixelBits + kMaxLook;

		/* this is really just to clear the fore and aft MaxLook bits */
		memset(pixelBits, 0, sizeof(pixelBits));

		/* unravel the bits */
		for (int byt = 0; byt < kPixelsPerLine / 7; byt++) {
			unsigned char val;
			
			if (byt & 0x01) {
				/* odd pixels come from main memory */
				val = *(lineData+kPageSize);
				lineData++;
			} else {
				/* even pixels come from aux mem */
				val = *lineData;
			}

			for (int bit = 0; bit < 7; bit++) {
				*bitPtr++ = val & 0x01;
				val >>= 1;
			}
		}
		ASSERT(lineData <= buf + kPageSize);
		ASSERT((char*)bitPtr == (char*)pixelBits +
			sizeof(pixelBits) - kMaxLook*sizeof(pixelBits[0]));

		/*
		 * Convert the bits to colors.
		 */
		int idx;
		if (fAlgorithm == kDHRBlackWhite) {
			for (idx = 0; idx < kPixelsPerLine; idx ++) {
				int bufTarget = idx;

				if (!pixelBits[idx]) {
					colorBuf[bufTarget] = kColorBlack;
				} else {
					colorBuf[bufTarget] = kColorWhite;
				}
			}
		} else if (fAlgorithm == kDHRPlain140) {
			/*
			 * Very simple: every four pixels is a solid color.  Not too
			 * close to reality, but easy to implement.
			 */
			int pixVal = 0;
			bitPtr = pixelBits + kMaxLook;

			for (idx = 0; idx < kPixelsPerLine/4; idx++) {
				pixVal = *bitPtr++;
				pixVal = (pixVal << 1) | (*bitPtr++);
				pixVal = (pixVal << 1) | (*bitPtr++);
				pixVal = (pixVal << 1) | (*bitPtr++);
				colorBuf[idx*4] = pixVal;
				colorBuf[idx*4+1] = pixVal;
				colorBuf[idx*4+2] = pixVal;
				colorBuf[idx*4+3] = pixVal;
			}
			ASSERT(bitPtr == pixelBits + sizeof(pixelBits)/sizeof(pixelBits[0]) - kMaxLook);
		} else if (fAlgorithm == kDHRWindow) {
			/*
			 * The way we determine the value of the color at pixel N
			 * is by looking at the pixels at N-3, N-2, N-1, and N.
			 *
			 * We manage this with a continously shifting 4-bit-wide
			 * window.
			 *
			 * Note to self: interesting case at line 87, pixels 200-240,
			 * in "screen2".
			 */
			int pixVal = 0;
			bitPtr = pixelBits + kMaxLook;

			for (idx = 0; idx < kPixelsPerLine; idx++) {
				pixVal = (pixVal << 1) & 0x0f;
				if (*bitPtr++)
					pixVal |= 1;
				colorBuf[idx] = fColorLookup[(idx+1) & 0x03][pixVal];

			}
			ASSERT(bitPtr <= pixelBits + sizeof(pixelBits)/sizeof(pixelBits[0]));
#if 0
			/*
			 * Zero-stop pass, where we set the luminosity to zero if we
			 * see four zeros in a row.
			 */
			bitPtr = pixelBits + kMaxLook;
			for (idx = 0; idx < kPixelsPerLine; idx++) {
				if (!bitPtr[idx] && !bitPtr[idx+1] &&
					!bitPtr[idx+2] && !bitPtr[idx+3])
				{
					//if (line == 87 && idx > 200 && idx < 240) {
					//	WMSG1(" %4d ERASE\n", idx);
					//}
					/*colorBuf[idx] =*/ colorBuf[idx+1] =
						colorBuf[idx+2] = /*colorBuf[idx+3] =*/ kColorBlack;
				}
			}
#endif
		} else if (fAlgorithm == kDHRLatched) {
			/*
			 * We determine the value of the color at pixel N is by looking
			 * at the pixels at N-3, N-2, N-1, and N.  When we see a color
			 * transition, we also look at (N+1..N+4) to special-case
			 * white/black transitions.  This is necessary to reduce the
			 * color fringes around sharply defined objects.
			 *
			 * Once a color is "latched", we keep outputting that color
			 * until we find a new one that we like more.
			 *
			 * We manage this with a continously shifting 8-bit-wide window.
			 *
			 * Note to self: interesting case at line=98, pixels 50-80.
			 */
			unsigned int whole;
			int newColor, oldColor;

			bitPtr = pixelBits;

			whole = 0;
			for (idx = 0; idx < 8; idx++) {
				whole <<= 1;
				if (*bitPtr++)
					whole |= 1;
			}
			ASSERT((whole & (~0xff)) == 0);

			/* grab the color of the "previous" 4 pixels */
			oldColor = fColorLookup[idx & 0x03][whole & 0x0f];

			for (idx = 0; idx < kPixelsPerLine; idx++, bitPtr++) {
				//if (line == 98 && idx > 50 && idx < 80) {
				//	if (!(idx % 4)) {
				//		WMSG2(" idx %3d: bits=0x%02x  PPPPCNNN\n",
				//			idx, whole & 0xff);
				//	}
				//}

				/* shift another bit in, to give us 3 prev and 4 next */
				/* looks like PPPCNNNN */
				ASSERT(*bitPtr == 0 || *bitPtr == 1);
				whole = (whole << 1) | *bitPtr;
				whole &= 0xff;		// not needed; useful for printfs

				/* get the new color (from PPPC bits) */
				newColor = fColorLookup[(idx+1) & 0x03][(whole & 0xf0) >> 4];

				//if (line == 98 && idx > 50 && idx < 80) {
				//	WMSG4(" idx %3d:   old=%-2d new=%-2d (bits=0x%02x)\n", idx,
				//		oldColor, newColor, whole & 0xff);
				//}

				if (newColor != oldColor) {
					/*
					 * Transition to new color; check for white/black blocks
					 * in *next* chunk of pixels.  The goal is to eliminate
					 * color fringes on white/black boundaries, which are the
					 * most easily visible.
					 */
					int shift1, shift2, shift3;
					shift1 = (whole >> 3) & 0x0f;	// PPCN
					shift2 = (whole >> 2) & 0x0f;	// PCNN
					shift3 = (whole >> 1) & 0x0f;	// CNNN

					if (shift1 == 0x0f || shift2 == 0x0f || shift3 == 0x0f)
						newColor = kColorWhite;
					else if (shift1 == 0 || shift2 == 0 || shift3 == 0)
						newColor = kColorBlack;
					//if (line == 98 && idx > 50 && idx < 80) {
					//	WMSG2(" idx %3d:    S new=%-2d\n", idx, newColor);
					//}
				}

				//if (line == 98 && idx > 50 && idx < 80) {
					//newColor = kColorYellow;
				//}

				colorBuf[idx] = newColor;

				/*
				 * Use the new color as the old color for the next iteration.
				 * This is *NOT* the same as getting the color from PPPP
				 * before shifting next round, because we might have
				 * overridden white or black above.  In that case, the new
				 * color would be compared against white or black in the
				 * transition check, instead of comparing against the actual
				 * color of the PPPP pixels.
				 */
				oldColor = newColor;		// latch it
				//oldColor = fColorLookup[idx & 0x03][whole & 0x0f];

			}
			ASSERT(bitPtr <= pixelBits + sizeof(pixelBits)/sizeof(pixelBits[0]));
		} else {

			ASSERT(false);

#if defined(DHR_MULTIPASS)
			unsigned int colorBuf1[kMaxLook+kOutputWidth+kMaxLook];
			int pixVal = 0;
			bitPtr = pixelBits + kMaxLook;

			/*
			 * Start simple.
			 */
			memset(colorBuf1, 0, sizeof(colorBuf1));
			for (idx = 0; idx < kPixelsPerLine/4; idx++) {
				pixVal = *bitPtr++;
				pixVal = (pixVal << 1) | (int)(*bitPtr++);
				pixVal = (pixVal << 1) | (int)(*bitPtr++);
				pixVal = (pixVal << 1) | (int)(*bitPtr++);
				colorBuf1[idx*4 +kMaxLook] = pixVal;
				colorBuf1[idx*4+1 +kMaxLook] = pixVal;
				colorBuf1[idx*4+2 +kMaxLook] = pixVal;
				colorBuf1[idx*4+3 +kMaxLook] = pixVal;
			}
			ASSERT(bitPtr <= pixelBits + sizeof(pixelBits)/sizeof(pixelBits[0]));

			/*
			 * Now go back and patch all of the transitions between colors.
			 * The most significant are transitions to and from black,
			 * which have to truncate bits.
			 */
			for (idx = 3; idx < kMaxLook+kPixelsPerLine; idx += 4) {
				if (colorBuf1[idx] != colorBuf1[idx+1]) {
					/* color change */
					if (colorBuf1[idx] == 0) {
						/* trim pixels on left */
						pixVal = colorBuf1[idx+1];
						ASSERT(pixVal != 0);
						unsigned int* iptr = &colorBuf1[idx+1];
						while (!(pixVal & 0x08)) {
							*iptr++ = kColorBlack;
							pixVal <<= 1;
						}
					} else if (colorBuf1[idx+1] == 0) {
						/* trim pixels on right */
						pixVal = colorBuf1[idx];
						ASSERT(pixVal != 0);
						unsigned int* iptr = &colorBuf1[idx];
						while (!(pixVal & 0x01)) {
							*iptr-- = kColorBlack;
							pixVal >>= 1;
						}
					} else {
						/*
						 * The center four pixels have a color determined
						 * like this:
						 *
						 *	orange = 0011
						 *	pink = 1011
						 *
						 * Take last two of orange and first two of pink,
						 * and swap them so first two of pink come first,
						 * e.g. 1011.  The color, which also happens to be
						 * pink, is what we set the middle four pixels to.
						 *
						 * Can't explain it, but that's how it works...
						 * usually.
						 */
						unsigned char mergePix;

						mergePix = colorBuf1[idx] & 0x03;
						mergePix |= colorBuf1[idx+1] & 0x0c;
						ASSERT((mergePix & 0xf0) == 0);
						if (line == 191) {
							WMSG3("idx=0x%02x idx+1=0x%02x merge=0x%02x\n",
								colorBuf1[idx], colorBuf1[idx+1], mergePix);
						}
						colorBuf1[idx-1] = colorBuf1[idx] = colorBuf1[idx+1] =
							colorBuf1[idx+2] = mergePix;
					}
				}
			}
			memcpy(colorBuf, colorBuf1+kMaxLook, sizeof(colorBuf));
#endif
		} /* color algorithm choices */

		/* convert colors to 4-bit bitmap pixels, with line-doubling */
#define SetPix(x, y, twoval) \
		outBuf[((kOutputHeight-1) - (y)) * (kOutputWidth/2) + (x)] = twoval

		unsigned char pix4;
		for (int pix = 0; pix < kPixelsPerLine/2; pix++) {
			int bufPosn = pix * 2;
			ASSERT(colorBuf[bufPosn] < kNumColors);
			ASSERT(colorBuf[bufPosn+1] < kNumColors);

			pix4 = colorBuf[bufPosn] << 4 | colorBuf[bufPosn+1];
			SetPix(pix, line*2, pix4);
			SetPix(pix, line*2+1, pix4);
		}
	} /*for each line*/
#undef SetPix

bail:
	return pDib;
}
