//////////////////////////////////////////////////////////////////////////
//
// pgAdmin III - PostgreSQL Tools
//
// Copyright (C) 2002 - 2016, The pgAdmin Development Team
// This software is released under the PostgreSQL Licence
//
// ctlTree.cpp - wxTreeCtrl containing pgObjects
//
//////////////////////////////////////////////////////////////////////////

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

// App headers
#include "pgAdmin3.h"
#include "ctl/ctlTree.h"
#include "schema/pgObject.h"
#include "schema/pgCollection.h"
#include "schema/pgServer.h"

BEGIN_EVENT_TABLE(ctlTree, wxTreeCtrl)
	EVT_CHAR(ctlTree::OnChar)
	EVT_MOUSE_EVENTS(ctlTree::OnMouse)
END_EVENT_TABLE()


wxTreeItemId ctlTree::FindItem(const wxTreeItemId &idParent, const wxString &prefixOrig, const bool full )
{
	// match is case insensitive as this is more convenient to the user: having
	// to press Shift-letter to go to the item starting with a capital letter
	// would be too bothersome
	wxString prefix = prefixOrig.Lower();

	// determine the starting point: we shouldn't take the current item (this
	// allows to switch between two items starting with the same letter just by
	// pressing it) but we shouldn't jump to the next one if the user is
	// continuing to type as otherwise he might easily skip the item he wanted
	wxTreeItemId id = idParent;
	int le=prefix.length();
	if ( prefix.length() == 1 )
	{
		wxCookieType cookie;
		if (HasChildren(id))
			id = GetFirstChild(id, cookie);
		else
		{
			// Try a sibling of this or ancestor instead
			wxTreeItemId p = id;
			wxTreeItemId toFind;
			do
			{
				toFind = GetNextSibling(p);
				p = GetItemParent(p);
			}
			while (p.IsOk() && !toFind.IsOk());
			id = toFind;
		}
	}

	// look for the item starting with the given prefix after it
	while ( id.IsOk() &&
	        ( ( GetItemText(id) == wxT("Dummy") && !GetItemData(id) ) ||
			!( (!full&&GetItemText(id).Lower().StartsWith(prefix)) || 
			(full && //GetItemText(id).Lower().StartsWith(prefix) && GetItemText(id).length()==le)
			           GetObject(id)!=NULL&&GetObject(id)->GetName()==prefix )
			)
			))
	{
		wxCookieType cookie;
		if ( HasChildren(id) )
			id = GetFirstChild(id, cookie);
		else
		{
			// Try a sibling of this or ancestor instead
			wxTreeItemId p = id;
			wxTreeItemId toFind;
			do
			{
				toFind = GetNextSibling(p);
				p = GetItemParent(p);
			}
			while (p.IsOk() && !toFind.IsOk());
			id = toFind;
		}
	}

	// if we haven't found anything...
	if ( !id.IsOk() )
	{
		// ... wrap to the beginning
		id = GetRootItem();
		if ( HasFlag(wxTR_HIDE_ROOT) )
		{
			wxCookieType cookie;
			// can't select virtual root
			if ( HasChildren(id) )
				id = GetFirstChild(id, cookie);
			else
			{
				// Try a sibling of this or ancestor instead
				wxTreeItemId p = id;
				wxTreeItemId toFind;
				do
				{
					toFind = GetNextSibling(p);
					p = GetItemParent(p);
				}
				while (p.IsOk() && !toFind.IsOk());
				id = toFind;
			}
		}

		// and try all the items (stop when we get to the one we started from)
		while ( id.IsOk() && id != idParent &&
		        (( GetItemText(id) == wxT("Dummy") && !GetItemData(id) ) ||
		         !((!full&&GetItemText(id).Lower().StartsWith(prefix)) || 
				   (full && //GetItemText(id).Lower().StartsWith(prefix) && GetItemText(id).length()==le)
							GetObject(id)!=NULL&&GetObject(id)->GetName()==prefix )
				  )
				))
		{
			wxCookieType cookie;
			if ( HasChildren(id) )
				id = GetFirstChild(id, cookie);
			else
			{
				// Try a sibling of this or ancestor instead
				wxTreeItemId p = id;
				wxTreeItemId toFind;
				do
				{
					toFind = GetNextSibling(p);
					p = GetItemParent(p);
				}
				while (p.IsOk() && !toFind.IsOk());
				id = toFind;
			}
		}
		// If we haven't found the item, id.IsOk() will be false, as per
		// documentation
	}

	return id;
}
wxTreeItemId ctlTree::GetVerticalItem(wxPoint& pt) {
	int flags = 0;
	wxTreeItemId item = DoTreeHitTest(pt, flags);
	if ((flags & wxTREE_HITTEST_ONITEMINDENT) == wxTREE_HITTEST_ONITEMINDENT) {
		if (item) {
			wxRect r;
			wxTreeItemId itemParent = item;
			GetBoundingRect(itemParent, r, true);
			r.x = r.x - 19;
			wxTreeItemId prev;
			bool ex = false;
			while (!ex) {
				ex = true;
				prev = itemParent;
				itemParent = GetItemParent(itemParent);
				//this->ScrollTo(itemParent);
				r.x = r.x - 19;
				if (r.x >= pt.x) ex = false;
			}
			if (prev) return (prev);
		}
		//wxTreeEvent	nevent(wxEVT_TREE_ITEM_MIDDLE_CLICK, this, item);
		//nevent.m_pointDrag = CalcScrolledPosition(pt);
		//event.Skip(!GetEventHandler()->ProcessEvent(nevent));
		
	}
	return NULL;
}
void ctlTree::DrawDbName(const wxTreeItemId& item) {
	if (item && settings->GetVisibleDbNameTree()) {
		wxRect r;
		GetBoundingRect(item, r, false);
		wxTreeItemId prev;
		wxString name = "";
		pgObject* o = GetObject(item);
		if (o) {
			pgConn* conn = o->GetConnection();
			pgServer* s = o->GetServer();
			if (conn)
				name = conn->GetName().BeforeFirst(' ');
			if (name.IsEmpty()) {
				return ;
			}
			wxColour c;
			wxClientDC dc(this);
				if (s) {
					if (!(s->GetColour().IsEmpty())) {
						c=s->GetColour();
						dc.SetBrush(*wxTheBrushList->FindOrCreateBrush(c));
					} else
						dc.SetBrush(*wxYELLOW_BRUSH);
				}
//			pgServer* s = wxColour(server->GetColour();
			
			wxRect orig;
//			dc.SetBrush(*wxYELLOW_BRUSH);
			dc.SetPen(*wxTRANSPARENT_PEN);

			wxString str = wxString::Format("%s", name);
			wxSize sz = dc.GetTextExtent(str);
			sz.SetWidth(sz.GetX() + 3);
			r.SetWidth(sz.GetX() + 5);
			orig = r;
			orig.Deflate(2);
			orig.SetLeft(orig.GetLeft() + (orig.GetWidth() - sz.GetWidth()));
			orig.SetHeight(sz.GetHeight());
			orig.SetWidth(sz.GetWidth());
			dc.DrawRoundedRectangle(orig, 2);

			dc.DrawText(name, wxPoint(orig.x + 2, orig.y + 1));
		}

		//Update();
		//return;
	}

}
void ctlTree::OnMouse(wxMouseEvent& event)
{
	wxPoint pt = event.GetPosition();
	int flags = 0;
	wxTreeItemId item = DoTreeHitTest(pt, flags);
	if (isJumpRoot) {
		if (event.LeftDown())
		{
			if ((flags & wxTREE_HITTEST_ONITEMINDENT) == wxTREE_HITTEST_ONITEMINDENT) {
				if (item) {
					wxRect r;
					wxTreeItemId itemParent = item;
					GetBoundingRect(itemParent, r, false);
					wxTreeItemId prev;
					prev = GetVerticalItem(pt);
					if (prev) SelectItem(prev);
					return;
				}
				//wxTreeEvent	nevent(wxEVT_TREE_ITEM_MIDDLE_CLICK, this, item);
				//nevent.m_pointDrag = CalcScrolledPosition(pt);
				//event.Skip(!GetEventHandler()->ProcessEvent(nevent));
				return;
			}

		}
		else {
			if ((flags & wxTREE_HITTEST_ONITEMINDENT) == wxTREE_HITTEST_ONITEMINDENT && (item == GetSelection())) {
				wxRect r;
				GetBoundingRect(item, r, true);
				r.width = r.x - 19;
				r.x = r.x - 19;
				if (r.x < 10) return;
				wxTreeItemId prev;
				wxTreeItemId itemParent = item;
				wxImageList* list = this->GetImageList();
				wxClientDC dc(this);
				bool ex = false;
				while (!ex) {
					//ex = true;
					prev = itemParent;
					itemParent = GetItemParent(itemParent);
					//this->ScrollTo(itemParent);
					if (!itemParent.IsOk()) break;
					r.x = r.x - 19;
					//if (r.x >= pt.x) ex = false;
					int image = this->GetItemImage(itemParent);
					if (image >= 0) {
						dc.SetClippingRegion(r.x + 1 - 19, r.y + 1,
							16, 16);
						list->Draw(image, dc,
							r.x + 1 - 19,
							r.y + 1,
							wxIMAGELIST_DRAW_TRANSPARENT
						);
						dc.DestroyClippingRegion();
					}
				}
				//			dc.SetBrush(*wxRED);
				//			dc.SetPen(*wxTRANSPARENT_PEN);
				//			dc.DrawRectangle(r);
				Update();
				return;

			}
		}
	}
	event.Skip();

}
void ctlTree::OnChar(wxKeyEvent &event)
{
	int keyCode = event.GetKeyCode();
	if ( !event.HasModifiers() &&
	        ((keyCode >= '0' && keyCode <= '9') ||
	         (keyCode >= 'a' && keyCode <= 'z') ||
	         (keyCode >= 'A' && keyCode <= 'Z')))
	{
		wxTreeItemId currItem = GetSelection();

		if (!currItem.IsOk())
			return;

		wxTreeItemId matchItem;
		wxTreeItemData* data = GetItemData(currItem);
		// Set the item colour
		if (data)
		{
			if (((pgObject*)data)->GetMetaType() == PGM_SERVER)
			{
				wxString prefix = m_findPrefix + (wxChar)keyCode;
				wxTreeItemId id= GetNextSibling(currItem);
				while (id.IsOk() &&
					((GetItemText(id) == wxT("Dummy") && !GetItemData(id)) ||
						!((GetItemText(id).Lower().StartsWith(prefix))
							)
					))
				{
					id = GetNextSibling(id);
				}
				if (id.IsOk()) matchItem = id;
			} else
				matchItem = FindItem(currItem, m_findPrefix + (wxChar)keyCode);
		}

		

		if ( matchItem.IsOk() )
		{
			EnsureVisible(matchItem);
			SelectItem(matchItem);

			m_findPrefix += (wxChar)keyCode;

			// also start the timer to reset the current prefix if the user
			// doesn't press any more alnum keys soon -- we wouldn't want
			// to use this prefix for a new item search
			if ( !m_findTimer )
			{
				m_findTimer = new ctlTreeFindTimer(this);
			}

			m_findTimer->Start(ctlTreeFindTimer::CTLTREE_DELAY, wxTIMER_ONE_SHOT);
			return;
		}
	}
	else
	{
		event.Skip(true);
	}
}

