#include "HexView.h"

namespace Upp {

inline int FormatHexDigit(int c) {
	return c < 10 ? c + '0' : c - 10 + 'a';
}

void FormatHex(char *buffer, int64 number, int n) {
	buffer[n] = '\0';
	while(n) {
		buffer[--n] = FormatHexDigit((byte)number & 0x0f);
		number >>= 4;
	}
}

void HexViewInfo::PrintValue(Draw& w, int x, int y, int bytes, bool be)
{
	dword d = 0;
	Size fsz = GetTextSize("X", font);
	for(int i = 0; i < bytes; i++) {
		int b = data[be ? i : bytes - i - 1];
		if(b < 0) {
			w.DrawText(x, y, String('?', 2 * bytes), font, SColorHighlight);
			x += 2 * bytes * fsz.cx;
			w.DrawText(x, y, "=", font);
			x += fsz.cx;
			w.DrawText(x, y, "?", font, Red);
			return;
		}
		d = (d << 8) | (byte) b;
	}
	w.DrawText(x, y, FormatIntHex(d, 2 * bytes), font, SColorHighlight);
	x += 2 * bytes * fsz.cx;
	w.DrawText(x, y, "=", font);
	x += fsz.cx;
	String txt = FormatUnsigned(d);
	w.DrawText(x, y, txt, font, Red);
	x += GetTextSize(txt, font).cx;
	w.DrawText(x, y, "=", font);
	x += fsz.cx;
	int q = d;
	if(bytes == 1)
		q = (int8) d;
	else
	if(bytes == 2)
		q = (int16) d;
	w.DrawText(x, y, FormatInt(q), font, Magenta);
}

void HexViewInfo::Paint(Draw& w)
{
	Size sz = GetSize();
	w.DrawRect(sz, SColorLtFace);
	if(mode < 1)
		return;
	Size fsz = GetTextSize("X", font);
	char h[17];
	FormatHex(h, pos, longmode ? 16 : 8);
	int xx = 0;
	w.DrawText(xx, 0, h, font, SColorHighlight);
	xx += (longmode ? 16 : 8) * fsz.cx;
	w.DrawText(xx, 0, "=", font);
	xx += fsz.cx;
	w.DrawText(xx, 0, Format64(pos), font, Red);
	xx += (longmode ? 22 : 12) * fsz.cx;
	int y = 0;
	int x;
	for(int q = 0; q < mode; q++) {
		x = xx;
		if(q < 1)
			PrintValue(w, x, y, 1, q);
		x += 12 * fsz.cx;
		PrintValue(w, x, y, 2, q);
		x += 18 * fsz.cx;
		PrintValue(w, x, y, 4, q);
		x += 32 * fsz.cx;
		y += fsz.cy;
	}
	wchar wh[40];
	memset(wh, 0, sizeof(wh));
	int i = 0;
	for(i = 0; i < 40; i++) {
		if(data[2 * i] < 0 || data[2 * i + 1] < 0)
			break;
		wh[i] = MAKEWORD(data[2 * i], data[2 * i + 1]);
	}
	w.DrawText(x, 0, wh, font, Cyan, i);
	if(mode < 2)
		return;
	char sh[80];
	memset(sh, 0, sizeof(sh));
	for(i = 0; i < 80; i++) {
		if(data[i] < 0)
			break;
		sh[i] = data[i];
	}
	WString ws = FromUtf8(sh, i);
	w.DrawText(x, fsz.cy, ws, font, Cyan, i);
	String txt;
	String ftxt;
	i = 0;
	for(;;) {
		if(data[i] < 0) {
			if((unsigned)i < sizeof(float))
				ftxt = "?";
			txt = "?";
			break;
		}
		if((unsigned)i >= sizeof(double)) {
			double h;
			memcpy(&h, sh, sizeof(double));
			txt = Sprintf("%.8g", h);
			break;
		}
		if(i == sizeof(float)) {
			float h;
			memcpy(&h, sh, sizeof(float));
			ftxt = Sprintf("%.6g", h);
		}
		sh[i] = data[i];
		i++;
	}
	w.DrawText(0, fsz.cy, txt, font, Red);
	w.DrawText(18 * fsz.cx, fsz.cy, ftxt, font, Red);
}

void HexViewInfo::SetMode(int _mode)
{
	mode = _mode;
	Height(mode * GetTextSize("X", CourierZ(12)).cy + 3);
	Show(mode);
}

HexViewInfo::HexViewInfo()
{
	SetMode(0);
	AddFrame(TopSeparatorFrame());
	AddFrame(RightSeparatorFrame());
	font = CourierZ(12);
}

int HexView::Byte(int64 adr)
{
	return 0;
}

void HexView::Paint(Draw& w)
{
	Size sz = GetSize();
	w.DrawRect(sz, SColorPaper);
	int y = 0;
	int64 adr = sc;
	while(y < sz.cy) {
		char h[17];
		FormatHex(h, adr, IsLongMode() ? 16 : 8);
		w.DrawText(0, y, h, font);
		int x = (IsLongMode() ? 17 : 9) * fsz.cx;
		int tx = x + columns * fcx3;
		for(int q = columns; q--;) {
			if(adr >= total)
				return;
			if(adr == cursor) {
				w.DrawRect(x, y, fsz.cx * 2, fsz.cy, LtCyan);
				w.DrawRect(tx, y, fsz.cx, fsz.cy, LtCyan);
			}
			int b = Byte(adr++);
			if(b < 0) {
				w.DrawText(x, y, "??", font, Brown);
				w.DrawText(tx, y, "?", font, Brown);
			}
			else {
				h[0] = FormatHexDigit((b & 0xf0) >> 4);
				h[1] = FormatHexDigit(b & 0x0f);
				h[2] = '\0';
				w.DrawText(x, y, h, font, SColorText);
				Color color = SColorMark;
				switch(b) {
				case '\a': *h = 'a'; break;
				case '\b': *h = 'b'; break;
				case '\t': *h = 't'; break;
				case '\f': *h = 'f'; break;
				case '\r': *h = 'r'; break;
				case '\n': *h = 'n'; break;
				case '\v': *h = 'v'; break;
				case '\0': *h = '0'; break;
				default:
					if(b >= 32) {
						*h = b;
						color = SColorText;
					}
					else {
						*h = '.';
						color = SColorDisabled;
					}
				}
				h[1] = '\0';
				w.DrawText(tx, y, h, charset, font, color);
			}
			tx += fsz.cx;
			x += fcx3;
		}
		y += fsz.cy;
	}
}

void HexView::MouseWheel(Point, int zdelta, dword)
{
	sb.Wheel(zdelta);
}

void HexView::SetSb()
{
	sbm = 0;
	while((total >> sbm) > (1 << 30))
		sbm++;
	sb.SetTotal(int(total >> sbm) / columns + 2);
	sb.SetPage(int(rows >> sbm));
	sb.Set(int(sc >> sbm) / columns + 1);
}

void HexView::Layout()
{
	Size sz = GetSize();
	columns = fixed ? fixed : max(4, (sz.cx - (IsLongMode() ? 18 : 10) * fsz.cx) / (4 * fsz.cx));
	rows = max(1, sz.cy / fsz.cy);
	bytes = columns * rows;
	SetSb();
}

void HexView::SetTotal(int64 _total)
{
	total = _total;
	Layout();
	SetSb();
	Refresh();
	RefreshInfo();
}

void HexView::SetSc(int64 address)
{
	sc = minmax(address, (int64)0, total);
	SetSb();
	Refresh();
}

void HexView::Scroll()
{
	int64 q = (int64)(int)sb << sbm;
	if(q == 0)
		sc = 0;
	else
		sc = (q - 1) * columns + sc % columns;
	Refresh();
}

void HexView::RefreshInfo()
{
	info.SetPos(cursor, IsLongMode());
	for(int i = 0; i < 80; i++)
		info.Set(i, Byte(cursor + i));
}

void HexView::SetCursor(int64 _cursor)
{
	cursor = _cursor;
	if(cursor > total)
		cursor = total - 1;
	if(cursor < 0)
		cursor = 0;
	int q = int(sc % columns);
	if(cursor >= sc + bytes)
		sc = cursor - bytes + columns;
	if(cursor < sc) {
		sc = cursor;
	}
	if(sc > q)
		sc = (sc - q) / columns * columns + q;
	if(sc >= total)
		sc = total - 1;
	if(sc < 0)
		sc = 0;
	SetSb();
	Refresh();
	RefreshInfo();
}

void HexView::LeftDown(Point p, dword)
{
	int rowi = p.y / fsz.cy;
	int x = (IsLongMode() ? 17 : 9) * fsz.cx;
	int tx = x + columns * fcx3;
	if(p.x >= x && p.x < tx) {
		x = p.x - x;
		int q = x / fcx3;
		if(x - q * fcx3 < 2 * fsz.cx && q < columns) {
			int64 c = sc + rowi * columns + q;
			if(c < total)
				SetCursor(c);
		}
	}
	else
	if(p.x >= tx) {
		int q = (p.x - tx) / fsz.cx;
		if(q >= 0 && q < columns) {
			int64 c = sc + rowi * columns + q;
			if(c < total)
				SetCursor(c);
		}
	}
	SetFocus();
}

bool HexView::Key(dword key, int)
{
	int pg = max(columns, bytes - columns);
	int q = int(sc % columns);
	switch(key) {
	case K_LEFT:
		SetCursor(cursor - 1);
		return true;
	case K_RIGHT:
		SetCursor(cursor + 1);
		return true;
	case K_UP:
		SetCursor(cursor - columns);
		return true;
	case K_DOWN:
		SetCursor(cursor + columns);
		return true;
	case K_PAGEUP:
		SetSc(sc - pg);
		SetCursor(cursor - pg);
		return true;
	case K_PAGEDOWN:
		SetSc(sc + pg);
		SetCursor(cursor + pg);
		return true;
	case K_CTRL_LEFT:
		SetSc(sc - 1);
		break;
	case K_CTRL_RIGHT:
		SetSc(sc + 1);
		break;
	case K_CTRL_UP:
		SetSc(sc - columns);
		break;
	case K_CTRL_DOWN:
		SetSc(sc + columns);
		break;
	case K_HOME:
		SetCursor((cursor - q) / columns * columns + q);
		break;
	case K_END:
		SetCursor((cursor - q) / columns * columns + q + columns - 1);
		break;
	case K_CTRL_HOME:
	case K_CTRL_PAGEUP:
		SetCursor(0);
		break;
	case K_CTRL_END:
	case K_CTRL_PAGEDOWN:
		SetCursor(total - 1);
		break;
	}
	return MenuBar::Scan(WhenBar, key);
}

void HexView::SetColumns(int x)
{
	FixedColumns(x);
}

void HexView::SetCharset(int chr)
{
	Charset(chr);
}

void HexView::StdGoto(const String& s)
{
	CParser p(s);
	int n = 10;
	if(p.Char2('0', 'x') || p.Char('$') || p.Char('#'))
		n = 16;
	if(p.IsNumber(n)) {
		int64 a = p.ReadNumber(n);
		if(a >= 0 && a < total) {
			SetCursor(a);
			SetSc(a);
			return;
		}
	}
	Exclamation("Invalid position!");
}

void HexView::Goto()
{
	if(go.Execute() == IDOK)
		WhenGoto((String)~go.text);
}

void HexView::ColumnsMenu(Bar& bar)
{
	bar.Add("Auto", THISBACK1(SetColumns, 0))
	   .Radio(fixed == 0);
	bar.Add("8", THISBACK1(SetColumns, 8))
	   .Radio(fixed == 8);
	bar.Add("16", THISBACK1(SetColumns, 16))
	   .Radio(fixed == 16);
	bar.Add("32", THISBACK1(SetColumns, 32))
	   .Radio(fixed == 32);
}

void HexView::SetInfo(int m)
{
	info.SetMode(m);
}

void HexView::InfoMenu(Bar& bar)
{
	bar.Add("None", THISBACK1(SetInfo, 0))
	   .Check(info.GetMode() == 0);
	bar.Add("Standard", THISBACK1(SetInfo, 1))
	   .Check(info.GetMode() == 1);
	bar.Add("Extended", THISBACK1(SetInfo, 2))
	   .Check(info.GetMode() == 2);
}

void HexView::CharsetMenu(Bar& bar)
{
	for(int i = 1; i < CharsetCount(); i++)
		bar.Add(CharsetName(i), THISBACK1(SetCharset, i))
		   .Radio(charset == i);
}

void HexView::StdMenu(Bar& bar)
{
	bar.Add("Go to..", THISBACK(Goto))
	   .Key(K_CTRL_G);
	bar.Add("Columns", THISBACK(ColumnsMenu));
	bar.Add("Charset", THISBACK(CharsetMenu));
	bar.Add("Position info", THISBACK(InfoMenu));
}

void HexView::RightDown(Point p, dword w)
{
	LeftDown(p, w);
	MenuBar::Execute(WhenBar);
}

HexView& HexView::SetFont(Font fnt)
{
	font = fnt;
	fsz = GetTextSize("X", font);
	fcx3 = 3 * fsz.cx;
	Layout();
	Refresh();
	SetSb();
	return *this;
}

void  HexView::SerializeSettings(Stream& s)
{
	int version = 0;
	s / version;
	s / fixed;
	s % charset;
	int mode = info.GetMode();
	s / mode;
	info.SetMode(mode);
	go.text.SerializeList(s);
}

HexView::HexView()
{
	SetFont(CourierZ(12));
	BackPaint();
	charset = CHARSET_WIN1252;
	sb <<= THISBACK(Scroll);
	SetFrame(InsetFrame());
	AddFrame(sb);
	cursor = sc = 0;
	total = 0;
	fixed = 0;
	SetSc(0);
	SetCursor(0);
	AddFrame(info);
	info.SetMode(1);
	WhenBar = THISBACK(StdMenu);
	CtrlLayoutOKCancel(go, "Go to");
	WhenGoto = THISBACK(StdGoto);
}

}
