/*
** This program is free software; you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation; either version 1, or (at your option)
** any later version.

** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
** GNU General Public License for more details.

** You should have received a copy of the GNU General Public License
** along with this program; if not, write to the Free Software
** Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/

/*
 * Author : Alexandre Parenteau <aubonbeurre@hotmail.com> --- February 2000
 */

/*
 * 
 */

#include "stdafx.h"

#include <sys/stat.h>

#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif

#include "UCvsGraph.h"
#include "UCvsFiles.h"
#include "CvsEntries.h"
#include "CvsLog.h"
#include "AppConsole.h"
#include "udraw.h"

UIMPLEMENT_DYNAMIC(UCvsGraph, UWidget)

UBEGIN_MESSAGE_MAP(UCvsGraph, UWidget)
	ON_UDESTROY(UCvsGraph)
	ON_UCREATE(UCvsGraph)
	ON_CUSTOM_DRAW(UCvsGraph::kDrawing, UCvsGraph::OnDrawing)
UEND_MESSAGE_MAP()

UCvsGraph::UCvsGraph(CLogNode *node, const char *dir) :
	UWidget(::UEventGetWidID()), m_node(node), m_dir(dir), m_data(0L)
{
	// get the CVS/Entries infos for this file
	CSortList<ENTNODE> entries(200, ENTNODE::Compare);
	if(!Entries_Open (entries, dir))
	{
		cvs_err("Warning : Error while accessing the CVS folder in '%s'\n", dir);
	}
	else
	{
		if(node->GetType() != kNodeHeader)
		{
			cvs_err("Warning : Top node is not a RCS file description !\n");
		}
		else
		{
			const char *name = (**(CLogNodeHeader *)node).WorkingFile();
			struct stat sb;
			if (stat(name, &sb) == -1)
			{
				cvs_err("Warning : Unknown file '%s'\n", name);
			}
			else
			{
				m_data = Entries_SetVisited(dir, entries, name, sb, false);
				m_data->Ref();
				m_name = name;
			}
		}
	}
}

UCvsGraph::~UCvsGraph()
{
	if(m_node != 0L)
		delete m_node;
	if(m_data != 0L)
		m_data->UnRef();
}

void UCvsGraph::OnDestroy(void)
{
	delete this;
}

void UCvsGraph::OnCreate(void)
{
	UStr title("Graph log : ");
	title << m_name;
	UEventSendMessage(GetWidID(), EV_SETTEXT, kUMainWidget, (void *)(const char *)title);
}

class CScrollView
{
public:
	UPoint GetDeviceScrollPosition() { return UPoint(0, 0); }
	void InvalidateRect(const URECT *r) {}
};

class CWinLogData : public CLogNodeData
{
public:
	CWinLogData(CLogNode *node);
	virtual ~CWinLogData();

	inline kLogNode GetType(void) const { return fNode->GetType(); }
	const char *GetStr(void);

	void ComputeBounds(const UPoint & topLeft, UDC & hdc,
		EntnodeData *entryInfo = 0L);
	void Offset(UPoint o);
	void Update(UDC & hdc, EntnodeData *entryInfo = 0L);
	
	inline URect & Bounds(void) { return totB; }
	inline URect & SelfBounds(void) { return selfB; }

	inline bool Selected(void) const { return sel; }
	void SetSelected(CScrollView *view, bool state);
	void UnselectAll(CScrollView *view);
	CWinLogData *HitTest(UPoint point);

	static CWinLogData *CreateNewData(CLogNode *node);

	bool IsDiskNode(EntnodeData *entryInfo);

protected:
	virtual void UpdateSelf(UDC & hdc, EntnodeData *entryInfo) = 0;
	virtual USize GetBoundsSelf(UDC & hdc, EntnodeData *entryInfo) = 0;

	inline CWinLogData *GetData(CLogNode *node)
		{ return (CWinLogData *)node->GetUserData(); }

	URect selfB;
	URect totB;
	bool sel;
};

class CWinLogHeaderData : public CWinLogData
{
public:
	CWinLogHeaderData(CLogNode *node) : CWinLogData(node) {}
	virtual ~CWinLogHeaderData() {}

protected:
	virtual void UpdateSelf(UDC & hdc, EntnodeData *entryInfo);
	virtual USize GetBoundsSelf(UDC & hdc, EntnodeData *entryInfo);
};

