#include <ide/QuickTabs/QuickTabs.h>
#include <ide/Common/Common.h>

#define IMAGECLASS Img
#define IMAGEFILE <ide/QuickTabs/QuickTabs.iml>
#include <Draw/iml_source.h>

static String ParseGroup(const String &s)
{
	if(!s.IsEmpty())
	{
		if(s[0] == '/' || s[0] == '\\')
			return s.Mid(1);
		else if(s[1] == ':')
			return s.Mid(3);
	}
	return Null;
}

TabScrollBar::TabScrollBar()
{
	total = 0;
	pos = 0;
	ps = 0;
	start_pos = 0;
	new_pos = 0;
	old_pos = 0;
	ready = false;
}

void TabScrollBar::UpdatePos(bool update)
{
	sz = GetSize();
	if(total <= 0 || sz.cx <= 0)
		cs = ics = 0;
	else
	{
		cs = sz.cx / ((double) total + 0.5);
		ics = total / ((double) sz.cx);
	}
	size = sz.cx * cs;
	if(update)
		pos = new_pos - start_pos;
	if(pos < 0)
		pos = 0;
	else if(pos + size > sz.cx)
		pos = sz.cx - size;

	ps = total > sz.cx ? pos * ics : 0;
	WhenScroll();
}

void TabScrollBar::Paint(Draw &w)
{
	if(!ready)
	{
		UpdatePos();
		ready = true;
	}
	Size sz = GetSize();
	w.DrawRect(sz, White);
	if(total > sz.cx)
		w.DrawRect(ffloor(pos), 1, fceil(size), 2, Blue);
	else
		w.DrawRect(0, 1, sz.cx, 2, Blue);
}

void TabScrollBar::Layout()
{
	UpdatePos(false);
}

void TabScrollBar::LeftDown(Point p, dword keyflags)
{
	SetCapture();
	old_pos = new_pos = p.x;
	if(p.x < pos || p.x > pos + size)
		start_pos = size / 2;
	else
		start_pos = tabs(p.x - pos);
	UpdatePos();
	Refresh();
}

void TabScrollBar::LeftUp(Point p, dword keyflags)
{
	ReleaseCapture();
	old_pos = p.x;
}

void TabScrollBar::MouseMove(Point p, dword keyflags)
{
	if(!HasCapture())
		return;

	new_pos = p.x;
	UpdatePos();
	Refresh();
}

void TabScrollBar::MouseWheel(Point p, int zdelta, dword keyflags)
{
	AddPos(-zdelta / 4, true);
}

int TabScrollBar::GetPos() const
{
	return ffloor(ps);
}

void TabScrollBar::SetPos(int p, bool dontscale)
{
	pos = total > 0 ? dontscale ? p : iscale(p, sz.cx, total) : 0;
	UpdatePos(false);
	Refresh();
}

void TabScrollBar::AddPos(int p, bool dontscale)
{
	pos += total > 0 ? dontscale ? p : iscale(p, sz.cx, total) : 0;
	UpdatePos(false);
	Refresh();
}

int TabScrollBar::GetTotal() const
{
	return total;
}

void TabScrollBar::SetTotal(int t)
{
	bool upd = total < t;
	total = t;
	UpdatePos(upd);
	Refresh();
}

void TabScrollBar::AddTotal(int t)
{
	total += t;
	UpdatePos();
	Refresh();
}

void TabScrollBar::GoEnd()
{
	pos = total;
	UpdatePos(false);
	Refresh();
}

void TabScrollBar::GoBegin()
{
	pos = 0;
	UpdatePos(false);
	Refresh();
}

void TabScrollBar::Set(const TabScrollBar& t)
{
	total = t.total;
	pos = t.pos;
	ps = t.ps;
	Refresh();
}

QuickTabs::QuickTabs()
{
	AddFrame(sc.Height(QT_SBHEIGHT));
	sc.WhenScroll = THISBACK(Scroll);
	highlight = -1;
	active = -1;
	cross = -1;
	target = -1;
	crosses = true;
	file_icons = false;
	grouping = true;
	isctrl = false;
	isdrag = false;
	id = -1;

	Group &g = groups.Add();
	g.name = "All";
	group = 0;

	SetStyle(TabCtrl::StyleDefault());
	BackPaint();
}

