#include "TCtrlLib.h"

NAMESPACE_UPP

RulerCtrl::RulerCtrl()
{
	width = GetStdWidth();
	scale = 1;
	delta = 0;
	cursor = Null;
	raw_delta = Null;
	min_range = Null;
	max_range = Null;
	small_repeat = Null;
	text_repeat = Null;
	text_convert = NULL;
	font = Arial(11);
	is_vert = false;
	is_right = false;
	background = Yellow;
	outer_color = SGray();
	autoselect = false;
}

RulerCtrl::~RulerCtrl() {}

void RulerCtrl::SetZoom(double sc, double dl)
{
	scale = sc;
	delta = dl;
	Layout();
}

void RulerCtrl::MinMax(double min, double max)
{
	min_range = min;
	max_range = max;
	Refresh();
}

void RulerCtrl::PickSmallStep(pick_ Vector<double>& pos, double repeat)
{
	small_step = pos;
	small_repeat = repeat;
	Refresh();
}

void RulerCtrl::SetSmallStep(double delta)
{
	small_step.Clear();
	small_step.Add(0);
	small_repeat = delta;
	Refresh();
}

void RulerCtrl::PickTextStep(pick_ Vector<double>& pos, pick_ Vector<String>& texts, double repeat, const Convert& convert)
{
	text_step = pos;
	text_value = texts;
	text_repeat = repeat;
	text_convert = &convert;
}

void RulerCtrl::SetTextStep(double repeat, const Convert& convert)
{
	text_step.SetCount(1);
	text_step[0] = 0;
	text_value.Clear();
	text_repeat = repeat;
	text_convert = &convert;
}

double RulerCtrl::Snap(double v) const
{
	if(small_step.IsEmpty() || IsNull(small_repeat))
		return v;
	double base = 0;
	if(!IsNull(small_repeat)) {
		base = small_repeat * floor(v / small_repeat);
		v -= base;
	}
	double best_v = small_step[0] + Nvl(small_repeat, 0.0);
	double best = fabs(best_v - v);
	for(int i = 0; i < small_step.GetCount(); i++) {
		double d = fabs(v - small_step[i]);
		if(d < best) {
			best = d;
			best_v = small_step[i];
		}
	}
	return best_v + base;
}

void RulerCtrl::Layout()
{
	raw_delta = Null;
	Refresh();
}

double RulerCtrl::GetRawDelta() const
{
	if(IsNull(raw_delta)) {
		raw_delta = delta;
		if(InFrame()) {
			Point pt = GetScreenView().TopLeft(), par = GetParent() ? GetParent() -> GetScreenView().TopLeft() : pt;
			raw_delta -= (is_vert ? pt.y - par.y : pt.x - par.x);
		}
	}
	return raw_delta;
}

void RulerCtrl::FrameLayout(Rect& rc)
{
	Rect pos = rc;
	int wd = (IsVisible() ? width : 0);
	if(IsVert()) {
		if(IsBottomRight())
			rc.right = max(rc.left, pos.left = rc.right - wd);
		else
			rc.left = min(rc.right, pos.right = pos.left + wd);
	}
	else {
		if(IsBottomRight())
			rc.bottom = max(rc.top, pos.top = pos.bottom - wd);
		else
			rc.top = min(rc.bottom, pos.bottom = pos.top + wd);
	}
	SetFrameRect(pos);
}

void RulerCtrl::FrameAddSize(Size& sz)
{
	if(IsVisible()) {
		if(IsVert())
			sz.cx += width;
		else
			sz.cy += width;
	}
}

void RulerCtrl::RefreshCursor(double cursor)
{
	if(!IsNull(cursor)) {
		double cl = ToClientf(cursor);
		Size size = GetSize();
		int lim = (is_vert ? size.cy : size.cx);
		if(cl >= -ARROW_RAD && cl <= lim + ARROW_RAD) {
			int icl = fround(cl);
			if(is_vert)
				Refresh(0, icl - ARROW_RAD, size.cx, ARROW_DIAM);
			else
				Refresh(icl - ARROW_RAD, 0, ARROW_DIAM, size.cy);
		}
	}
}

void RulerCtrl::SetCursor(double c)
{
	if(c != cursor) {
		RefreshCursor(cursor);
		RefreshCursor(cursor = c);
	}
}

void RulerCtrl::SetData(const Value& v)
{
	SetCursor(v);
}

Value RulerCtrl::GetData() const
{
	return cursor;
}