class CWinLogRevData : public CWinLogData
{
public:
	CWinLogRevData(CLogNode *node) : CWinLogData(node) {}
	virtual ~CWinLogRevData() {}
	
protected:
	virtual void UpdateSelf(UDC & hdc, EntnodeData *entryInfo);
	virtual USize GetBoundsSelf(UDC & hdc, EntnodeData *entryInfo);
};

class CWinLogTagData : public CWinLogData
{
public:
	CWinLogTagData(CLogNode *node) : CWinLogData(node) {}
	virtual ~CWinLogTagData() {}
	
protected:
	virtual void UpdateSelf(UDC & hdc, EntnodeData *entryInfo);
	virtual USize GetBoundsSelf(UDC & hdc, EntnodeData *entryInfo);
};

class CWinLogBranchData : public CWinLogData
{
public:
	CWinLogBranchData(CLogNode *node) : CWinLogData(node) {}
	virtual ~CWinLogBranchData() {}
	
protected:
	virtual void UpdateSelf(UDC & hdc, EntnodeData *entryInfo);
	virtual USize GetBoundsSelf(UDC & hdc, EntnodeData *entryInfo);
};

USize CWinLogHeaderData::GetBoundsSelf(UDC & hdc, EntnodeData *entryInfo)
{
	CLogNodeHeader & header = *(CLogNodeHeader *)fNode;
	return hdc.GetTextExtent((*header).WorkingFile(),
		(*header).WorkingFile().length());
}

static void DrawNode(UDC & hdc, const URect & selfB,
					 UCOLORREF contour, UCOLORREF shadowc,
					 bool isRounded, bool issel)
{
	UPen *pOldPen = 0L;
	UBrush *pOldBrush = 0L;
	UPen pen, pen2;
	UBrush brush;
	UCOLORREF selcolor = URGB(160, 160, 160);
	UCOLORREF wcolor = URGB(255, 255, 255);

	try
	{
		if(!isRounded)
		{
			if(!issel)
			{
				brush.CreateSolidBrush(wcolor);
				pOldBrush = hdc.SelectObject(&brush);
				pen.CreatePen(UPS_SOLID, 1, contour);
				pOldPen = hdc.SelectObject(&pen);
				hdc.Rectangle(selfB);
			}
			else
			{
				brush.CreateSolidBrush(selcolor);
				pOldBrush = hdc.SelectObject(&brush);
				pen.CreatePen(UPS_SOLID, 1, contour);
				pOldPen = hdc.SelectObject(&pen);
				hdc.Rectangle(selfB);
			}
		}
		else
		{
			URect shadow;
			if(!issel)
			{
				brush.CreateSolidBrush(shadowc);
				pOldBrush = hdc.SelectObject(&brush);
				pen.CreatePen(UPS_SOLID, 1, shadowc);
				pOldPen = hdc.SelectObject(&pen);
				shadow = selfB;
				shadow.OffsetRect(UPoint(2, 2));
				hdc.RoundRect(&shadow, UPoint(6, 6));

				brush.DeleteObject();
				brush.CreateSolidBrush(wcolor);
				hdc.SelectObject(&brush);
				pen2.CreatePen(UPS_SOLID, 1, contour);
				hdc.SelectObject(&pen2);
				hdc.RoundRect(selfB, UPoint(6, 6));
			}
			else
			{
				brush.CreateSolidBrush(shadowc);
				pOldBrush = hdc.SelectObject(&brush);
				pen.CreatePen(UPS_SOLID, 1, shadowc);
				pOldPen = hdc.SelectObject(&pen);
				shadow = selfB;
				shadow.OffsetRect(UPoint(2, 2));
				hdc.RoundRect(&shadow, UPoint(6, 6));

				brush.DeleteObject();
				brush.CreateSolidBrush(selcolor);
				hdc.SelectObject(&brush);
				pen2.CreatePen(UPS_SOLID, 1, contour);
				hdc.SelectObject(&pen2);
				hdc.RoundRect(selfB, UPoint(6, 6));
			}
		}

		if (pOldPen != 0L)
		{
			hdc.SelectObject(pOldPen);
			pOldPen = 0L;
		}
		if (pOldBrush != 0L)
		{
			hdc.SelectObject(pOldBrush);
			pOldBrush = 0L;
		}

		pen.DeleteObject();
		pen2.DeleteObject();
		brush.DeleteObject();
	}
	catch(...)
	{
		if (pOldPen != 0L)
			hdc.SelectObject(pOldPen);
		if (pOldBrush != 0L)
			hdc.SelectObject(pOldBrush);
	}
}