void QuickTabs::CloseAll()
{
	for(int i = tabs.GetCount() - 1; i >= 0; i--)
		if(i != active)
			tabs.Remove(i);

	sc.SetTotal(tabs[0].cx);
	MakeGroups();
	Repos();
	SetCursor(0);
}

int QuickTabs::GetNextId()
{
	return ++id;
}

void QuickTabs::Menu(Bar& bar)
{
	bar.Add(tabs.GetCount() > 1, "Close", THISBACK1(Close, highlight));
	bar.Separator();
	int cnt = groups.GetCount();
	for(int i = 0; i < cnt; i++)
	{
		String name = Format("%s (%d)", groups[i].name, groups[i].count);
		Bar::Item &it = i > 0 ? bar.Add(name, THISBACK1(GroupMenu, i))
		                      : bar.Add(name, THISBACK1(DoGrouping, i));
		if(i == group)
			it.Image(Img::CHK);
		if(i == 0 && cnt > 1)
			bar.Separator();
	}
	bar.Separator();

	bar.Add("Close all tabs except current", THISBACK(CloseAll));
	bar.Add("Close non-project tabs", WhenCloseRest);
}

void QuickTabs::GroupMenu(Bar &bar, int n)
{
	bar.Add("Set active", THISBACK1(DoGrouping, n));
	bar.Add("Close", THISBACK1(DoCloseGroup, n));
}

bool Tab::HasMouse(const Point& p)
{
	return visible && p.x >= x && p.x < x + cx;
}

bool Tab::HasMouseCross(const Point& p, int h)
{
	if(!visible)
		return false;

	Size isz = Img::CR0().GetSize();
	int iy = (h - isz.cy) / 2;
	int ix = x + cx - isz.cx - QT_MARGIN;

	return p.x >= ix && p.x < ix + isz.cx &&
	       p.y >= iy && p.y < iy + isz.cy;
}


int QuickTabs::FindGroup(const String& g)
{
	for(int i = 0; i < groups.GetCount(); i++)
		if(groups[i].path == g)
			return i;
	return -1;
}

void QuickTabs::MakeGroups()
{
	groups[0].count = tabs.GetCount();
	groups[0].first = 0;
	groups[0].last = tabs.GetCount() - 1;

	for(int i = 1; i < groups.GetCount(); i++)
	{
		groups[i].count = 0;
		groups[i].first = 1000000;
		groups[i].last = 0;
	}

	for(int i = 0; i < tabs.GetCount(); i++)
	{
		Tab &tab = tabs[i];
		String s = ParseGroup(tab.group);
		if(IsNull(s))
			continue;
		int n = FindGroup(tab.group);
		if(n < 0)
		{
			Group &g = groups.Add();
			g.count = 1;
			g.first = 1000000;
			g.last = 0;
			g.name = s;
			g.path = tab.group;
			g.active = tab.id;
			n = groups.GetCount() - 1;
		}
		else
		{
			groups[n].count++;
			groups[n].last = i;
		}

		if(i < groups[n].first)
			groups[n].first = i;
		if(i > groups[n].last)
			groups[n].last = i;
	}


	int removed = 0;
	for(int i = 1; i < groups.GetCount(); i++)
		if(groups[i].count == 0)
		{
			groups.Remove(i - removed);
			removed++;
		}
	if(group > groups.GetCount() - 1 && group > 0)
		group--;

}

void QuickTabs::DoGrouping(int n)
{
	group = n;
	Repos();
	SetCursor(-1);
}

void QuickTabs::DoCloseGroup(int n)
{
	int cnt = groups.GetCount();
	if(cnt <= 0)
		return;

	if(cnt == n)
		--group;

	String group = groups[n].path;

	for(int i = tabs.GetCount() - 1; i >= 0; i--)
	{
		String s = ParseGroup(tabs[i].group);
		if(IsNull(s))
			continue;
		if(group == tabs[i].group && tabs.GetCount() > 1)
			tabs.Remove(i);
	}
	if(cnt > 1)
		groups.Remove(n);
	MakeGroups();
	Repos();
	SetCursor(-1);
}

