/*
 *  MS - dependent bitmap code
 */

#include <Interviews\bitmap.h>
#include <Interviews\font.h>
#include <Interviews\transformer.h>
#include <Interviews\X11\painterrep.h>
#include <Interviews\X11\worldrep.h>
#include <math.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <dir.h>
#include <fstream.h>

static int BitmapSize (unsigned int w, unsigned int h) {
    return (w+7)/8 * h;
}

int hextoi (char* s) {
    int result;
    sscanf(s, "%x", &result);
    return result;
}

/*
 *  Steps through an X Bitmap File and collects extracted values in an 
 *  array.
 */

int BitmapRep::ParseBitmapFile (const char* filename, char** data) {
    ifstream source;

    char* file;
    if ((file = searchpath(filename)) == NULL) {
	return 0;
    }
    source.open(file, ios::nocreate);
    if (!source) {
	int a;
	a = 1;
    } else {
	char buf[256];
	char* ptr;

	source.getline(buf, 256);
	if ((ptr = strstr(buf, "width")) != NULL) {
	    ptr = strpbrk(ptr, "0123456789");
	    width = atoi(ptr);
	}

	source.getline(buf, 256);
	if ((ptr = strstr(buf, "height")) != NULL) {
	    ptr = strpbrk(ptr, "0123456789");
	    height = atoi(ptr);
	}

	source.getline(buf, 256);
	if ((ptr = strstr(buf, "x_hot")) != NULL) {
	    ptr = strpbrk(ptr, "0123456789");
	    x0 = atoi(ptr);
	    source.getline(buf, 256);
	}

	if ((ptr = strstr(buf, "y_hot")) != NULL) {
	    ptr = strpbrk(ptr, "0123456789");
	    y0 = atoi(ptr);
	    source.getline(buf, 256);
	}

	int size = BitmapSize(width, height);
	char* d = new char[size];
	int index = 0;
	do {
	    ptr = buf;
	    while ((ptr = strstr(ptr, "0x")) != NULL) {
		d[index++] = hextoi(ptr);
		ptr++;
	    }
	    source.getline(buf, 256);
	} while (!source.eof());
	*data = d;
	return 1;
    }
}

int BitmapDataSize (int width, int height) {
    return ((width/16 + 1)*height);
}

/*
 * A data byte of an X Bitmap must be reflected to get the Windows Format.
 */

unsigned BitmapRep::ReflectByte (unsigned value) {
    unsigned sbit, dbit, result = 0;

    for (int i = 0; i < 8; i++) {
	sbit = 1 << i;
	dbit = 128 >> i;
	if (sbit & value) {
	    result += dbit;
	}
    }
    return result;
}

/*
 * Organizes an incoming data array to Windows Format. The width and height 
 * of a Windows Bitmap are always multiples of 16. X organizes his bitmaps 
 * as a continous byte stream. 2 byte units must be created.
 */

void BitmapRep::CreateWinFormat (char* data,  unsigned** newdata) {
    int column, row = 0;
    int j = 0, i = 0;
    boolean high;
    unsigned value;

    unsigned* d = new unsigned[BitmapDataSize(width, height)];
    while (row < height) {
	column = 0;
	high = false;
	while (column < width) {
	    column += 8;
	    value = ReflectByte(data[i]);
	    i++;
	    if (high) {
		d[j] += value << 8;
		high = false;
		j++;
	    } else {
		d[j] = value;
		high = true;
	    }
	}
	if (high) {
	    j++;
	}
	row++;
    }
    *newdata = d;
}

boolean BitmapRep::IsWinFormat (const char* filename) {
    if (strstr(filename, ".bmp")) {
	return true;
    }
    return false;
}

/* 
 * Returns width, height of the bitmap.
 */

void BitmapRep::GetBitmapExt () {
    HDC hMemDC = CreateCompatibleDC(NULL);
    HBITMAP holdBitmap = SelectObject(hMemDC, (HBITMAP)map);
    RECT rect;
    GetClipBox(hMemDC, &rect);
    width = rect.right;
    height = rect.bottom;
    SelectObject(hMemDC, holdBitmap);
    DeleteDC(hMemDC);
}