ctlTree::ctlTree(wxWindow *parent, wxWindowID id, const wxPoint &pos, const wxSize &size, long style)
	: wxTreeCtrl(parent, id, pos, size, style), m_findTimer(NULL)
{
	isJumpRoot = settings->GetJumpRoot();
}

ctlTree::~ctlTree()
{
	if ( m_findTimer )
		delete m_findTimer;
	m_findTimer = NULL;
}


void ctlTree::RemoveDummyChild(pgObject *obj)
{
	wxCookieType cookie;
	wxTreeItemId childItem = GetFirstChild(obj->GetId(), cookie);
	if (childItem && !GetItemData(childItem))
	{
		// The child was a dummy item, which will be replaced by the following ShowTreeDetail by true items
		Delete(childItem);
	}
}


pgObject *ctlTree::GetObject(wxTreeItemId id)
{
	if (id)
		return (pgObject *)GetItemData(id);
	return 0;
}


pgCollection *ctlTree::GetParentCollection(wxTreeItemId id)
{
	pgCollection *coll = (pgCollection *)GetParentObject(id);
	if (coll && coll->IsCollection())
		return coll;
	return 0;
}


void ctlTree::SetItemImage(const wxTreeItemId &item, int image, wxTreeItemIcon which)
{
	wxTreeCtrl::SetItemImage(item, image, which);

	wxTreeItemData *data = GetItemData(item);

	// Set the item colour
	if (data)
	{
		if (((pgObject *)data)->GetMetaType() == PGM_SERVER)
		{
			if (!((pgServer *)data)->GetColour().IsEmpty())
				SetItemBackgroundColour(item, wxColour(((pgServer *)data)->GetColour()));
		}
		else if (((pgObject *)data)->GetServer())
		{
			if (!((pgObject *)data)->GetServer()->GetColour().IsEmpty())
				SetItemBackgroundColour(item, wxColour(((pgObject *)data)->GetServer()->GetColour()));
		}
	}
}