void QuickTabs::DrawTab(Draw &w, int i)
{
	Size sz = GetSize();

	Size isz = Img::CR0().GetSize();
	int cnt = tabs.GetCount();

	Tab &t = tabs[i];

	int lx = i > 0 ? style->extendleft : 0;

	int x = t.x - sc.GetPos() + style->margin - lx;

	bool ac = i == active;
	bool hl = i == highlight;

	int ndx = !IsEnabled() ? CTRL_DISABLED :
		       ac ? CTRL_PRESSED :
		       hl ? CTRL_HOT : CTRL_NORMAL;

	const Value& sv = (cnt == 1 ? style->both : i == 0 ? style->first : i == cnt - 1 ? style->last : style->normal)[ndx];

	if(ac)
		ChPaint(w, x - style->sel.left,
		           0,
		           t.cx + style->sel.right + style->sel.left + lx,
		           t.cy + style->sel.bottom,
		           sv);
	else
		ChPaint(w, x,
		           style->sel.top,
		           t.cx + lx,
		           t.cy - style->sel.top,
		           sv);

	int h = sz.cy + (ac ? 0 : style->sel.top);

	if(crosses && tabs.GetCount() > 1)
		w.DrawImage(x + t.cx - isz.cx - QT_MARGIN,
		            (h - isz.cy) / 2,
		            (ac || hl) ? (cross == i ? Img::CR2 : ac ? Img::CR1 : Img::CR0) : Img::CR0);

	if(file_icons)
	{
		//Image icon = Img::ARH;
		Image icon = IdeFileImage(t.name);
		w.DrawImage(x + QT_MARGIN, (h - icon.GetSize().cx) / 2, icon);
	}

	String fn = GetFileName(t.name);
	int ty = (h - t.tsz.cy) / 2 + 1;
	DrawFileName(w, x + QT_MARGIN + (QT_FILEICON + QT_SPACEICON) * int(file_icons), ty,
	             t.tsz.cx, t.tsz.cy, fn.ToWString(),
	             false, StdFont(), Black, LtBlue, Null, Null, false);

}

void QuickTabs::Paint(Draw &w)
{
	Size sz = GetSize();
	w.DrawRect(sz, SColorFace());
	w.DrawRect(0, sz.cy - 1, sz.cx, 1, Color(128, 128, 128));

	for(int i = 0; i < tabs.GetCount(); i++)
		if(tabs[i].visible && i != active)
			DrawTab(w, i);

	if(active >= 0)
		DrawTab(w, active);


	if(target >= 0)
	{
		int last = GetLast();
		int first = GetFirst();

		if(target != active && target != active + 1)
		{
			int x = (target == last + 1 ? tabs[last].Right() : tabs[target].x)
			        - sc.GetPos() - (target <= first ? 1 : 2)
			        + style->margin - (target > 0 ? style->extendleft : 0);

			int y = style->sel.top;
			int cy = sz.cy - y;
			Color c(255, 0, 0);
			w.DrawRect(x + 1, y, 2, cy, c);
			w.DrawRect(x, y, 4, 1, c);
			w.DrawRect(x, y + cy - 1, 4, 1, c);
		}
	}
}

void QuickTabs::Scroll()
{
	Refresh();
}

int QuickTabs::GetWidth(int n)
{
	Tab &t = tabs[n];
	t.tsz = GetTextSize(GetFileName(t.name), StdFont());
	return QT_MARGIN * 2 + t.tsz.cx + (QT_SPACE + Img::CR0().GetSize().cx) * crosses
	                                + (QT_FILEICON + QT_SPACEICON) * file_icons;
}

void QuickTabs::Add(const char *name, bool make_active)
{
	Tab &t = tabs.Add();

//	int cnt = tabs.GetCount() - 1; Mirek: unused
	t.name = name;
	t.group = GetFileFolder(name);
	t.id = GetNextId();
	MakeGroups();
	Repos();
	SetCursor(tabs.GetCount() - 1);
}

int QuickTabs::GetWidth()
{
//	int j = GetLast(); Mirek: unused
	return tabs[GetLast()].Right() + style->margin * 2;
}

int QuickTabs::GetHeight()
{
	return style->tabheight + style->sel.top /*+ style->sel.bottom */ + QT_SBHEIGHT + QT_SBSEPARATOR;
}

void QuickTabs::Repos()
{
	if(!tabs.GetCount())
		return;

	String g = GetGroupName();

	int j = 0;
	bool first = true;
	for(int i = 0; i < tabs.GetCount(); i++)
	{
		bool v = IsNull(g) ? true : g == tabs[i].group;
		if(v)
		{
			tabs[i].x = first ? 0 : tabs[j].Right();
			j = i;
			first = false;
		}
		else
			tabs[i].x = 0;

		tabs[i].visible = v;
		tabs[i].y = 0;
		tabs[i].cx = GetWidth(i);
		tabs[i].cy = style->tabheight + style->sel.top;
	}
	sc.SetTotal(GetWidth());
}

