//////////////////////////////////////////////////////////////////////////
//
// pgAdmin III - PostgreSQL Tools
//
// Copyright (C) 2002 - 2013, The pgAdmin Development Team
// This software is released under the PostgreSQL Licence
//
// hdDrawingView.cpp - Main canvas where all figures are drawn
//
//////////////////////////////////////////////////////////////////////////

#include "pgAdmin3.h"

// wxWindows headers
#include <wx/wx.h>
#include <wx/dcbuffer.h>

// App headers
#include "hotdraw/main/hdDrawingView.h"
#include "hotdraw/utilities/hdArrayCollection.h"
#include "hotdraw/main/hdDrawingEditor.h"
#include "hotdraw/utilities/hdGeometry.h"
#include "hotdraw/utilities/hdMouseEvent.h"
#include "hotdraw/tools/hdCanvasMenuTool.h"
#include "hotdraw/figures/defaultAttributes/hdFontAttribute.h"
#include "hotdraw/tools/hdSelectionTool.h"

// Images
#include "images/check.pngc"
#include "images/ddcancel.pngc"

BEGIN_EVENT_TABLE(hdDrawingView, wxScrolledWindow)
	EVT_PAINT(                     hdDrawingView::onPaint)
	EVT_MOTION(                    hdDrawingView::onMotion)
	EVT_RIGHT_DOWN(                hdDrawingView::onMouseDown)
	EVT_RIGHT_UP(                  hdDrawingView::onMouseUp)
	EVT_LEFT_DOWN(                 hdDrawingView::onMouseDown)
	EVT_LEFT_DCLICK(               hdDrawingView::onMouseDown)
	EVT_LEFT_UP(                   hdDrawingView::onMouseUp)
	EVT_ERASE_BACKGROUND(          hdDrawingView::onEraseBackGround)  //This erase flicker
	EVT_TEXT(CTL_TEXTTOOLID,       hdDrawingView::simpleTextToolChangeHandler)
	EVT_BUTTON(CTL_OKBUTTONID,     hdDrawingView::OnOkTxtButton)
	EVT_BUTTON(CTL_CANCELBUTTONID, hdDrawingView::OnCancelTxtButton)
	EVT_KEY_DOWN(				   hdDrawingView::onKeyDown)
	EVT_KEY_UP(					   hdDrawingView::onKeyUp)
END_EVENT_TABLE()


hdDrawingView::hdDrawingView(int diagram, wxWindow *ddParent, hdDrawingEditor *editor, wxSize size, hdDrawing *initialDrawing)// gqbController *controller, gqbModel *model)
	: wxScrolledWindow(ddParent, wxID_ANY, wxPoint(0, 0), size,
	                   wxHSCROLL | wxVSCROLL | wxBORDER | wxRETAINED)
{
	diagramIndex = diagram;
	drawing = initialDrawing;
	drawingEditor = editor;
	canvasSize = size;

#if wxCHECK_VERSION(2, 9, 0)
	FitInside();
#else
	SetVirtualSizeHints(canvasSize);
#endif

	// Hack to avoid selection rectangle drawing bug
	drawSelRect = false;
	// Hack to avoid event problem with simpleTextTool wxTextCrtl at EVT_TEXT event
	simpleTextToolEdit = new wxTextCtrl(this, CTL_TEXTTOOLID, wxT(""), wxDefaultPosition, wxDefaultSize, wxBORDER_NONE);
	simpleTextToolEdit->Hide();
	simpleTextFigure = NULL;
	menuFigure = NULL;
	okTxtButton = new wxBitmapButton(this, CTL_OKBUTTONID, *check_png_bmp, wxDefaultPosition, wxDefaultSize, wxBORDER_NONE);
	okTxtButton->Hide();
	cancelTxtButton = new wxBitmapButton(this, 1981, wxBitmap(*ddcancel_png_img), wxDefaultPosition, wxDefaultSize, wxBORDER_NONE);
	cancelTxtButton->Hide();
	canvasMenu = NULL;
	_tool = NULL;
	_tool = new hdSelectionTool(this);
}

hdDrawingView::~hdDrawingView()
{
	if(simpleTextToolEdit)
		delete simpleTextToolEdit;
	if(okTxtButton)
		delete okTxtButton;
	if(cancelTxtButton)
		delete cancelTxtButton;
	if(_tool)
		delete _tool;
}