void CWinLogHeaderData::UpdateSelf(UDC & hdc, EntnodeData *entryInfo)
{
	// draw the node
	::DrawNode(hdc, selfB, URGB(255, 0, 0), URGB(120, 0, 0), true, sel);

	// draw the content of the node
	UPoint center = selfB.CenterPoint();
	int nCenterX = center.x;
	int nCenterY = center.y - hdc.GetTextHeight()/2;
	CLogNodeHeader & header = *(CLogNodeHeader *)fNode;
	hdc.TextOutClipped(nCenterX, nCenterY, &selfB,
		(*header).WorkingFile(), (*header).WorkingFile().length());
}

USize CWinLogRevData::GetBoundsSelf(UDC & hdc, EntnodeData *entryInfo)
{
	CLogNodeRev & rev = *(CLogNodeRev *)fNode;
	USize size = hdc.GetTextExtent((*rev).RevNum().c_str(),
		strlen((*rev).RevNum().c_str()));
	if(IsDiskNode(entryInfo))
	{
		size.cx += 2 + 16 + 2;
		size.cy = std::max(16, size.cy);
	}
	return size;
}

void CWinLogRevData::UpdateSelf(UDC & hdc, EntnodeData *entryInfo)
{
	URect txtRect = selfB;

	if(IsDiskNode(entryInfo))
	{
		txtRect.left += 2 + 16 + 2;
	}

	// draw the node
	::DrawNode(hdc, selfB, URGB(0, 0, 255), URGB(0, 0, 0), false, sel);

	// draw the state icon if it is the rev. of the disk
	if(IsDiskNode(entryInfo))
	{
		UPoint w(selfB.TopLeft());
		w.x += 2;
		w.y += (selfB.Height() - 16) / 2;
		hdc.DrawBitmap(UCvsFiles::GetImageForEntry(entryInfo), w);
	}

	// draw the content of the node
	UPoint center = txtRect.CenterPoint();
	int nCenterX = center.x;
	int nCenterY = center.y - hdc.GetTextHeight()/2;
	CLogNodeRev & rev = *(CLogNodeRev *)fNode;
	hdc.TextOutClipped(nCenterX, nCenterY, &txtRect,
			(*rev).RevNum().c_str(), strlen((*rev).RevNum().c_str()));
}

USize CWinLogTagData::GetBoundsSelf(UDC & hdc, EntnodeData *entryInfo)
{
	CLogNodeTag & tag = *(CLogNodeTag *)fNode;
	return hdc.GetTextExtent(*tag, (*tag).length());
}

void CWinLogTagData::UpdateSelf(UDC & hdc, EntnodeData *entryInfo)
{
	// draw the node
	::DrawNode(hdc, selfB, URGB(0, 0, 0), URGB(120, 120, 120), true, sel);

	// draw the content of the node
	UPoint center = selfB.CenterPoint();
	int nCenterX = center.x;
	int nCenterY = center.y - hdc.GetTextHeight()/2;
	CLogNodeTag & tag = *(CLogNodeTag *)fNode;
	hdc.TextOutClipped(nCenterX, nCenterY, &selfB,
		*tag, (*tag).length());
}

USize CWinLogBranchData::GetBoundsSelf(UDC & hdc, EntnodeData *entryInfo)
{
	CLogNodeBranch & branch = *(CLogNodeBranch *)fNode;
	return hdc.GetTextExtent(*branch, (*branch).length());
}