int QuickTabs::Find(int id)
{
	for(int i = 0; i < tabs.GetCount(); i++)
		if(tabs[i].id == id)
			return i;
	return -1;
}

int QuickTabs::GetNext(int n)
{
	for(int i = n + 1; i < tabs.GetCount(); i++)
		if(tabs[i].visible)
			return i;
	return -1;
}

int QuickTabs::GetPrev(int n)
{
	for(int i = n - 1; i >= 0; i--)
		if(tabs[i].visible)
			return i;
	return -1;
}

void QuickTabs::Clear()
{
	tabs.Clear();
	Refresh();
}

QuickTabs& QuickTabs::FileIcons(bool b)
{
	file_icons = b;
	Repos();
	return *this;
}

QuickTabs& QuickTabs::Crosses(bool b)
{
	crosses = b;
	Repos();
	return *this;
}

QuickTabs& QuickTabs::Grouping(bool b)
{
	grouping = b;
	return *this;
}

void QuickTabs::LeftDown(Point p, dword keyflags)
{
	if(keyflags & K_SHIFT)
	{
		highlight = -1;
		Refresh();
		SetCapture();
		oldp = p;
		return;
	}

	isctrl = keyflags & K_CTRL;
	if(isctrl)
		return;

	if(cross != -1)
		Close(cross);
	else if(highlight >= 0)
		SetCursor(highlight);
}

void QuickTabs::LeftUp(Point p, dword keyflags)
{
	ReleaseCapture();
}

void QuickTabs::RightDown(Point p, dword keyflags)
{
	MenuBar::Execute(THISBACK(Menu), GetMousePos());
}

void QuickTabs::MiddleDown(Point p, dword keyflags)
{
	Close(highlight);
}

void QuickTabs::MiddleUp(Point p, dword keyflags)
{
}

int QuickTabs::GetTargetTab(Point p)
{
	p.x += sc.GetPos();

	int f = GetFirst();
	int l = GetLast();

	if(tabs[f].visible && p.x < tabs[f].x + tabs[f].cx / 2)
		return f;

	for(int i = l; i >= f; i--)
		if(tabs[i].visible && p.x >= tabs[i].x + tabs[i].cx / 2)
			return i == l ? i + 1 : GetNext(i);
	return -1;
}

void QuickTabs::MouseWheel(Point p, int zdelta, dword keyflags)
{
	sc.AddPos(-zdelta / 4, true);
	MouseMove(p, 0);
}

void QuickTabs::MouseMove(Point p, dword keyflags)
{
	if(HasCapture())
	{
		sc.AddPos(p.x - oldp.x, true);
		oldp = p;
		return;
	}

	p.x += sc.GetPos() - style->margin;
	Size sz = GetSize();

	int h = highlight;
	bool iscross = false;
	bool istab = false;
	for(int i = 0; i < tabs.GetCount(); i++)
	{
		Point np(p.x + (i > 0 ? style->extendleft : 0), p.y);
		if(tabs[i].HasMouse(np))
		{
			istab = true;
			int h = sz.cy + (active == i ? 0 : style->sel.top);
			iscross = crosses ? tabs[i].HasMouseCross(np, h) : false;
			if(highlight != i || (iscross && cross != i))
			{
				cross = iscross ? i : -1;
				highlight = i;
				Refresh();
				return;
			}
		}
	}

	if(!istab && h >= 0)
	{
		highlight = -1;
		Refresh();
	}

	if(!iscross && cross >= 0)
	{
		cross = -1;
		Refresh();
	}
}

void QuickTabs::MouseLeave()
{
	if(isdrag)
		return;

	highlight = -1;
	cross = -1;
	Refresh();
}

void QuickTabs::FrameLayout(Rect &r)
{
	LayoutFrameTop(r, this, GetHeight());
	r.top += 2;
	r.left += 2;
	r.right -= 2;
	r.bottom -= 2;
}

void QuickTabs::FrameAddSize(Size& sz)
{
	sz += 4;
	sz.cy += GetHeight();
}

void QuickTabs::FramePaint(Draw& w, const Rect& rr)
{
	Rect r = rr;
	r.top += GetHeight();
	ViewFrame().FramePaint(w, r);
}