BitmapRep::BitmapRep (const char* filename) {
    if (IsWinFormat(filename)) {
	map = (void*)LoadBitmap(_world->hinstance(), (LPSTR)filename);
	GetBitmapExt();
    } else {
	char* data;
	unsigned* windata;
	ParseBitmapFile(filename, &data);
	CreateWinFormat(data, &windata);
	map = (void*)CreateBitmap (width, height, 1, 1, (LPSTR)windata);
	delete data;
	delete windata;
	data = nil;
    }
    data = nil;
}

BitmapRep::BitmapRep (
    char* d, unsigned int w, unsigned int h, int x, int y
) {
    unsigned* newd;
    width = w; height = h;
    x0 = x; y0 = y;

    CreateWinFormat(d, &newd);
    map = (void*)CreateBitmap (w, h, 1, 1, (LPSTR)newd);
    delete newd;
    data = nil;
}

BitmapRep::BitmapRep (Font* f, int c) {
    HDC hMemDC = CreateCompatibleDC(NULL);
    if (!f->Valid()) {
	return;
    }
    HFONT holdFont = SelectObject(hMemDC, (HFONT)f->Id());
    width = GetTextExtent(hMemDC, (char*)&c, 1);
    height = f->Height();
    x0 = 0; y0 = 0;
    map = (void*)CreateBitmap (width, height, 1, 1, NULL);
    HBITMAP holdBitmap = SelectObject(hMemDC, (HBITMAP)map);
    TextOut(hMemDC, 0, 0, (LPSTR)&c, 1);
    SelectObject(hMemDC, holdFont);
    SelectObject(hMemDC, holdBitmap);
    DeleteDC(hMemDC);
    data = nil;
}

BitmapRep::BitmapRep (BitmapRep* b, BitTx t) {
    switch (t) {
    case NoTx: case FlipH: case FlipV: case Rot180: case Inv:
	width = b->width; height = b->height; break;
    case Rot90: case Rot270:
	width = b->height; height = b->width; break;
    }
    x0 = b->x0;
    y0 = b->y0;

    map = (void*)CreateBitmap(width, height, 1, 1, NULL);

    for (int x = 0; x < width; ++x) {
	for (int y = 0; y < height; ++y) {
	    boolean bit;
	    switch(t) {
	    case NoTx:   bit = b->GetBit(x, y); break;
	    case FlipH:  bit = b->GetBit(width-x-1, y); break;
	    case FlipV:  bit = b->GetBit(x, height-y-1); break;
	    case Rot90:  bit = b->GetBit(height-y-1, x); break;
	    case Rot180: bit = b->GetBit(width-x-1, height-y-1); break;
	    case Rot270: bit = b->GetBit(y, width-x-1); break;
	    case Inv:    bit = !b->GetBit(x, y); break;
	    }
	    PutBit(x, y, bit);
	}
    }
}

/*----------------------------------------------------------------------*/