wxTreeItemId ctlTree::AppendItem(const wxTreeItemId &parent, const wxString &text, int image, int selImage, wxTreeItemData *data)
{
	wxTreeItemId itm = wxTreeCtrl::AppendItem(parent, text, image, selImage, data);

	// Set the item colour
	if (data)
	{
		if (((pgObject *)data)->GetMetaType() == PGM_SERVER)
		{
			if (!((pgServer *)data)->GetColour().IsEmpty())
				SetItemBackgroundColour(itm, wxColour(((pgServer *)data)->GetColour()));
		}
		else if (((pgObject *)data)->GetServer())
		{
			if (!((pgObject *)data)->GetServer()->GetColour().IsEmpty())
				SetItemBackgroundColour(itm, wxColour(((pgObject *)data)->GetServer()->GetColour()));
		}
	}

	return itm;
}

wxTreeItemId ctlTree::AppendObject(pgObject *parent, pgObject *object)
{
	wxString label;
	wxTreeItemId item;

	if (object->IsCollection())
		label = object->GetTypeName();
	else
		label = object->GetDisplayName();
	item = AppendItem(parent->GetId(), label, object->GetIconId(), -1, object);
	if (object->IsCollection())
		object->ShowTreeDetail(this);
	else if (object->WantDummyChild())
		AppendItem(object->GetId(), wxT("Dummy"));

	return item;
}


