//////////////////////////////////////////////////////////////////////////
//
// pgAdmin III - PostgreSQL Tools
//
// Portions Copyright (C) 1998 - 2011, Julian Smart
// Portions Copyright (C) 2011 - 2013, The pgAdmin Development Team
// This software is released under the PostgreSQL Licence
//
// ogldiag.cpp - wxDiagram
//
//////////////////////////////////////////////////////////////////////////

#include "pgAdmin3.h"

#include <ctype.h>
#include <math.h>
#include <stdlib.h>

#include "ogl/ogl.h"


IMPLEMENT_DYNAMIC_CLASS(wxDiagram, wxObject)

// Object canvas
wxDiagram::wxDiagram()
{
	m_diagramCanvas = NULL;
	m_quickEditMode = FALSE;
	m_snapToGrid = TRUE;
	m_gridSpacing = 5.0;
	m_shapeList = new wxList;
	m_mouseTolerance = DEFAULT_MOUSE_TOLERANCE;
}

wxDiagram::~wxDiagram()
{
	if (m_shapeList)
		delete m_shapeList;
}

void wxDiagram::SetSnapToGrid(bool snap)
{
	m_snapToGrid = snap;
}

void wxDiagram::SetGridSpacing(double spacing)
{
	m_gridSpacing = spacing;
}

void wxDiagram::Snap(double *x, double *y)
{
	if (m_snapToGrid)
	{
		*x = m_gridSpacing * ((int)(*x / m_gridSpacing + 0.5));
		*y = m_gridSpacing * ((int)(*y / m_gridSpacing + 0.5));
	}
}


void wxDiagram::Redraw(wxDC &dc)
{
	if (m_shapeList)
	{
		if (GetCanvas())
			GetCanvas()->SetCursor(* wxHOURGLASS_CURSOR);
		wxNode *current = m_shapeList->GetFirst();

		while (current)
		{
			wxShape *object = (wxShape *)current->GetData();
			if (!object->GetParent())
				object->Draw(dc);

			current = current->GetNext();
		}
		if (GetCanvas())
			GetCanvas()->SetCursor(* wxSTANDARD_CURSOR);
	}
}

void wxDiagram::Clear(wxDC &dc)
{
	dc.Clear();
}

// Insert object after addAfter, or at end of list.
void wxDiagram::AddShape(wxShape *object, wxShape *addAfter)
{
	wxNode *nodeAfter = NULL;


	if (addAfter)
	{
#if wxCHECK_VERSION(2, 9, 0)
		nodeAfter = m_shapeList->Find(addAfter);
#else
		nodeAfter = m_shapeList->Member(addAfter);
#endif
	}

	if (!m_shapeList->Member(object))
	{
		if (nodeAfter)
		{
			if (nodeAfter->GetNext())
				m_shapeList->Insert(nodeAfter->GetNext(), object);
			else
				m_shapeList->Append(object);
		}
		else
			m_shapeList->Append(object);
		object->SetCanvas(GetCanvas());
	}
}

void wxDiagram::InsertShape(wxShape *object)
{
	m_shapeList->Insert(object);
	object->SetCanvas(GetCanvas());
}

void wxDiagram::RemoveShape(wxShape *object)
{
	m_shapeList->DeleteObject(object);
	object->RemoveChildren();
}

// Should this delete the actual objects too? I think not.
void wxDiagram::RemoveAllShapes()
{
	m_shapeList->Clear();
}

void wxDiagram::DeleteAllShapes()
{
	wxNode *node = m_shapeList->GetFirst();
	while (node)
	{
		wxShape *shape = (wxShape *)node->GetData();
		if (!shape->GetParent())
		{
			RemoveShape(shape);
			delete shape;
			node = m_shapeList->GetFirst();
		}
		else
			node = node->GetNext();
	}
}

void wxDiagram::ShowAll(bool show)
{
	wxNode *current = m_shapeList->GetFirst();

	while (current)
	{
		wxShape *object = (wxShape *)current->GetData();
		object->Show(show);

		current = current->GetNext();
	}
}

void wxDiagram::DrawOutline(wxDC &dc, double x1, double y1, double x2, double y2)
{
	wxPen dottedPen(wxColour(0, 0, 0), 1, wxDOT);
	dc.SetPen(dottedPen);
	dc.SetBrush((* wxTRANSPARENT_BRUSH));

	wxPoint points[5];

	points[0].x = (int) x1;
	points[0].y = (int) y1;

	points[1].x = (int) x2;
	points[1].y = (int) y1;

	points[2].x = (int) x2;
	points[2].y = (int) y2;

	points[3].x = (int) x1;
	points[3].y = (int) y2;

	points[4].x = (int) x1;
	points[4].y = (int) y1;
	dc.DrawLines(5, points);
}