void CWinLogBranchData::UpdateSelf(UDC & hdc, EntnodeData *entryInfo)
{
	// draw the node
	::DrawNode(hdc, selfB, URGB(0, 0, 255), URGB(0, 0, 120), true, sel);

	// draw the content of the node
	UPoint center = selfB.CenterPoint();
	int nCenterX = center.x;
	int nCenterY = center.y - hdc.GetTextHeight()/2;
	CLogNodeBranch & branch = *(CLogNodeBranch *)fNode;
	hdc.TextOutClipped(nCenterX, nCenterY, &selfB,
		*branch, (*branch).length());
}

CWinLogData::CWinLogData(CLogNode *node) : CLogNodeData(node)
{
	selfB.SetRectEmpty();
	totB.SetRectEmpty();
	sel = false;
}

CWinLogData::~CWinLogData()
{
}

const char *CWinLogData::GetStr(void)
{
	switch(GetType())
	{
		case kNodeHeader :
		{
			CLogNodeHeader & header = *(CLogNodeHeader *)fNode;
			return (*header).WorkingFile();
			break;
		}
		case kNodeBranch :
		{
			CLogNodeBranch & branch = *(CLogNodeBranch *)fNode;
			return *branch;
			break;
		}
		case kNodeRev :
		{
			CLogNodeRev & rev = *(CLogNodeRev *)fNode;
			return (*rev).RevNum().c_str();
			break;
		}
		case kNodeTag :
		{
			CLogNodeTag & tag = *(CLogNodeTag *)fNode;
			return *tag;
			break;
		}
	}
	return 0L;
}

CWinLogData * CWinLogData::CreateNewData(CLogNode *node)
{
	if(node->GetUserData() != 0L)
		return (CWinLogData *)node->GetUserData();

	CWinLogData *res = 0L;
	switch(node->GetType())
	{
	case kNodeHeader :
		res = new CWinLogHeaderData(node);
		break;
	case kNodeBranch :
		res = new CWinLogBranchData(node);
		break;
	case kNodeRev :
		res = new CWinLogRevData(node);
		break;
	case kNodeTag :
		res = new CWinLogTagData(node);
		break;
	}
	node->SetUserData(res);
	return res;
}

bool CWinLogData::IsDiskNode(EntnodeData *entryInfo)
{
	if(entryInfo == 0L || GetType() != kNodeRev)
		return false;
	const char *vn = (*entryInfo)[EntnodeFile::kVN];
	if(vn == 0L)
		return false;

	return strcmp(vn, (**(CLogNodeRev *)fNode).RevNum().c_str()) == 0;
}

const int kVChildSpace = 8;
const int kHChildSpace = 40;
const int kInflateNodeSpace = 8;

void CWinLogData::Offset(UPoint o)
{
	selfB.OffsetRect(o);
	totB.OffsetRect(o);
	std::vector<CLogNode *>::iterator i;
	for(i = fNode->Childs().begin(); i != fNode->Childs().end(); ++i)
	{
		CLogNode *subNode = *i;
		GetData(subNode)->Offset(o);
	}
	if(fNode->Next() != 0L)
		GetData(fNode->Next())->Offset(o);
}

void CWinLogData::SetSelected(CScrollView *view, bool state)
{
	sel = state;

	URect invalR = selfB;
	invalR.OffsetRect(-view->GetDeviceScrollPosition());
	view->InvalidateRect(&invalR);
}

void CWinLogData::UnselectAll(CScrollView *view)
{
	if(sel)
	{
		SetSelected(view, false);
	}

	std::vector<CLogNode *>::iterator i;
	for(i = fNode->Childs().begin(); i != fNode->Childs().end(); ++i)
	{
		CLogNode *subNode = *i;
		GetData(subNode)->UnselectAll(view);
	}
	if(fNode->Next() != 0L)
		GetData(fNode->Next())->UnselectAll(view);
}

CWinLogData *CWinLogData::HitTest(UPoint point)
{
	if(!totB.PtInRect(point))
		return 0L;
	if(selfB.PtInRect(point))
		return this;

	CWinLogData *result;
	std::vector<CLogNode *>::iterator i;
	for(i = fNode->Childs().begin(); i != fNode->Childs().end(); ++i)
	{
		CLogNode *subNode = *i;
		result = GetData(subNode)->HitTest(point);
		if(result != 0L)
			return result;
	}
	if(fNode->Next() != 0L)
	{
		result = GetData(fNode->Next())->HitTest(point);
		if(result != 0L)
			return result;
	}
	return 0L;
}

