#include "Lvc.h"
#include "lgi/common/ClipBoard.h"
#include "../Resources/resdefs.h"
#include "lgi/common/Path.h"
#include "lgi/common/HashTable.h"

const char *sPalette[] = { "9696ff", "ff8787", "ff934b", "a8d200", "00f0c0", "87e7ff", "a5a5ff", "f3c3ff", "b40090", "00b400" };

LColour GetPaletteColour(int i)
{
	LColour c;
	const char *s = sPalette[i % CountOf(sPalette)];
	#define Comp(comp, off) { const char h[3] = {s[off], s[off+1], 0}; c.comp(htoi(h)); }
	Comp(r, 0);
	Comp(g, 2);
	Comp(b, 4);
	return c;
}

VcEdge::~VcEdge()
{
	if (Parent)
		Parent->Edges.Delete(this);
	if (Child)
		Child->Edges.Delete(this);
}

void VcEdge::Detach(VcCommit *c)
{
	if (Parent == c)
		Parent = NULL;
	if (Child == c)
		Child = NULL;
	if (Parent == NULL && Child == NULL)
		delete this;
}

void VcEdge::Set(VcCommit *p, VcCommit *c)
{
	if ((Parent = p))
		Parent->Edges.Add(this);
	if ((Child = c))
		Child->Edges.Add(this);
}

size_t VcCommit::Instances = 0;
VcCommit::VcCommit(AppPriv *priv, VcFolder *folder) : Pos(32, -1)
{
	VcCommit::Instances++;
	d = priv;
	Index = -1;
	Folder = folder;
	Current = false;
	NodeIdx = -1;
	NodeColour = GetPaletteColour(0);
	Parents.SetFixedLength(false);
}

VcCommit::~VcCommit()
{
	for (auto e: Edges)
		e->Detach(this);
	VcCommit::Instances--;
}

char *VcCommit::GetRev()
{
	return Rev;
}

char *VcCommit::GetAuthor()
{
	return Author;
}

char *VcCommit::GetMsg()
{
	return Msg;
}

char *VcCommit::GetBranch()
{
	return Branch;
}

void VcCommit::SetCurrent(bool b)
{
	Current = b;
}

void VcCommit::OnPaintColumn(LItem::ItemPaintCtx &Ctx, int i, LItemColumn *c)
{
	if (i == 0)
	{
		double Px = 12;
		int Ht = Ctx.Y();
		double Half = 5.5;
		#define MAP(col) ((col) * Px + Half)

		LMemDC Mem(Ctx.X(), Ctx.Y(), System32BitColourSpace);

		#ifdef LINUX
		{
			LItem::ItemPaintCtx TmpCtx = Ctx;
			TmpCtx.Offset(-TmpCtx.x1, -TmpCtx.y1);
			TmpCtx.pDC = &Mem;
			LListItem::OnPaintColumn(TmpCtx, i, c);
		}
		#else
		LListItem::OnPaintColumn(Ctx, i, c);
		#endif

		double r = Half - 1;

		double x = MAP(NodeIdx);
		Mem.Colour(LColour::Black);
		
		VcCommit *Prev = NULL, *Next = NULL;
		Prev = Folder->Log.IdxCheck(Idx - 1) ? Folder->Log[Idx - 1] : NULL;
		Next = Folder->Log.IdxCheck(Idx + 1) ? Folder->Log[Idx + 1] : NULL;
		
		for (auto it: Pos)
		{
			VcEdge *e = it.key;
			int CurIdx = it.value;
			if (CurIdx < 0)
			{
				continue;
			}

			double CurX = MAP(CurIdx);
			
			#define I(v) ((int)(v))
			if (e->Child != this)
			{
				// Line to previous commit
				int PrevIdx = Prev ? Prev->Pos.Find(e) : -1;
				if (PrevIdx >= 0)
				{
					double PrevX = MAP(PrevIdx);
					Mem.Line(I(PrevX), I(-(Ht/2)), I(CurX), I(Ht/2));
				}
				else
				{
					Mem.Colour(LColour::Red);
					Mem.Line(I(CurX), I(Ht/2), I(CurX), I(Ht/2-5));
					Mem.Colour(LColour::Black);
				}
			}

			if (e->Parent != this)
			{
				int NextIdx = Next ? Next->Pos.Find(e) : -1;
				if (NextIdx >= 0)
				{
					double NextX = MAP(NextIdx);
					Mem.Line(I(NextX), I(Ht+(Ht/2)), I(CurX), I(Ht/2));
				}
				else
				{
					Mem.Colour(LColour::Red);
					Mem.Line(I(CurX), I(Ht/2), I(CurX), I(Ht/2+5));
					Mem.Colour(LColour::Black);
				}
			}
		}

		if (NodeIdx >= 0)
		{
			double Cx = x;
			double Cy = Ht / 2;
			{
				LPath p;
				p.Circle(Cx, Cy, r + (Current ? 1 : 0));
				LSolidBrush sb(LColour::Black);
				p.Fill(&Mem, sb);
			}
			{
				LPath p;
				p.Circle(Cx, Cy, r-1);
				LAssert(NodeColour.IsValid());
				// LgiTrace("%s = %s\n", Branch.Get(), NodeColour.GetStr());
				LSolidBrush sb(NodeColour);
				p.Fill(&Mem, sb);
			}
		}

		// Mem.ConvertPreMulAlpha(false);
		Ctx.pDC->Op(GDC_ALPHA);
		Ctx.pDC->Blt(Ctx.x1, Ctx.y1, &Mem);
	}
	else
	{
		LListItem::OnPaintColumn(Ctx, i, c);
	}
}