// Make sure all text that should be centred, is centred.
void wxDiagram::RecentreAll(wxDC &dc)
{
	wxNode *object_node = m_shapeList->GetFirst();
	while (object_node)
	{
		wxShape *obj = (wxShape *)object_node->GetData();
		obj->Recentre(dc);
		object_node = object_node->GetNext();
	}
}

// Input/output
#if wxUSE_PROLOGIO
bool wxDiagram::SaveFile(const wxString &filename)
{
	wxBeginBusyCursor();

	wxExprDatabase *database = new wxExprDatabase;

	// First write the diagram type
	wxExpr *header = new wxExpr(wxT("diagram"));
	OnHeaderSave(*database, *header);

	database->Append(header);

	wxNode *node = m_shapeList->GetFirst();
	while (node)
	{
		wxShape *shape = (wxShape *)node->GetData();

		if (!shape->IsKindOf(CLASSINFO(wxControlPoint)))
		{
			wxExpr *expr;
			if (shape->IsKindOf(CLASSINFO(wxLineShape)))
				expr = new wxExpr(wxT("line"));
			else
				expr = new wxExpr(wxT("shape"));

			OnShapeSave(*database, *shape, *expr);
		}
		node = node->GetNext();
	}
	OnDatabaseSave(*database);

	wxString tempFile;
	wxGetTempFileName(wxT("diag"), tempFile);
	FILE *file = fopen(tempFile.mb_str(wxConvFile), "w");
	if (! file)
	{
		wxEndBusyCursor();
		delete database;
		return FALSE;
	}

	database->Write(file);
	fclose(file);
	delete database;

	/*
	  // Save backup
	  if (FileExists(filename))
	  {
	    char buf[400];
	#ifdef __X__
	    sprintf(buf, "%s.bak", filename);
	#endif
	#ifdef __WXMSW__
	    sprintf(buf, "_diagram.bak");
	#endif
	    if (FileExists(buf)) wxRemoveFile(buf);
	    if (!wxRenameFile(filename, buf))
	    {
	      wxCopyFile(filename, buf);
	      wxRemoveFile(filename);
	    }
	  }
	*/

	// Copy the temporary file to the correct filename
	if (!wxRenameFile(tempFile, filename))
	{
		wxCopyFile(tempFile, filename);
		wxRemoveFile(tempFile);
	}

	wxEndBusyCursor();
	return TRUE;
}

bool wxDiagram::LoadFile(const wxString &filename)
{
	wxBeginBusyCursor();

	wxExprDatabase database(wxExprInteger, wxT("id"));
	if (!database.Read(filename))
	{
		wxEndBusyCursor();
		return FALSE;
	}

	DeleteAllShapes();

	database.BeginFind();
	wxExpr *header = database.FindClauseByFunctor(wxT("diagram"));

	if (header)
		OnHeaderLoad(database, *header);

	// Scan through all clauses and register the ids
	wxNode *node = database.GetFirst();
	while (node)
	{
		wxExpr *clause = (wxExpr *)node->GetData();
		long id = -1;
		clause->GetAttributeValue(wxT("id"), id);
		wxRegisterId(id);
		node = node->GetNext();
	}

	ReadNodes(database);
	ReadContainerGeometry(database);
	ReadLines(database);

	OnDatabaseLoad(database);

	wxEndBusyCursor();

	return TRUE;
}

void wxDiagram::ReadNodes(wxExprDatabase &database)
{
	// Find and create the node images
	database.BeginFind();
	wxExpr *clause = database.FindClauseByFunctor(wxT("shape"));
	while (clause)
	{
		wxChar *type = NULL;
		long parentId = -1;

		clause->AssignAttributeValue(wxT("type"), &type);
		clause->AssignAttributeValue(wxT("parent"), &parentId);
		wxClassInfo *classInfo = wxClassInfo::FindClass(type);
		if (classInfo)
		{
			wxShape *shape = (wxShape *)classInfo->CreateObject();
			OnShapeLoad(database, *shape, *clause);

			shape->SetCanvas(GetCanvas());
			shape->Show(TRUE);

			m_shapeList->Append(shape);

			// If child of composite, link up
			if (parentId > -1)
			{
				wxExpr *parentExpr = database.HashFind(wxT("shape"), parentId);
				if (parentExpr && parentExpr->GetClientData())
				{
					wxShape *parent = (wxShape *)parentExpr->GetClientData();
					shape->SetParent(parent);
					parent->GetChildren().Append(shape);
				}
			}

			clause->SetClientData(shape);
		}
		if (type)
			delete[] type;

		clause = database.FindClauseByFunctor(wxT("shape"));
	}
	return;
}