static void DrawSourceTransformedImage (
    HBITMAP s, int sx0, int sy0,
    HBITMAP m, int mx0, int my0,
    HWND d, unsigned int height, int dx0, int dy0,
    boolean stencil, COLORREF fg, COLORREF bg,
    HDC hDC, Transformer* matrix,
    int xmin, int ymin, int xmax, int ymax,
    int s_width, int s_height,
    int m_width, int m_height
) {
    HDC hmDC = CreateCompatibleDC(hDC);
    HDC hsDC = CreateCompatibleDC(hDC);
    HBITMAP holdBitmap_s = SelectObject(hsDC, s);
    HBITMAP holdBitmap_m = SelectObject(hmDC, m);

    COLORREF lastdrawnpixel = fg;
    for (int xx = xmin; xx <= xmax; ++xx) {
	float lx, ly;
	float rx, ry;
	float tx, ty;
	matrix->Transform(float(xx), float(ymin), lx, ly);
	matrix->Transform(float(xx + 1), float(ymin), rx, ry);
	matrix->Transform(float(xx), float(ymax+1), tx, ty);
	float dx = (tx - lx) / float(ymax - ymin + 1);
	float dy = (ty - ly) / float(ymax - ymin + 1);
	int ilx = 0, ily = 0;
	int irx = 0, iry = 0;
	boolean lastmask = false, mask;
	COLORREF lastpixel = fg, pixel, source;
	for (int yy = ymin; yy <= ymax+1; ++yy) {
	    mask = (
		(yy <= ymax)
		&& (m == nil || GetPixel(hmDC, xx-mx0, m_height-1-(yy-my0) != -1))
	    );
	    if (
		yy<sy0 || yy>=sy0+s_height || xx<sx0 || xx>=sx0+s_width
	    ) {
		source = bg;
	    } else {
		DWORD rgb = GetPixel(hsDC, xx-sx0, s_height-1-(yy-sy0));
		source = PALETTERGB(GetRValue(rgb), GetGValue(rgb), GetBValue(rgb));
	    }
	    if (stencil) {
		pixel = (source != 0x2000000) ? fg : bg;
	    } else {
		pixel = source;
	    }
	    if (mask != lastmask || lastmask && pixel != lastpixel) {
		int iilx = round(lx), iily = round(ly);
		int iirx = round(rx), iiry = round(ry);
		if (lastmask) {
		    if (lastpixel != lastdrawnpixel) {
			SetTextColor(hDC, lastpixel);
			lastdrawnpixel = lastpixel;
		    }
		    if (
			(ilx==iilx || ily==iily) && (irx==ilx || iry==ily)
		    ) {
			HBRUSH hBrush = CreateSolidBrush(lastpixel);
			RECT rect;
			rect.left   = min(ilx, iirx) - dx0;
			rect.top    = height - (max(ily, iiry) - dy0);
			rect.right  = rect.left + abs(ilx - iirx);
			rect.bottom = rect.top + abs(ily - iiry);
			::FillRect(hDC, &rect, hBrush);
			DeleteObject(hBrush);
		    } else {
			POINT v[4];
			v[0].x = ilx-dx0;  v[0].y = height - (ily-dy0);
			v[1].x = iilx-dx0; v[1].y = height - (iily-dy0);
			v[2].x = iirx-dx0; v[2].y = height - (iiry-dy0);
			v[3].x = irx-dx0;  v[3].y = height - (iry-dy0);
			HBRUSH hBrush    = CreateSolidBrush(lastpixel);
			HPEN   hPen      = GetStockObject(NULL_PEN);
			HBRUSH holdBrush = SelectObject(hDC, hBrush);
			HPEN   holdPen   = SelectObject(hDC, hPen);
			::Polygon(hDC, v, 4);
			SelectObject(hDC, holdPen);
			SelectObject(hDC, holdBrush);
			DeleteObject(hBrush);
		    }
		}
		ilx = iilx; ily = iily;
		irx = iirx; iry = iiry;
		lastpixel = pixel;
		lastmask = mask;
	    }
	    lx += dx; ly += dy;
	    rx += dx; ry += dy;
	}
    }
    SetTextColor(hDC, fg);

    SelectObject(hsDC, holdBitmap_s);
    SelectObject(hmDC, holdBitmap_m);
    DeleteDC(hsDC);
    DeleteDC(hmDC);
}