pgCollection *ctlTree::AppendCollection(pgObject *parent, pgaFactory &factory)
{
	pgCollection *collection = factory.CreateCollection(parent);
	AppendObject(parent, collection);
	return collection;
}


pgObject *ctlTree::FindObject(pgaFactory &factory, wxTreeItemId parent)
{
	wxCookieType cookie;
	wxTreeItemId item = GetFirstChild(parent, cookie);
	while (item)
	{
		pgObject *obj = (pgObject *)GetItemData(item);
		if (obj && obj->IsCreatedBy(factory))
			return obj;
		item = GetNextChild(parent, cookie);
	}
	return 0;
}


pgCollection *ctlTree::FindCollection(pgaFactory &factory, wxTreeItemId parent)
{
	pgaFactory *cf = factory.GetCollectionFactory();
	if (!cf)
		return 0;

	pgCollection *collection = (pgCollection *)FindObject(*cf, parent);

	if (!collection || !collection->IsCollection())
		return 0;
	return collection;
}


void ctlTree::NavigateTree(int keyCode)
{
	switch(keyCode)
	{
		case WXK_LEFT:
		{
			//If tree item has children and is expanded, collapse it, otherwise select it's parent if has one
			wxTreeItemId currItem = GetSelection();

			if (ItemHasChildren(currItem) && IsExpanded(currItem))
			{
				Collapse(currItem);
			}
			else
			{
				wxTreeItemId parent = GetItemParent(currItem);
				if (parent.IsOk())
				{
					SelectItem(currItem, false);
					SelectItem(parent, true);
				}
			}
		}
		break;
		case WXK_RIGHT:
		{
			//If tree item do not have any children ignore it,
			//otherwise  expand it if not expanded, and select first child if already expanded
			wxTreeItemId currItem = GetSelection();

			if(ItemHasChildren(currItem))
			{
				if (!IsExpanded(currItem))
				{
					Expand(currItem);
				}
				else
				{
					wxCookieType cookie;
					wxTreeItemId firstChild = GetFirstChild(currItem, cookie);
					SelectItem(currItem, false);
					SelectItem(firstChild, true);
				}
			}
		}
		break;
		default:
			wxASSERT_MSG(false, _("Currently handles only right and left arrow key, other keys are working"));
			break;
	}
}

//////////////////////

treeObjectIterator::treeObjectIterator(ctlTree *brow, pgObject *obj)
{
	browser = brow;
	object = obj;
}


pgObject *treeObjectIterator::GetNextObject()
{
	if (!object || !browser)
		return 0;

	if (!lastItem)
		lastItem = browser->GetFirstChild(object->GetId(), cookie);
	else
		lastItem = browser->GetNextChild(object->GetId(), cookie);

	if (lastItem)
		return browser->GetObject(lastItem);
	else
		object = 0;

	return 0;
}