void wxDiagram::ReadLines(wxExprDatabase &database)
{
	database.BeginFind();
	wxExpr *clause = database.FindClauseByFunctor(wxT("line"));
	while (clause)
	{
		wxString type;
		long parentId = -1;

		clause->GetAttributeValue(wxT("type"), type);
		clause->GetAttributeValue(wxT("parent"), parentId);
		wxClassInfo *classInfo = wxClassInfo::FindClass(type);
		if (classInfo)
		{
			wxLineShape *shape = (wxLineShape *)classInfo->CreateObject();
			shape->Show(TRUE);

			OnShapeLoad(database, *shape, *clause);
			shape->SetCanvas(GetCanvas());

			long image_to = -1;
			long image_from = -1;
			clause->GetAttributeValue(wxT("to"), image_to);
			clause->GetAttributeValue(wxT("from"), image_from);

			wxExpr *image_to_expr = database.HashFind(wxT("shape"), image_to);

			if (!image_to_expr)
			{
				// Error
			}
			wxExpr *image_from_expr = database.HashFind(wxT("shape"), image_from);

			if (!image_from_expr)
			{
				// Error
			}

			if (image_to_expr && image_from_expr)
			{
				wxShape *image_to_object = (wxShape *)image_to_expr->GetClientData();
				wxShape *image_from_object = (wxShape *)image_from_expr->GetClientData();

				if (image_to_object && image_from_object)
				{
					image_from_object->AddLine(shape, image_to_object, shape->GetAttachmentFrom(), shape->GetAttachmentTo());
				}
			}
			clause->SetClientData(shape);

			m_shapeList->Append(shape);
		}

		clause = database.FindClauseByFunctor(wxT("line"));
	}
}

// Containers have divisions that reference adjoining divisions,
// so we need a separate pass to link everything up.
// Also used by Symbol Library.
void wxDiagram::ReadContainerGeometry(wxExprDatabase &database)
{
	database.BeginFind();
	wxExpr *clause = database.FindClauseByFunctor(wxT("shape"));
	while (clause)
	{
		wxShape *image = (wxShape *)clause->GetClientData();
		if (image && image->IsKindOf(CLASSINFO(wxCompositeShape)))
		{
			wxCompositeShape *composite = (wxCompositeShape *)image;
			wxExpr *divisionExpr = NULL;

			// Find the list of divisions in the composite
			clause->GetAttributeValue(wxT("divisions"), &divisionExpr);
			if (divisionExpr)
			{
				int i = 0;
				wxExpr *idExpr = divisionExpr->Nth(i);
				while (idExpr)
				{
					long divisionId = idExpr->IntegerValue();
					wxExpr *childExpr = database.HashFind(wxT("shape"), divisionId);
					if (childExpr && childExpr->GetClientData())
					{
						wxDivisionShape *child = (wxDivisionShape *)childExpr->GetClientData();
						composite->GetDivisions().Append(child);

						// Find the adjoining shapes
						long leftSideId = -1;
						long topSideId = -1;
						long rightSideId = -1;
						long bottomSideId = -1;
						childExpr->GetAttributeValue(wxT("left_side"), leftSideId);
						childExpr->GetAttributeValue(wxT("top_side"), topSideId);
						childExpr->GetAttributeValue(wxT("right_side"), rightSideId);
						childExpr->GetAttributeValue(wxT("bottom_side"), bottomSideId);
						if (leftSideId > -1)
						{
							wxExpr *leftExpr = database.HashFind(wxT("shape"), leftSideId);
							if (leftExpr && leftExpr->GetClientData())
							{
								wxDivisionShape *leftSide = (wxDivisionShape *)leftExpr->GetClientData();
								child->SetLeftSide(leftSide);
							}
						}
						if (topSideId > -1)
						{
							wxExpr *topExpr = database.HashFind(wxT("shape"), topSideId);
							if (topExpr && topExpr->GetClientData())
							{
								wxDivisionShape *topSide = (wxDivisionShape *)topExpr->GetClientData();
								child->SetTopSide(topSide);
							}
						}
						if (rightSideId > -1)
						{
							wxExpr *rightExpr = database.HashFind(wxT("shape"), rightSideId);
							if (rightExpr && rightExpr->GetClientData())
							{
								wxDivisionShape *rightSide = (wxDivisionShape *)rightExpr->GetClientData();
								child->SetRightSide(rightSide);
							}
						}
						if (bottomSideId > -1)
						{
							wxExpr *bottomExpr = database.HashFind(wxT("shape"), bottomSideId);
							if (bottomExpr && bottomExpr->GetClientData())
							{
								wxDivisionShape *bottomSide = (wxDivisionShape *)bottomExpr->GetClientData();
								child->SetBottomSide(bottomSide);
							}
						}
					}
					i ++;
					idExpr = divisionExpr->Nth(i);
				}
			}
		}

		clause = database.FindClauseByFunctor(wxT("shape"));
	}
}