void hdDrawingView::onPaint(wxPaintEvent &event)
{
	// Prepare Context for Buffered Draw
	wxPaintDC dcc(this);
	wxBufferedDC dc(&dcc, canvasSize);
	dc.Clear();
	hdIFigure *toDraw = NULL;
	hdIteratorBase *iterator = drawing->figuresEnumerator();

	while(iterator->HasNext())
	{
		toDraw = (hdIFigure *)iterator->Next();
		if(toDraw->isSelected(diagramIndex))
			toDraw->drawSelected(dc, this);
		else
			toDraw->draw(dc, this);
	}

	delete iterator;

	hdIHandle *tmpHandle = NULL;
	hdIteratorBase *selectionIterator = drawing->selectionFigures();//selection->createIterator();
	while(selectionIterator->HasNext())
	{
		toDraw = (hdIFigure *)selectionIterator->Next();
		hdIteratorBase *handlesIterator = toDraw->handlesEnumerator()->createIterator();
		while(handlesIterator->HasNext())
		{
			tmpHandle = (hdIHandle *)handlesIterator->Next();
			tmpHandle->draw(dc, this);
		}
		delete handlesIterator;
	}

	delete selectionIterator;

	//Hack to avoid selection rectangle drawing bug
	if (drawSelRect)
	{
		wxPen *pen = wxThePenList->FindOrCreatePen(*wxRED, 1, wxDOT);
		dc.SetPen(*pen);
		wxBrush *brush = wxTheBrushList->FindOrCreateBrush(*wxRED, wxTRANSPARENT);
		dc.SetBackground(*brush);
		dc.SetBackgroundMode(wxTRANSPARENT);
		//Adjust points before drawing
		wxPoint selAjustedPoints[5];
		CalcScrolledPosition(selPoints[0].x, selPoints[0].y, &selAjustedPoints[0].x, &selAjustedPoints[0].y);
		CalcScrolledPosition(selPoints[1].x, selPoints[1].y, &selAjustedPoints[1].x, &selAjustedPoints[1].y);
		CalcScrolledPosition(selPoints[2].x, selPoints[2].y, &selAjustedPoints[2].x, &selAjustedPoints[2].y);
		CalcScrolledPosition(selPoints[3].x, selPoints[3].y, &selAjustedPoints[3].x, &selAjustedPoints[3].y);
		CalcScrolledPosition(selPoints[4].x, selPoints[4].y, &selAjustedPoints[4].x, &selAjustedPoints[4].y);
		//Draw
		dc.DrawLines(5, selAjustedPoints, 0, 0);
		drawSelRect = false;
	}
}

//Hack to avoid selection rectangle drawing bug
void hdDrawingView::disableSelRectDraw()
{
	drawSelRect = false;
}

//Hack to avoid selection rectangle drawing bug
void hdDrawingView::setSelRect(hdRect &selectionRect)
{
	//Create rectangle lines to avoid non transparent brush for filling bug in wxwidgets
	selPoints[0].x = selectionRect.x;
	selPoints[0].y = selectionRect.y;
	selPoints[1].x = selectionRect.x + selectionRect.width;
	selPoints[1].y = selectionRect.y;
	selPoints[2].x = selectionRect.x + selectionRect.width;
	selPoints[2].y = selectionRect.y + selectionRect.height;
	selPoints[3].x = selectionRect.x;
	selPoints[3].y = selectionRect.y + selectionRect.height;
	selPoints[4].x = selectionRect.x;
	selPoints[4].y = selectionRect.y;
	drawSelRect = true;
}

// Overwrite and disable onEraseBackground Event to avoid Flicker
void hdDrawingView::onEraseBackGround(wxEraseEvent &event)
{
}

hdMultiPosRect hdDrawingView::getVisibleArea()
{
	int x, y, w, h;
	GetViewStart(&x, &y);
	GetClientSize(&w, &h);
	hdMultiPosRect visibleArea(x, y, w, h);
	return visibleArea;
}

hdMultiPosRect hdDrawingView::getVirtualSize()
{
	int w, h;
	GetVirtualSize(&w, &h);
	hdMultiPosRect virtualSize(0, 0, w, h);
	return virtualSize;
}

void hdDrawingView::ScrollToMakeVisible(hdPoint p)
{
	//implement this function
}

void hdDrawingView::ScrollToMakeVisible (hdRect r)
{
	//implement this function
}