void CWinLogData::Update(UDC & hdc, EntnodeData *entryInfo)
{
	CLogNode *subNode;
	CLogNode *nextNode = fNode->Next();
	UPen *pOldPen = 0L;
	UPen pen, pen2;

	UpdateSelf(hdc, entryInfo);

	try
	{
		pen.CreatePen(UPS_SOLID, 2, URGB(0, 0, 255));
		pen2.CreatePen(UPS_SOLID, 1, URGB(0, 0, 0));
		pOldPen = hdc.SelectObject(&pen);

		int topY = selfB.TopLeft().y + 2;
		int botY = selfB.BottomRight().y - 2;
		int lefX = selfB.BottomRight().x + 4;
		int count = 1;
		int tot = fNode->Childs().size() + 1;

		// draw the children
		std::vector<CLogNode *>::iterator i;
		for(i = fNode->Childs().begin(); i != fNode->Childs().end(); ++i, ++count)
		{
			subNode = *i;
			
			// draw the link
			URect & cb = GetData(subNode)->SelfBounds();
			if(subNode->GetType() == kNodeRev || subNode->GetType() == kNodeBranch)
				hdc.SelectObject(&pen);
			else
				hdc.SelectObject(&pen2);
			float y = (float)topY + (float)(botY - topY) *
				(float)count / (float)tot;
			int rigX = cb.TopLeft().x - 4;
			float x = (float)rigX - (float)(rigX - lefX) *
				(float)count / (float)tot;
			//int halfX = (cb.TopLeft().x - selfB.BottomRight().x) / 2;
			hdc.MoveTo(selfB.BottomRight().x, (int)y);
			hdc.LineTo((int)x, (int)y);
			hdc.LineTo((int)x, cb.CenterPoint().y);
			hdc.LineTo(cb.TopLeft().x, cb.CenterPoint().y);
			
			// draw the sub-node
			GetData(subNode)->Update(hdc, entryInfo);
		}

		// draw the next node
		if(nextNode != 0L)
		{
			// draw the link
			URect & nb = GetData(nextNode)->SelfBounds();
			if(nextNode->GetType() == kNodeRev || nextNode->GetType() == kNodeBranch)
				hdc.SelectObject(&pen);
			else
				hdc.SelectObject(&pen2);
			int centerX = selfB.Width() < nb.Width() ?
				selfB.CenterPoint().x : nb.CenterPoint().x;
			hdc.MoveTo(centerX, selfB.BottomRight().y);
			hdc.LineTo(centerX, nb.TopLeft().y);
			
			// draw the next node
			GetData(nextNode)->Update(hdc, entryInfo);
		}

		if (pOldPen != 0L)
		{
			hdc.SelectObject(pOldPen);
			pOldPen = 0L;
		}

		pen.DeleteObject();
		pen2.DeleteObject();
	}
	catch(...)
	{
		if (pOldPen != 0L)
			hdc.SelectObject(pOldPen);
	}
}