static void DrawDestinationTransformedImage (
    HBITMAP s, int sx0, int sy0,
    HBITMAP m, int mx0, int my0,
    HWND d, unsigned int height, int dx0, int dy0,
    boolean stencil, COLORREF fg, COLORREF bg,
    HDC hDC, Transformer* matrix,
    int xmin, int ymin, int xmax, int ymax,
    int s_width, int s_height,
    int m_width, int m_height
) {
    HBRUSH holdBrush;
    HDC hmDC = CreateCompatibleDC(hDC);
    HDC hsDC = CreateCompatibleDC(hDC);
    HBITMAP holdBitmap_m = SelectObject(hmDC, m);
    HBITMAP holdBitmap_s = SelectObject(hsDC, s);


    Transformer t(matrix);
    t.Invert();

    COLORREF lastdrawnpixel = fg;
    for (Coord xx = xmin; xx <= xmax; ++xx) {
	float fx, fy;
	float tx, ty;
	t.Transform(float(xx) + 0.5, float(ymin) + 0.5, fx, fy);
	t.Transform(float(xx) + 0.5, float(ymax) + 1.5, tx, ty);
	float dx = (tx - fx) / float(ymax - ymin + 1);
	float dy = (ty - fy) / float(ymax - ymin + 1);
	Coord lasty = ymin;
	boolean lastmask = false, mask;
	COLORREF lastpixel = fg, pixel, source;
	for (Coord yy = ymin; yy <= ymax+1; ++yy) {
	    int ix = round(fx - 0.5), iy = round(fy - 0.5);
	    boolean insource = (
	       ix >= sx0 && ix < sx0 + s_width
	       && iy >= sy0 && iy < sy0 + s_height
	    );
	    boolean inmask = (
		m != nil && ix >= mx0 && ix < mx0 + m_width
		&& iy >= my0 && iy < my0 + m_height
	    );
	    if (yy <= ymax) {
		if (m == nil) {
		    mask = insource;
		} else if (inmask) {
		    mask = GetPixel(hmDC, ix-mx0, m_height-1-(iy-my0)) != -1;
		} else {
		    mask = false;
		}
	    } else {
		mask = false;
	    }
	    if (insource) {
		DWORD rgb = GetPixel(hsDC, ix-sx0, s_height-1-(iy-sy0));
		source = PALETTERGB(GetRValue(rgb), GetGValue(rgb), GetBValue(rgb));
	    } else {
		source = bg;
	    }
	    if (stencil) {
		pixel = (source != 0x2000000) ? fg : bg;
	    } else {
		pixel = source;
	    }
	    if (mask != lastmask || lastmask && pixel != lastpixel) {
		if (lastmask) {
		    if (lastpixel != lastdrawnpixel) {
			SetTextColor(hDC, lastpixel);
			lastdrawnpixel = lastpixel;
		    }
		    HBRUSH hBrush = CreateSolidBrush(lastpixel);
		    RECT rect;
		    rect.left = xx - dx0;
		    rect.top = height -1 -(yy -dy0);
		    rect.right = rect.left + 1;
		    rect.bottom = rect.top + (yy - lasty);
		    FillRect(hDC, &rect, hBrush);
		    DeleteObject(hBrush);
		}
		lastmask = mask;
		lastpixel = pixel;
		lasty = yy;
	    }
	    fx += dx;
	    fy += dy;
	}
    }
    SetTextColor(hDC, fg);

    SelectObject(hsDC, holdBitmap_s);
    SelectObject(hmDC, holdBitmap_m);
    DeleteDC(hsDC);
    DeleteDC(hmDC);
}

void DrawTransformedImage (
    HBITMAP s, int sx0, int sy0,
    HBITMAP m, int mx0, int my0,
    HWND d, unsigned int height, int dx0, int dy0,
    boolean stencil, COLORREF fg, COLORREF bg,
    HDC hDC, Transformer* matrix,
    int s_width, int s_height,
    int m_width, int m_height
) {
    int x1 = (m != nil) ? mx0 : sx0;
    int y1 = (m != nil) ? my0 : sy0;
    int x2 = (m != nil) ? mx0 : sx0;
    int y2 = (m != nil) ? my0 + m_height : sy0 + s_height;
    int x3 = (m != nil) ? mx0 + m_width : sx0 + s_width;
    int y3 = (m != nil) ? my0 + m_height : sy0 + s_height;
    int x4 = (m != nil) ? mx0 + m_width : sx0 + s_width;
    int y4 = (m != nil) ? my0 : sy0;

    int sxmin = min(x1, min(x2, min(x3, x4)));
    int sxmax = max(x1, max(x2, max(x3, x4))) - 1;
    int symin = min(y1, min(y2, min(y3, y4)));
    int symax = max(y1, max(y2, max(y3, y4))) - 1;

    matrix->Transform(x1, y1);
    matrix->Transform(x2, y2);
    matrix->Transform(x3, y3);
    matrix->Transform(x4, y4);

    int dxmin = min(x1,min(x2,min(x3,x4)));
    int dxmax = max(x1,max(x2,max(x3,x4))) - 1;
    int dymin = min(y1,min(y2,min(y3,y4)));
    int dymax = max(y1,max(y2,max(y3,y4))) - 1;

    int swidth = sxmax - sxmin + 1;
    int sheight = symax - symin + 1;
    int dwidth = dxmax - dxmin + 1;
    int dheight = dymax - dymin + 1;

    boolean rect = (x1==x2 || y1==y2) && (x1==x4 || y1==y4);
    boolean alwaysdest = dwidth < 2 * swidth;
    boolean alwayssource = dwidth * dheight > 3 * swidth * sheight;
    boolean dest;

    dest = alwaysdest || (!alwayssource && !rect);

    if (dest) {
	if (dheight > 0) {
	    DrawDestinationTransformedImage(
		s, sx0, sy0, m, mx0, my0, d, height, dx0, dy0,
		stencil, fg, bg, hDC, matrix,
		dxmin, dymin, dxmax, dymax,
		s_width, s_height,
		m_width, m_height
	    );
	}
    } else {
	if (sheight > 0) {
	    DrawSourceTransformedImage(
		s, sx0, sy0, m, mx0, my0, d, height, dx0, dy0,
		stencil, fg, bg, hDC, matrix,
		sxmin, symin, sxmax, symax,
		s_width, s_height,
		m_width, m_height
	    );
	}
    }
}