hdIHandle *hdDrawingView::findHandle(int posIdx, double x, double y)
{
	hdIFigure *tmpFigure = NULL;
	hdIHandle *tmpHandle = NULL, *out = NULL;


	//Look for handles at each figure in SelectionEnumerator
	hdIteratorBase *selectionIterator = drawing->selectionFigures(); //selection->createIterator();
	while(selectionIterator->HasNext())
	{
		tmpFigure = (hdIFigure *)selectionIterator->Next();
		hdIteratorBase *handlesIterator = tmpFigure->handlesEnumerator()->createIterator();
		while(handlesIterator->HasNext())
		{
			tmpHandle = (hdIHandle *)handlesIterator->Next();
			if(tmpHandle->containsPoint(posIdx, x, y))
			{
				out = tmpHandle;
				break;
			}
		}
		delete handlesIterator;
	}
	delete selectionIterator;
	return out;
}

hdDrawing *hdDrawingView::getDrawing()
{
	return drawing;
}

void hdDrawingView::onMotion(wxMouseEvent &event)
{
// simple hack to don't update so frequently the canvas and mouse events
// Should be changed for a better one using time intervals not a simple counter
	static int simpleOptimization = 0;
	simpleOptimization++;
	if(simpleOptimization > 0)
	{
		simpleOptimization = 0;

		hdMouseEvent ddEvent = hdMouseEvent(event, this);
		if(event.Dragging())
		{
			_tool->mouseDrag(ddEvent);
			this->Refresh();			//only a dragging event on montion will change model
		}
		else
		{
			_tool->mouseMove(ddEvent);
		}
	}
}

void hdDrawingView::onMouseDown(wxMouseEvent &event)
{
	this->AcceptsFocus();
	this->SetFocus();
	startDrag = event.GetPosition();
	hdMouseEvent ddEvent = hdMouseEvent(event, this);
	_tool->mouseDown(ddEvent);
	this->Refresh();
}

void hdDrawingView::onMouseUp(wxMouseEvent &event)
{
	this->AcceptsFocus();
	this->SetFocus();
	hdMouseEvent ddEvent = hdMouseEvent(event, this);
	_tool->mouseUp(ddEvent);
	this->Refresh();
}

void hdDrawingView::onKeyDown(wxKeyEvent &event)
{
	hdKeyEvent ddEvent = hdKeyEvent(event, this);
	_tool->keyDown(ddEvent);
	this->Refresh();
}

void hdDrawingView::onKeyUp(wxKeyEvent &event)
{
	hdKeyEvent ddEvent = hdKeyEvent(event, this);
	_tool->keyUp(ddEvent);
	this->Refresh();
}

//Hack to avoid event problem with simpleTextTool wxTextCrtl at EVT_TEXT event
void hdDrawingView::setSimpleTextToolFigure(hdSimpleTextFigure *figure, bool onlySetFigure)
{
	simpleTextFigure = figure;
	menuFigure = NULL;
	if(simpleTextFigure && !onlySetFigure)
	{
		oldText = simpleTextFigure->getText();
		simpleTextToolEdit->SetValue(simpleTextFigure->getText());
		simpleTextToolEdit->SelectAll();
	}
}

//Hack to allow use (events) of wxmenu inside a tool Generic Way
void hdDrawingView::setMenuToolFigure(hdAbstractMenuFigure *figure)
{
	menuFigure = figure;
	simpleTextFigure = NULL;
}

//Hack to avoid event problem with simpleTextTool wxTextCrtl at EVT_TEXT event
void hdDrawingView::OnOkTxtButton(wxCommandEvent &event)
{
	_tool->deactivate(this);
	simpleTextToolEdit->Hide();
	okTxtButton->Hide();
	cancelTxtButton->Hide();
	setSimpleTextToolFigure(NULL);
}

//Hack to avoid event problem with simpleTextTool wxTextCrtl at EVT_TEXT event
void hdDrawingView::OnCancelTxtButton(wxCommandEvent &event)
{
	simpleTextToolEdit->SetValue(oldText);
	_tool->deactivate(this);
	simpleTextToolEdit->Hide();
	okTxtButton->Hide();
	cancelTxtButton->Hide();
	setSimpleTextToolFigure(NULL);
}