void CWinLogData::ComputeBounds(const UPoint & topLeft, UDC & hdc,
								EntnodeData *entryInfo)
{
	CLogNode *subNode;
	CLogNode *nextNode = fNode->Next();

	// first compute childrens bounds
	std::vector<CLogNode *>::iterator i;
	for(i = fNode->Childs().begin(); i != fNode->Childs().end(); ++i)
	{
		subNode = *i;
		CWinLogData::CreateNewData(subNode);
		GetData(subNode)->ComputeBounds(topLeft, hdc, entryInfo);
	}
	if(nextNode != 0L)
		CWinLogData::CreateNewData(nextNode);

	// compute self place
	selfB.SetRectEmpty();
	USize size = GetBoundsSelf(hdc, entryInfo);
	size.cx += kInflateNodeSpace;
	size.cy += kInflateNodeSpace;
	selfB.SetRect(0, 0, size.cx, size.cy);

	// offset to the place assigned to this node
	selfB.OffsetRect(topLeft - selfB.TopLeft());

	// calculate the total height of the childrens
	int vSize = 0;
	for(i = fNode->Childs().begin(); i != fNode->Childs().end(); ++i)
	{
		subNode = *i;
		URect & b = GetData(subNode)->Bounds();
		vSize += b.Height();
	}
	if(!fNode->Childs().empty())
		vSize += (fNode->Childs().size() - 1) * kVChildSpace;

	// offset the children relative to self
	UPoint startChilds(topLeft.x + selfB.Width() + kHChildSpace,
		/*selfB.CenterPoint().y - vSize / 2*/topLeft.y);
	for(i = fNode->Childs().begin(); i != fNode->Childs().end(); ++i)
	{
		subNode = *i;
		URect curPos = GetData(subNode)->Bounds();
		GetData(subNode)->Offset(startChilds - curPos.TopLeft());
		startChilds.y += curPos.Height() + kVChildSpace;
	}

	// calculate the total bounds of the childrens
	URect bc;
	bc.SetRectEmpty();
	for(i = fNode->Childs().begin(); i != fNode->Childs().end(); ++i)
	{
		subNode = *i;
		URect & b = GetData(subNode)->Bounds();
		bc.UnionRect(&bc, &b);
	}

	// now we got the complete size
	totB.UnionRect(&selfB, &bc);

	if(nextNode != 0L)
	{
		UPoint nextTopLeft;
		nextTopLeft.x = totB.TopLeft().x;
		nextTopLeft.y = totB.BottomRight().y + kVChildSpace;
		GetData(nextNode)->ComputeBounds(nextTopLeft, hdc, entryInfo);

		totB.UnionRect(&totB, &GetData(nextNode)->Bounds());
	}
}

void UCvsGraph::CalcImageSize(void)
{
	if(m_node == 0L)
		return;

	// set all the bounds locations
	UDC dc(GetWidID(), kDrawing);
	CWinLogData *data = CWinLogData::CreateNewData(m_node);
	m_node->SetUserData(data);
	UPoint start(5, 5);
	data->ComputeBounds(start, dc, m_data);

	// reoffset from (0, 0)
	URect bounds = data->Bounds();
	data->Offset(start - bounds.TopLeft());
	bounds = data->Bounds();

#if qGTK
	GtkWidget *layout = (GtkWidget *)GetWidget(kDrawing);
	gtk_layout_set_size (GTK_LAYOUT (layout), bounds.Width(), bounds.Height());
#endif
#if 0
	// set the scroll size
	SetScrollSizes(MM_TEXT, bounds.Size() + USize(5, 5));
#endif
}

void UCvsGraph::OnDrawing(void *info)
{
#if qGTK
	GdkEventExpose *event = (GdkEventExpose *)info;
	GtkLayout *layout = GTK_LAYOUT(GetWidget(kDrawing));
	gdk_window_clear_area (layout->bin_window,
						   event->area.x, event->area.y,
						   event->area.width, event->area.height);
#endif
	CalcImageSize();

	if(m_node == 0L || m_node->GetUserData() == 0L)
		return;

	UDC dc(GetWidID(), kDrawing);
	CWinLogData *data = (CWinLogData *)m_node->GetUserData();
	data->Update(dc, m_data);

/*	GtkWidget *widget = (GtkWidget *)GetWidget(kDrawing);
	if(widget == 0L)
		return;

	GtkDrawingArea *darea;
	GdkDrawable *drawable;
	GdkGC *black_gc;
	GdkGC *gray_gc;
	GdkGC *white_gc;
	guint max_width;
	guint max_height;
	
	g_return_if_fail (widget != NULL);
	g_return_if_fail (GTK_IS_DRAWING_AREA (widget));
	
	darea = GTK_DRAWING_AREA (widget);
	drawable = widget->window;
	white_gc = widget->style->white_gc;
	gray_gc = widget->style->bg_gc[GTK_STATE_NORMAL];
	black_gc = widget->style->black_gc;
	max_width = widget->allocation.width;
	max_height = widget->allocation.height;
	gdk_draw_rectangle (drawable, black_gc,
						TRUE,
						0,
						max_height / 2,
						max_width,
						max_height / 2);*/
}