const char *VcCommit::GetFieldText(CommitField Fld)
{
	switch (Fld)
	{
		case LRevision:
			return Rev;
		case LIndex:
			Cache.Printf(LPrintfInt64, Index);
			return Cache;
		case LBranch:
			if (Branch)
				return Branch;
			return "default";
		case LAuthor:
			return Author;
		case LTimeStamp:
			Cache = Ts.Get();
			return Cache;
		case LMessageTxt:
			if (!Msg)
				return NULL;
			Cache = Msg.Split("\n", 1)[0];
			return Cache;
		default:
			break;
	}

	return NULL;
}

const char *VcCommit::GetText(int Col)
{
	if (!Folder)
	{
		LAssert(0);
		return "#nofolder";
	}

	if (!Folder->Fields.IdxCheck(Col))
	{
		LAssert(0);
		return "#nofield";
	}

	return (char*)GetFieldText(Folder->Fields[Col]);
}

bool VcCommit::GitParse(LString s, bool RevList)
{
	LString::Array lines = s.Split("\n");
	if (lines.Length() < 3)
		return false;

	if (RevList)
	{
		auto a = lines[0].SplitDelimit();
		if (a.Length() != 2)
			return false;

		Ts.SetUnix((uint64) a[0].Int());
		Rev = a[1];

		for (int i=0; i<lines.Length(); i++)
		{
			LString &Ln = lines[i];
			if (IsWhiteSpace(Ln(0)))
			{
				if (Msg)
					Msg += "\n";
				Msg += Ln.Strip();
			}
			else
			{
				a = Ln.SplitDelimit(" \t\r", 1);
				if (a.Length() <= 1)
					continue;
				if (a[0].Equals("parent"))
					Parents.Add(a[1]);
				else if (a[0].Equals("author"))
					Author = a[1].RStrip("0123456789+ ");
			}
		}
	}
	else
	{
		for (unsigned ln = 0; ln < lines.Length(); ln++)
		{
			LString &l = lines[ln];
			if (ln == 0)
				Rev = l.SplitDelimit().Last();
			else if (l.Find("Author:") >= 0)
				Author = l.Split(":", 1)[1].Strip();
			else if (l.Find("Date:") >= 0)
				Ts.Parse(l.Split(":", 1)[1].Strip());
			else if (l.Strip().Length() > 0)
			{
				if (Msg)
					Msg += "\n";
				Msg += l.Strip();
			}
		}
	}

	OnParse();
	return Author && Rev;
}

bool VcCommit::CvsParse(LDateTime &Dt, LString Auth, LString Message)
{
	Ts = Dt;
	Ts.ToLocal();
	
	uint64 i;
	if (Ts.Get(i))
		Rev.Printf(LPrintfInt64, i);
	Author = Auth;
	Msg = Message;

	OnParse();
	return true;
}

void VcCommit::OnParse()
{
	NodeColour = Folder->BranchColour(Branch);
}

bool VcCommit::HgParse(LString s)
{
	LString::Array Lines = s.SplitDelimit("\n");
	if (Lines.Length() < 1)
		return false;

	for (auto Ln: Lines)
	{
		LString::Array f = Ln.Split(":", 1);
		if (f.Length() == 2)
		{
			if (f[0].Equals("changeset"))
			{
				auto p = f[1].Strip().Split(":");
				Index = p[0].Int();
				Rev = p[1];
			}
			else if (f[0].Equals("user"))
				Author = f[1].Strip();
			else if (f[0].Equals("date"))
				Ts.Parse(f[1].Strip());
			else if (f[0].Equals("summary"))
				Msg = f[1].Strip();
			else if (f[0].Equals("parent"))
			{
				auto p = f[1].Strip().Split(":");
				Parents.Add(p.Last());
			}
			else if (f[0].Equals("branch"))
				Branch = f[1].Strip();
		}
	}

	OnParse();
	return Rev.Get() != NULL;
}