BitmapRep::BitmapRep (BitmapRep* b, Transformer* matrix) {
    Transformer t(matrix);

    Coord x1 = - b->x0;
    Coord y1 = - b->y0;
    Coord x2 = - b->x0;
    Coord y2 = b->height - b->y0;
    Coord x3 = b->width - b->x0;
    Coord y3 = b->height - b->y0;
    Coord x4 = b->width - b->x0;
    Coord y4 = - b->y0;

    t.Transform(x1, y1);
    t.Transform(x2, y2);
    t.Transform(x3, y3);
    t.Transform(x4, y4);

    Coord xmax = max(x1,max(x2,max(x3,x4))) - 1;
    Coord xmin = min(x1,min(x2,min(x3,x4)));
    Coord ymax = max(y1,max(y2,max(y3,y4))) - 1;
    Coord ymin = min(y1,min(y2,min(y3,y4)));

    int w = xmax - xmin + 1;
    int h = ymax - ymin + 1;
    width = (w <= 0) ? 1 : w;
    height = (h <= 0) ? 1 : h;
    x0 = -xmin;
    y0 = -ymin;

    HDC hMemDC = CreateCompatibleDC(NULL);
    map = (void*)CreateBitmap(width, height, 1, 1, NULL);
    HBITMAP holdBitmap = SelectObject(hMemDC, (HBITMAP)map);
    PatBlt(hMemDC, 0, 0, width, height, WHITENESS);
    DrawTransformedImage(
	(HBITMAP)b->GetData(), -b->x0, -b->y0,
	(HBITMAP)b->GetData(), -b->x0, -b->y0,
	(HBITMAP)map, height, -x0, -y0,
	true, 1, 0, hMemDC, &t,
	b->width, b->height,
	b->width, b->height
    );
    SelectObject(hMemDC, holdBitmap);
    DeleteDC (hMemDC);
    data = nil;
}

BitmapRep::~BitmapRep () {
    Touch();
}

void BitmapRep::Touch () {
/*    if (map != nil) {
	DeleteObject((HBITMAP)map);
	map = nil;
    }
*/
}

void BitmapRep::PutBit (int x, int y, boolean bit) {
    HDC hMemDC = CreateCompatibleDC(NULL);
    HBITMAP holdBitmap = SelectObject(hMemDC, (HBITMAP)map);
    if (bit) {
	SetPixel(hMemDC, x, y, RGB(255, 255, 255));
    } else {
	SetPixel(hMemDC, x, y, RGB(0, 0, 0));
    }
    SelectObject(hMemDC, holdBitmap);
    DeleteDC(hMemDC);
}

boolean BitmapRep::GetBit (int x, int y) {
    boolean result;

    HDC hMemDC = CreateCompatibleDC(NULL);
    HBITMAP holdBitmap = SelectObject(hMemDC, (HBITMAP)map);
    if (GetPixel(hMemDC, x, y) != 0xFFFFFF) {
	result = false;
    } else {
	result = true;
    }
    SelectObject(hMemDC, holdBitmap);
    DeleteDC(hMemDC);
    return result;
}

void* BitmapRep::GetData () {
    return map;
}

void* BitmapRep::GetMap () {
    return map;
}