//Hack to avoid event problem with simpleTextTool wxTextCrtl at EVT_TEXT event (when text is set at edit it generate this event not sure ????)
void hdDrawingView::simpleTextToolChangeHandler(wxCommandEvent &event)
{
	if(!simpleTextToolEdit->IsModified() && simpleTextFigure)
	{
		simpleTextFigure->setText(simpleTextToolEdit->GetValue());
		//getFontMetrics
		int width, height;
		wxWindowDC dc(this);
		dc.SetFont(*hdFontAttribute::defaultFont);
		if(simpleTextFigure->getText(true).length() > 5)
			dc.GetTextExtent(simpleTextFigure->getText(true), &width, &height);
		else
			dc.GetTextExtent(wxT("EMPTY"), &width, &height);
		//recalculateDisplayBox
		hdGeometry g;
		simpleTextFigure->displayBox().width = g.max(width, 10) + simpleTextFigure->getPadding();
		simpleTextFigure->displayBox().height = g.max(height, 10) + simpleTextFigure->getPadding();
		//calculateSizeEntry
		hdPoint p = simpleTextFigure->displayBox().GetPosition(this->diagramIndex);
		CalcScrolledPosition(p.x, p.y, &p.x, &p.y);
		simpleTextToolEdit->SetPosition(p);
		simpleTextToolEdit->SetSize(simpleTextFigure->displayBox().GetSize());
		okTxtButton->SetPosition(wxPoint(p.x + simpleTextToolEdit->GetSize().GetWidth() + 4, p.y));
		cancelTxtButton->SetPosition(wxPoint(okTxtButton->GetPosition().x + okTxtButton->GetSize().GetWidth() + 4, p.y));
	}
	else if(!simpleTextFigure)
	{
		wxMessageDialog *error = new wxMessageDialog(NULL, wxT("Error locating hdSimpleTextTool figure"), wxT("Error!"), wxOK | wxICON_ERROR);
		error->ShowModal();
		delete error;
	}
	event.Skip();
}

//Hack to avoid event problem with simpleTextTool wxTextCrtl at EVT_TEXT event
wxTextCtrl *hdDrawingView::getSimpleTextToolEdit()
{
	return simpleTextToolEdit;
}

//Hack to avoid event problem with simpleTextTool wxTextCrtl at EVT_TEXT event
wxBitmapButton *hdDrawingView::getOkTxt()
{
	return okTxtButton;
}

//Hack to avoid event problem with simpleTextTool wxTextCrtl at EVT_TEXT event
wxBitmapButton *hdDrawingView::getCancelTxt()
{
	return cancelTxtButton;
}

//Hack to allow use (events) of wxmenu inside a tool like simpletexttool
void hdDrawingView::OnGenericPopupClick(wxCommandEvent &event)
{

	if(canvasMenu)
		canvasMenu->OnGenericPopupClick(event, this);
	else if(menuFigure)
		menuFigure->OnGenericPopupClick(event, this);
	else if(simpleTextFigure)
		simpleTextFigure->OnGenericPopupClick(event, this);

	simpleTextFigure = NULL;
	menuFigure = NULL;
	canvasMenu = NULL;

	event.Skip();
}

//Hack to allow use (events) of wxmenu inside a tool like simpletexttool
void hdDrawingView::connectPopUpMenu(wxMenu &mnu)
{
	// Connect the main menu
	mnu.Connect(wxEVT_COMMAND_MENU_SELECTED,
	            (wxObjectEventFunction) (wxEventFunction) (wxCommandEventFunction) &hdDrawingView::OnGenericPopupClick,
	            NULL,
	            this);

	// Connect all submenus
	wxMenuItem *item;
	wxMenuItemList list = mnu.GetMenuItems();
	for (unsigned int index = 0; index < list.GetCount(); index++)
	{
		wxMenuItemList::compatibility_iterator node = list.Item(index);
		item = (wxMenuItem *) node->GetData();
		if (item->IsSubMenu())
		{
			wxMenu *submenu = item->GetSubMenu();
			submenu->Connect(wxEVT_COMMAND_MENU_SELECTED,
			                 (wxObjectEventFunction) (wxEventFunction) (wxCommandEventFunction) &hdDrawingView::OnGenericPopupClick,
			                 NULL,
			                 this);
		}
	}
}

//Hack to allow use (events) of wxmenu inside a tool without a figure, Generic Way
void hdDrawingView::setCanvasMenuTool(hdCanvasMenuTool *menuTool)
{
	canvasMenu = menuTool;
}

hdDrawingEditor *hdDrawingView::editor()
{
	return drawingEditor;
}

bool hdDrawingView::AcceptsFocus() const
{
	return true;
}

void hdDrawingView::setTool(hdITool *tool)
{
	if(_tool)
		delete _tool;
	_tool = tool;
}

void hdDrawingView::createViewMenu(wxMenu &mnu)
{
	wxMenuItem *item;
	item = mnu.AppendCheckItem(1000, _("Sample Item"));
	item->Check(true);
}

void hdDrawingView::OnGenericViewPopupClick(wxCommandEvent &event)
{
	switch(event.GetId())
	{
		case 1000:
			wxMessageBox(_("Sample menu item"), _("Sample"), wxOK, this);
	}
}

void hdDrawingView::notifyChanged()
{
	drawingEditor->notifyChanged();
}