// Allow for modifying file
bool wxDiagram::OnDatabaseLoad(wxExprDatabase &WXUNUSED(db))
{
	return TRUE;
}

bool wxDiagram::OnDatabaseSave(wxExprDatabase &WXUNUSED(db))
{
	return TRUE;
}

bool wxDiagram::OnShapeSave(wxExprDatabase &db, wxShape &shape, wxExpr &expr)
{
	shape.WriteAttributes(&expr);
	db.Append(&expr);

	if (shape.IsKindOf(CLASSINFO(wxCompositeShape)))
	{
		wxNode *node = shape.GetChildren().GetFirst();
		while (node)
		{
			wxShape *childShape = (wxShape *)node->GetData();
			wxExpr *childExpr = new wxExpr(wxT("shape"));
			OnShapeSave(db, *childShape, *childExpr);
			node = node->GetNext();
		}
	}

	return TRUE;
}

bool wxDiagram::OnShapeLoad(wxExprDatabase &WXUNUSED(db), wxShape &shape, wxExpr &expr)
{
	shape.ReadAttributes(&expr);
	return TRUE;
}

bool wxDiagram::OnHeaderSave(wxExprDatabase &WXUNUSED(db), wxExpr &WXUNUSED(expr))
{
	return TRUE;
}

bool wxDiagram::OnHeaderLoad(wxExprDatabase &WXUNUSED(db), wxExpr &WXUNUSED(expr))
{
	return TRUE;
}

#endif

void wxDiagram::SetCanvas(wxShapeCanvas *can)
{
	m_diagramCanvas = can;
}

// Find a shape by its id
wxShape *wxDiagram::FindShape(long id) const
{
	wxNode *node = GetShapeList()->GetFirst();
	while (node)
	{
		wxShape *shape = (wxShape *) node->GetData();
		if (shape->GetId() == id)
			return shape;
		node = node->GetNext();
	}
	return NULL;
}


//// Crossings classes

wxLineCrossings::wxLineCrossings()
{
}

wxLineCrossings::~wxLineCrossings()
{
	ClearCrossings();
}