void QuickTabs::DragAndDrop(Point p, PasteClip& d)
{
	int c = GetTargetTab(p);
	int tab = isctrl ? highlight : active;

	bool sametab = c == tab || c == tab + 1;
	bool internal = AcceptInternal<QuickTabs>(d, "tabs");

	if(!sametab && d.IsAccepted())
	{
		if(internal)
		{
			int id = tabs[active].id;
			Tab t = tabs[tab];
			tabs.Insert(c, t);
			tabs.Remove(tab + int(c < tab));
			active = Find(id);
			isdrag = false;
			MakeGroups();
			Repos();
			return;
		}
		else if(d.IsPaste())
		{
			CancelMode();
			return;
		}
	}
	else
	{
		//d.Reject();
		//unfortunately after Reject DragLeave stops working until d is accepted
	}

	if(c != target)
	{
		target = c;
		Refresh();
	}
}

void QuickTabs::CancelMode()
{
	isdrag = false;
	target = -1;
	Refresh();
}

void QuickTabs::LeftDrag(Point p, dword keyflags)
{
	if(keyflags & K_SHIFT)
		return;
	if(highlight < 0)
		return;

	isdrag = true;
	DoDragAndDrop(InternalClip(*this, "tabs"));
}

void QuickTabs::DragEnter()
{
}

void QuickTabs::DragLeave()
{
	target = -1;
	Refresh();
}

void QuickTabs::DragRepeat(Point p)
{
	if(target >= 0)
	{
		int dx = GetDragScroll(this, p, 16).x;
		if(dx != 0)
			sc.AddPos(dx);
	}
}

void QuickTabs::SetCursor(int n)
{
	if(tabs.GetCount() == 0)
		return;

	if(n < 0)
	{
		n = max(0, Find(GetActiveGroup()));
		active = -1;
	}

	bool is_all = IsGroupAll();
	bool same_group = tabs[n].group == GetGroupName();

	if((same_group || is_all) && active == n)
		return;

	active = n;

	if(!is_all && !same_group)
	{
		SetGroup(tabs[n].group);
		Repos();
	}

	SetActiveGroup(tabs[n].id);

	int cx = tabs[n].x - sc.GetPos();
	if(cx < 0)
		sc.AddPos(cx - 10);
	else
	{
		cx = tabs[n].x + tabs[n].cx - GetSize().cx - sc.GetPos();
		if(cx > 0)
			sc.AddPos(cx + 10);
	}
	if(HasMouse())
		MouseMove(GetMouseViewPos(), 0);

	UpdateActionRefresh();
}

void QuickTabs::Close(int n)
{
	if(tabs.GetCount() <= 1)
		return;

	if(n == active)
	{
		int c = Find(tabs[n].id);
		int nc = GetNext(c);
		if(nc < 0)
			nc = max(0, GetPrev(c));

		SetActiveGroup(tabs[nc].id);
	}
	sc.AddTotal(-tabs[n].cx);
	tabs.Remove(n);
	MakeGroups();
	Repos();
	SetCursor(-1);
}

bool QuickTabs::FindSetFile(const String& fn)
{
	for(int i = 0; i < tabs.GetCount(); i++)
		if(tabs[i].name == fn) {
			SetCursor(i);
			return true;
		}
	return false;
}

void QuickTabs::SetAddFile(const String& fn)
{
	if(IsNull(fn))
		return;
	if(FindSetFile(fn))
		return;
	Add(fn, true);
	Refresh();
}

void QuickTabs::RenameFile(const String& fn, const String& nn)
{
	for(int i = 0; i < tabs.GetCount(); i++)
		if(tabs[i].name == fn)
		{
			tabs[i].name = nn;
			tabs[i].group = GetFileFolder(nn);
		}
	Repos();
}

void QuickTabs::Set(const QuickTabs& t)
{
	active = t.active;
	file_icons = t.file_icons;
	crosses = t.crosses;
	grouping = t.grouping;

	cross = -1;
	highlight = -1;
	target = -1;

	tabs.SetCount(t.GetCount());
	for(int i = 0; i < t.GetCount(); i++)
	{
		tabs[i].name = t.tabs[i].name;
		tabs[i].group = t.tabs[i].group;
	}
	MakeGroups();
	SetGroup(t.GetGroup());
	Repos();
	sc.Set(t.sc);
}