void RulerCtrl::Paint(Draw& draw)
{
	Size client = GetSize();
	int csize = is_vert ? client.cy : client.cx;
	int cheight = is_vert ? client.cx : client.cy;
	Rect clip = draw.GetClip();
	int cy = font.Info().GetHeight();
	double pos1 = FromClient(0);
	double pos2 = FromClient(csize);
	if(pos1 > pos2)
		Swap(pos1, pos2);
	double lrange = min_range, rrange = max_range;
	if(!IsNull(min_range) && pos1 < min_range) pos1 = min_range;
	if(!IsNull(max_range) && pos2 > max_range) pos2 = max_range;
	int cli1 = 0, cli2 = csize;
	if(scale < 0)
		Swap(lrange, rrange);
	if(!IsNull(lrange))
		cli1 = max<int>(cli1, ToClient(lrange));
	if(!IsNull(rrange))
		cli2 = min<int>(cli2, ToClient(rrange));
	Rect nclip;
	if(is_vert) {
		draw.DrawRect(0, 0, client.cx, cli1, outer_color);
		if(!IsNull(lrange))
			draw.DrawRect(0, cli1++, client.cx, 1, SBlack);
		draw.DrawRect(0, cli2, client.cx, client.cy - cli2, outer_color);
		if(!IsNull(rrange))
			draw.DrawRect(0, --cli2, client.cx, 1, SBlack);
		draw.DrawRect(0, cli1, 1, cli2 - cli1, SGray);
//		draw.DrawRect(0, cli1, 1, cli2 - cli1, SBlack);
		draw.DrawRect(client.cx - 1, cli1, 1, cli2 - cli1, SBlack);
		nclip = RectC(1, cli1, client.cx - 2, cli2 - cli1);
	}
	else {
		draw.DrawRect(0, 0, cli1, client.cy, outer_color);
		if(!IsNull(lrange))
			draw.DrawRect(cli1++, 0, 1, client.cy, SBlack);
		draw.DrawRect(cli2, 0, client.cx - cli2, client.cy, outer_color);
		if(!IsNull(rrange))
			draw.DrawRect(--cli2, 0, 1, client.cy, SBlack);
		draw.DrawRect(cli1, 0, cli2 - cli1, 1, SGray);
		draw.DrawRect(cli1, client.cy - 1, cli2 - cli1, 1, SBlack);
		nclip = RectC(cli1, 1, cli2 - cli1, client.cy - 2);
	}
	draw.DrawRect(nclip, background);
	draw.Clip(nclip);

	enum {
		SMALL_LIMIT = 100,
		TEXT_LIMIT = 50,
		TGAP = 3,
		HGAP = 2,
		VGAP = 0,
		HXGAP = 3,
		VXGAP = 2,
		SMALL_SIZE = 5,
	};

	double rep_count = 1;
	double rep_delta = 0;
	if(!IsNull(small_repeat)) {
		rep_delta = small_repeat * floor(pos1 / small_repeat);
		rep_count = ceil(pos2 / small_repeat) - floor(pos1 / small_repeat);
	}
	if(!small_step.IsEmpty() && rep_count > 0
	&& rep_count * small_step.GetCount() * 3 <= cli2 - cli1) {
		int ix = BinFindIndex(small_step, pos1 - rep_delta);
		int ppos = (is_right ? 0 : cheight - SMALL_SIZE);
		for(int c = fround(rep_count); --c >= 0; ix = 0, rep_delta += small_repeat) {
			int lim = small_step.GetCount();
			if(small_step.Top() > pos2 - rep_delta)
				lim = BinFindIndex(small_step, pos2 - rep_delta);
			for(; ix < lim; ix++) {
				int cli = ToClient(small_step[ix] + rep_delta);
				if(is_vert)
					draw.DrawRect(ppos, cli, SMALL_SIZE, 1, Gray());
				else
					draw.DrawRect(cli, ppos, 1, SMALL_SIZE, Gray());
			}
		}
	}
	rep_count = 1;
	double rep_index = 0;
	if(!IsNull(text_repeat)) {
		rep_index = floor(pos1 / text_repeat);
		rep_count = ceil(pos2 / text_repeat) - floor(pos1 / text_repeat) + 1;
	}
	if(!text_step.IsEmpty() && (!text_value.IsEmpty() || text_convert)
	&& rep_count > 0 && rep_count * text_step.GetCount() <= TEXT_LIMIT) {
		int ix = BinFindIndex(text_step, pos1 - rep_index * text_repeat);
		int ppos = (is_right ? SMALL_SIZE : cheight - cy - SMALL_SIZE);
		for(int c = fround(rep_count); --c >= 0; ix = 0, rep_index++) {
			int lim = text_step.GetCount();
			double dp2 = pos2 - rep_index * text_repeat;
			if(text_step.Top() > dp2) {
				lim = BinFindIndex(text_step, dp2);
				if(lim + 1 < text_step.GetCount() && text_step[lim + 1] - dp2 < dp2 - text_step[lim])
					lim++;
			}
			for(; ix < lim; ix++) {
				double value = text_step[ix] + rep_index * text_repeat;
				int cli = ToClient(value);
				String text;
				if(ix < text_value.GetCount())
					text = text_value[ix];
				else if(text_convert)
					text = StdFormat(text_convert -> Format(value));
				else
					text = StdFormat(value);
				Size tsize = GetTextSize(text, font);
				int half = tsize.cx >> 1;
				if(is_vert) {
					draw.DrawRect(0, cli, ppos, 1, Gray());
//					draw.DrawRect(ppos - VGAP, cli - half - HGAP, tsize.cy + 2 * VGAP, tsize.cx + 2 * HGAP, background);
					draw.DrawText(ppos, cli + half, 900, text, font, Gray());
				}
				else {
					draw.DrawRect(cli, 0, 1, ppos, Gray());
//					draw.DrawRect(cli - half - HGAP, ppos - VGAP, tsize.cx + 2 * HGAP, tsize.cy + 2 * VGAP, background);
					draw.DrawText(cli - half, ppos, text, font, Gray());
				}
			}
		}
	}

	Font labelfont = font().Bold();
	double cl = IsNull(cursor) ? double(Null) : ToClientf(cursor);
	Size lsize = GetTextSize(label_text, labelfont);
	if(is_vert) {
		int tx = (IsBottomRight() ? client.cx - cy : 0);
//		draw.DrawRect(tx - VXGAP, cli2 - lsize.cx - TGAP - HXGAP, lsize.cy + 2 * VXGAP, lsize.cx + 2 * HXGAP, background);
		draw.DrawText(tx, cli2 - TGAP, 900, label_text, labelfont, Black());
		if(cl >= -ARROW_RAD && cl <= client.cy + ARROW_RAD) {
			int icl = fround(cl);
			Image icon = is_right ? CtrlImg::smallleft() : CtrlImg::smallright();
			draw.DrawImage(is_right ? 1 : client.cx - icon.GetWidth() - 1, icl - ARROW_RAD, icon);
		}
	}
	else {
		int tx = (IsBottomRight() ? client.cy - cy : 0);
//		draw.DrawRect(cli1 + TGAP - HXGAP, tx - VXGAP, lsize.cx + 2 * HXGAP, lsize.cy + 2 * VXGAP, background);
		draw.DrawText(cli1 + TGAP, tx, label_text, labelfont, Black());
		if(cl >= -ARROW_RAD && cl <= client.cx + ARROW_RAD) {
			int icl = fround(cl);
			Image icon = is_right ? CtrlImg::smallup() : CtrlImg::smalldown();
			draw.DrawImage(icl - ARROW_RAD, is_right ? 1 : client.cy - icon.GetHeight() - 1, icon);
		}
	}

	draw.End();
}

void RulerCtrl::LeftDown(Point pt, dword keyflags)
{
	double d = FromClient(is_vert ? pt.y : pt.x);
	if(autoselect)
		SetCursor(d);
	WhenSelect(d);
	if(autoselect) {
		WhenAction();
		SetCapture();
	}
	WhenLeftClick();
}

void RulerCtrl::LeftUp(Point pt, dword keyflags)
{
	ReleaseCapture();
}

void RulerCtrl::MouseMove(Point pt, dword keyflags)
{
	if(keyflags & K_MOUSELEFT) {
		double d = FromClient(is_vert ? pt.y : pt.x);
		if(autoselect)
			SetCursor(d);
		WhenSelect(d);
		if(autoselect)
			WhenAction();
	}
}

void RulerCtrl::RightDown(Point pt, dword keyflags)
{
	MenuBar::Execute(WhenBar);
}

int RulerCtrl::GetStdWidth()
{
	return 22;
}

END_UPP_NAMESPACE