void wxLineCrossings::FindCrossings(wxDiagram &diagram)
{
	ClearCrossings();
	wxNode *node1 = diagram.GetShapeList()->GetFirst();
	while (node1)
	{
		wxShape *shape1 = (wxShape *) node1->GetData();
		if (shape1->IsKindOf(CLASSINFO(wxLineShape)))
		{
			wxLineShape *lineShape1 = (wxLineShape *) shape1;
			// Iterate through the segments
			wxList *pts1 = lineShape1->GetLineControlPoints();
			size_t i;
			for (i = 0; i < (pts1->GetCount() - 1); i++)
			{
				wxRealPoint *pt1_a = (wxRealPoint *) (pts1->Item(i)->GetData());
				wxRealPoint *pt1_b = (wxRealPoint *) (pts1->Item(i + 1)->GetData());

				// Now we iterate through the segments again

				wxNode *node2 = diagram.GetShapeList()->GetFirst();
				while (node2)
				{
					wxShape *shape2 = (wxShape *) node2->GetData();

					// Assume that the same line doesn't cross itself
					if (shape2->IsKindOf(CLASSINFO(wxLineShape)) && (shape1 != shape2))
					{
						wxLineShape *lineShape2 = (wxLineShape *) shape2;
						// Iterate through the segments
						wxList *pts2 = lineShape2->GetLineControlPoints();
						int j;
						for (j = 0; j < (int) (pts2->GetCount() - 1); j++)
						{
							wxRealPoint *pt2_a = (wxRealPoint *) (pts2->Item(j)->GetData());
							wxRealPoint *pt2_b = (wxRealPoint *) (pts2->Item(j + 1)->GetData());

							// Now let's see if these two segments cross.
							double ratio1, ratio2;
							oglCheckLineIntersection(pt1_a->x, pt1_a->y, pt1_b->x, pt1_b->y,
							                         pt2_a->x, pt2_a->y, pt2_b->x, pt2_b->y,
							                         & ratio1, & ratio2);

							if ((ratio1 < 1.0) && (ratio1 > -1.0))
							{
								// Intersection!
								wxLineCrossing *crossing = new wxLineCrossing;
								crossing->m_intersect.x = (pt1_a->x + (pt1_b->x - pt1_a->x) * ratio1);
								crossing->m_intersect.y = (pt1_a->y + (pt1_b->y - pt1_a->y) * ratio1);

								crossing->m_pt1 = * pt1_a;
								crossing->m_pt2 = * pt1_b;
								crossing->m_pt3 = * pt2_a;
								crossing->m_pt4 = * pt2_b;

								crossing->m_lineShape1 = lineShape1;
								crossing->m_lineShape2 = lineShape2;

								m_crossings.Append(crossing);
							}
						}
					}
					node2 = node2->GetNext();
				}
			}
		}

		node1 = node1->GetNext();
	}
}

void wxLineCrossings::DrawCrossings(wxDiagram &WXUNUSED(diagram), wxDC &dc)
{
	dc.SetBrush(*wxTRANSPARENT_BRUSH);

	long arcWidth = 8;

	wxNode *node = m_crossings.GetFirst();
	while (node)
	{
		wxLineCrossing *crossing = (wxLineCrossing *) node->GetData();
//        dc.DrawEllipse((long) (crossing->m_intersect.x - (arcWidth/2.0) + 0.5), (long) (crossing->m_intersect.y - (arcWidth/2.0) + 0.5),
//           arcWidth, arcWidth);


		// Let's do some geometry to find the points on either end of the arc.
		/*

		(x1, y1)
		    |\
		    | \
		    |  \
		    |   \
		    |    \
		    |    |\ c    c1
		    |  a | \
		         |  \
		    |     -  x <-- centre of arc
		 a1 |     b  |\
		    |        | \ c2
		    |     a2 |  \
		    |         -  \
		    |         b2  \
		    |              \
		    |_______________\ (x2, y2)
		          b1

		*/

		double a1 = wxMax(crossing->m_pt1.y, crossing->m_pt2.y) - wxMin(crossing->m_pt1.y, crossing->m_pt2.y) ;
		double b1 = wxMax(crossing->m_pt1.x, crossing->m_pt2.x) - wxMin(crossing->m_pt1.x, crossing->m_pt2.x) ;
		double c1 = sqrt( (a1 * a1) + (b1 * b1) );

		double c = arcWidth / 2.0;
		double a = c * a1 / c1 ;
		double b = c * b1 / c1 ;

		// I'm not sure this is right, since we don't know which direction we should be going in - need
		// to know which way the line slopes and choose the sign appropriately.
		double arcX1 = crossing->m_intersect.x - b;
		double arcY1 = crossing->m_intersect.y - a;

		double arcX2 = crossing->m_intersect.x + b;
		double arcY2 = crossing->m_intersect.y + a;

		dc.SetPen(*wxBLACK_PEN);
		dc.DrawArc( (long) arcX1, (long) arcY1, (long) arcX2, (long) arcY2,
		            (long) crossing->m_intersect.x, (long) crossing->m_intersect.y);

		dc.SetPen(*wxWHITE_PEN);
		dc.DrawLine( (long) arcX1, (long) arcY1, (long) arcX2, (long) arcY2 );

		node = node->GetNext();
	}
}

void wxLineCrossings::ClearCrossings()
{
	wxNode *node = m_crossings.GetFirst();
	while (node)
	{
		wxLineCrossing *crossing = (wxLineCrossing *) node->GetData();
		delete crossing;
		node = node->GetNext();
	}
	m_crossings.Clear();
}