bool VcCommit::SvnParse(LString s)
{
	LString::Array lines = s.Split("\n");
	if (lines.Length() < 1)
		return false;

	for (unsigned ln = 0; ln < lines.Length(); ln++)
	{
		LString &l = lines[ln];
		if (ln == 0)
		{
			LString::Array a = l.Split("|");
			if (a.Length() > 3)
			{
				Rev = a[0].Strip(" \tr");
				Index = Rev.Int();
				Author = a[1].Strip();
				Ts.Parse(a[2]);
			}
		}
		else
		{
			if (Msg)
				Msg += "\n";
			Msg += l.Strip();
		}
	}

	Msg = Msg.Strip();
	OnParse();
	
	// LgiTrace("SvnParse %s %i %s %s %s\n", Rev.Get(), (int)Index, Author.Get(), Ts.GetDate().Get(), Msg.Get());

	return Author && Rev && Ts.IsValid();
}

VcFolder *VcCommit::GetFolder()
{
	for (LTreeItem *i = d->Tree->Selection(); i;
		i = i->GetParent())
	{
		auto f = dynamic_cast<VcFolder*>(i);
		if (f)
			return f;
	}
	return NULL;
}

void VcCommit::Select(bool b)
{
	LListItem::Select(b);

	if (Rev && b)
	{
		VcFolder *f = GetFolder();
		// LgiTrace("%s:%i select %s %i f=%p\n", _FL, Rev.Get(), b, f);
		if (f)
		{
			auto Rng = d->GetCommitRange();
			// LgiTrace("%s:%i select rng=%i\n", _FL, (int)Rng.Length());
			if (Rng.Length() > 1)
			{
				// Show the diffs over a range of commits.
				f->DiffRange(Rng[0], Rng[1]);
			}
			else
			{
				// Show a single commit's files:
				f->ListCommit(this);
			}
		}

		if (d->Msg)
		{
			d->Msg->Name(Msg);
			
			auto *w = d->Msg->GetWindow();
			if (w)
			{
				w->SetCtrlEnabled(IDC_COMMIT, false);
				w->SetCtrlEnabled(IDC_COMMIT_AND_PUSH, false);
			}
		}
		else LAssert(0);
	}
}

void VcCommit::OnMouseClick(LMouse &m)
{
	LListItem::OnMouseClick(m);

	if (m.IsContextMenu())
	{
		LArray<VcCommit*> Sel;
		GetList()->GetSelection(Sel);

		LSubMenu s;
		s.AppendItem("Update", IDM_UPDATE, !Current);
		s.AppendSeparator();
		s.AppendItem("Merge With Local", IDM_MERGE, !Current);
		if (Rev)
			s.AppendItem("Copy Revision", IDM_COPY_REV);
		if (Index >= 0)
			s.AppendItem("Copy Index", IDM_COPY_INDEX);
		s.AppendItem("Rename Branch", IDM_RENAME_BRANCH);
		
		int Cmd = s.Float(GetList(), m);
		switch (Cmd)
		{
			case IDM_MERGE:
			{
				VcFolder *f = GetFolder();
				if (!f)
				{
					LAssert(!"No folder?");
					break;
				}

				f->MergeToLocal(Rev);
				break;
			}
			case IDM_UPDATE:
			{
				VcFolder *f = GetFolder();
				if (!f)
				{
					LAssert(!"No folder?");
					break;
				}

				f->OnUpdate(Rev);
				break;
			}
			case IDM_COPY_REV:
			{
				LClipBoard c(GetList());

				LString::Array a;
				a.SetFixedLength(false);
				for (auto i: Sel)
					a.New() = i->GetRev();

				c.Text(LString(",").Join(a));
				break;
			}
			case IDM_COPY_INDEX:
			{
				LClipBoard c(GetList());
				LString b;
				b.Printf(LPrintfInt64, Index);
				c.Text(b);
				break;
			}
			case IDM_RENAME_BRANCH:
			{
				LArray<VcCommit*> Revs;
				if (GetList()->GetSelection(Revs))
				{
					LInput Inp(GetList(), "", "New branch name:", AppName);
					if (Inp.DoModal())
					{
						Folder->RenameBranch(Inp.GetStr(), Revs);
					}
				}
				break;
			}
		}
	}
}
