#include "listctrl.h"
#include "scenemanager.h"
#include "scene.h"
#include "kernel.h"
#include <wx/clipbrd.h>

using namespace VRUT;

////////////////////////////////////////////////////////////////////////////////
/// ListCtrl
////////////////////////////////////////////////////////////////////////////////

// Register with wxWidgets' dynamic class subsystem.
IMPLEMENT_DYNAMIC_CLASS(ListCtrl, wxListCtrl)
IMPLEMENT_DYNAMIC_CLASS(ListCtrlXmlHandler, wxXmlResourceHandler)

void ListCtrl::LocalInit()
{
	SAFE_DELETE(imageList);
	imageList = new wxImageList(16, 16);
	wxListCtrl::SetImageList(imageList, wxIMAGE_LIST_SMALL);
	if (wxListCtrl::InReportView())
	{
		wxListCtrl::InsertColumn(0, wxT("ID"), wxLIST_FORMAT_LEFT, wxLIST_AUTOSIZE);
		wxListCtrl::InsertColumn(1, wxT("Name"), wxLIST_FORMAT_LEFT, wxLIST_AUTOSIZE_USEHEADER);
	}
	sceneID = SCENE_ID_NONE;
	Connect(wxID_ANY, wxEVT_LIST_BEGIN_DRAG, wxListEventHandler(ListCtrl::OnBeginDrag), (wxObject *) NULL, this);
	Connect(wxID_ANY, wxEVT_LIST_KEY_DOWN, wxListEventHandler(ListCtrl::OnListKeyDown), (wxObject *) NULL, this);
	this->Connect(wxID_ANY, wxEVT_RIGHT_DOWN, wxMouseEventHandler(ListCtrl::OnRightDown), NULL, this);
}

void ListCtrl::ParseItemDefinition(const wxString &definition, unsigned long &id, wxString &name, wxString &iconName, bool &isActive)
{
	TextParser parser(definition.utf8_str());
	wxString label = parser.GetWxStringUtf8(",");
	iconName = parser.GetWxStringUtf8(",");
	wxString activeString = parser.GetWxStringUtf8(",");
	isActive = (activeString != wxT("0"));

	TextParser labelParser(label.utf8_str());
	if (labelParser.ParseULong(&id))
	{
		if (labelParser.IsEmpty("", false) || (labelParser.GetChar("", false) == ' '))
		{
			name = labelParser.GetBinWxStringUtf8();
		}
		else
		{
			id = -1;
			name = label;
		}
	}
	else
	{
		id = -1;
		name = label;
	}
}

void ListCtrl::Unselect()
{
	int n = GetItemCount();
	for (int i = 0; i < n; i++)
	{
		SetItemState(i, 0, wxLIST_STATE_SELECTED);
	}
}

int ListCtrl::ItemCompare(wxIntPtr item1, wxIntPtr item2, wxIntPtr sortData)
{
	ListCtrl *listControl = reinterpret_cast<ListCtrl *>(sortData);
	switch (listControl->order)
	{
	case ORDER_BY_ID:
		{
			unsigned long id1 = listControl->idVector[(long)item1];
			unsigned long id2 = listControl->idVector[(long)item2];
			if (id1 > id2) {
				return 1;
			} else if (id1 < id2) {
				return -1;
			} else {
				return 0;
			}
		}
		break;
	case ORDER_BY_NAME:
		{
			const wxString &name1 = listControl->nameVector[(long)item1];
			const wxString &name2 = listControl->nameVector[(long)item2];
			return name1.CmpNoCase(name2);
		}
		break;
	case ORDER_NONE:
	default:
		return 0;
	}
}

ListCtrl::ListCtrl() : wxListCtrl(), imageList(NULL), order(ORDER_NONE), sceneID(SCENE_ID_NONE)
{
}

ListCtrl::ListCtrl(wxWindow * parent, wxWindowID id, const wxPoint & pos, const wxSize & size, long style, const wxValidator & validator, const wxString & name)
	: wxListCtrl(parent, id, pos, size, style, validator, name), imageList(NULL), order(ORDER_NONE), sceneID(SCENE_ID_NONE)
{
	LocalInit();
}

bool ListCtrl::Create(wxWindow * parent, wxWindowID id, const wxPoint & pos, const wxSize & size, long style, const wxValidator & validator, const wxString & name)
{
	if (!wxListCtrl::Create(parent, id, pos, size, style, validator, name))
		return false;

	LocalInit();

	return true;
}

ListCtrl::~ListCtrl()
{
	SAFE_DELETE(imageList);
}

void ListCtrl::SetSceneID(const int id)
{
	sceneID = id;
}

SCENE_ID ListCtrl::GetSceneID() const
{
	return sceneID;
}

wxString ListCtrl::GetValue() const
{
	wxString value;

	long item = wxListCtrl::GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
	if (wxNOT_FOUND != item) {
		long itemId = (long)wxListCtrl::GetItemData(item);
		unsigned long id = idVector[itemId];
		wxString name = nameVector[itemId];
		value = wxString::Format(wxT("%lu "), id) + name;
	}

	return value;
}

bool ListCtrl::AddItem(const wxString &definition, bool updateGui)
{
	unsigned long id = -1L;
	wxString name;
	wxString iconName;
	bool isActive = true;
	ParseItemDefinition(definition, id, name, iconName, isActive);

	if ((id != (unsigned long)-1) && (id < itemIdVector.size()) && (itemIdVector.at(id) != -1))
	{
		LOGWARNING("Listctrl: Item with the same ID already exist.");
		return false;
	}

	long itemId = imageIndexVector.size();

	const_smart_pointer<Image> image = KERNEL->sceneManager->GetImage(iconName);
	int imageIndex = -1;
	if (image && (image->GetType() == Image::WXIMAGE))
	{
		ImageCommon * imgCommon = (ImageCommon*)image.getPtr();
		wxBitmap bitmap(imgCommon->GetWxImage()->Scale(16, 16));
		imageIndex = imageList->Add(bitmap);
	}

	long index = -1;
	if (wxListCtrl::InReportView())
	{
		wxString idString = (id == (unsigned long)-1) ? wxString() : wxString::Format(wxT("%lu"), id);
		index = wxListCtrl::InsertItem(GetItemCount(), idString, imageIndex);
		wxListCtrl::SetItem(index, 1, name);
	}
	else
	{
		wxString label = (id == (unsigned long)-1) ? name : (wxString::Format(wxT("%lu "), id) + name);
		index = wxListCtrl::InsertItem(GetItemCount(), label, imageIndex);
	}
	const wxColour *itemColour = isActive ? wxBLACK : wxLIGHT_GREY;
	wxListCtrl::SetItemTextColour(index, *itemColour);
	wxListCtrl::SetItemData(index, itemId);

	if (id != (unsigned long)-1)
	{
		if (itemIdVector.size() <= id)
		{
			itemIdVector.resize((id + 1), -1);
		}
		itemIdVector[id] = itemId;
	}
	imageIndexVector.push_back(imageIndex);
	idVector.push_back(id);
	nameVector.push_back(name);

	if (updateGui)
	{
		UpdateListGui();
	}

	return true;
}

bool ListCtrl::UpdateItem(const wxString &definition, bool updateGui)
{
	unsigned long id = -1;
	wxString name;
	wxString iconName;
	bool isActive = true;
	ParseItemDefinition(definition, id, name, iconName, isActive);

	if (id == (unsigned long)-1)
	{
		LOGWARNING("Listctrl: Item without id cannot be updated.");
		return false;
	}

	long itemId = -1;
	if (id < itemIdVector.size())
	{
		itemId = itemIdVector.at(id);
	}
	if (itemId == -1)
	{
		LOGWARNING("Listctrl: Item cannot be updated. it does not exist.");
		return false;
	}

	long index = wxListCtrl::FindItem(-1, itemId);
	if (index == wxNOT_FOUND)
	{
		LOGERROR("ListCtrl: Unkown error. Item not found");
		return false;
	}
	// Update icon
	int imageIndex = imageIndexVector[itemId];
	const_smart_pointer<Image> image = KERNEL->sceneManager->GetImage(iconName);
	if (image && (image->GetType() == Image::WXIMAGE))
	{
		ImageCommon * imgCommon = (ImageCommon*)image.getPtr();
		wxBitmap bitmap(imgCommon->GetWxImage()->Scale(16, 16));
		if (imageIndex != -1)
		{
			imageList->Replace(imageIndex, bitmap);
		}
		else
		{
			imageIndex = imageList->Add(bitmap);
			wxListCtrl::SetItemImage(index, imageIndex);
			imageIndexVector[itemId] = imageIndex;
		}
	}
	else
	{
		if (imageIndex != -1)
		{
			// Remove 
			wxListCtrl::SetItemImage(index, -1);
			imageIndexVector[itemId] = -1;
			imageList->Remove(imageIndex);
		}
	}
	// Update name
	if (wxListCtrl::InReportView())
	{
		wxListCtrl::SetItem(index, 1, name);
	}
	else
	{
		wxString label = (id == (unsigned long)-1) ? name : (wxString::Format(wxT("%lu "), id) + name);
		wxListCtrl::SetItemText(index, label);
	}
	nameVector[itemId] = name;
	// Update isActive
	const wxColour *itemColour = isActive ? wxBLACK : wxLIGHT_GREY;
	wxListCtrl::SetItemTextColour(index, *itemColour);

	if (updateGui)
	{
		UpdateListGui();
	}

	return true;
}

bool ListCtrl::RemoveItem(const wxString &definition, bool updateGui)
{
	unsigned long id = -1;
	wxString name;
	wxString iconName;
	bool isActive = true;
	ParseItemDefinition(definition, id, name, iconName, isActive);

	if (id == (unsigned long)-1)
	{
		LOGWARNING("Listctrl: Item without id cannot be removed.");
		return false;
	}

	long itemId = -1;
	if (id < itemIdVector.size())
	{
		itemId = itemIdVector.at(id);
	}
	if (itemId == -1)
	{
		LOGWARNING("Listctrl: Item cannot be removed. it does not exist.");
		return false;
	}

	long index = wxListCtrl::FindItem(-1, itemId);
	if (index == wxNOT_FOUND)
	{
		LOGERROR("ListCtrl: Unkown error. Item not found");
		return false;
	}

	wxListCtrl::DeleteItem(index);

	int imageIndex = imageIndexVector[itemId];
	if (imageIndex != -1)
	{
		imageList->Remove(imageIndex);
		imageIndexVector[itemId] = -1;
	}
	idVector[itemId] = -1;
	nameVector[itemId] = wxString();
	itemIdVector[id] = -1;

	if (updateGui)
	{
		// Item order has no changed, only column width may need change
		UpdateColumns();
	}

	return false;
}

bool ListCtrl::SelectByValue(const wxString &value)
{
	if (value.size()<=2)
	{
		// Empty value is treated as no value
		Unselect();
		return false;
	}

	unsigned long id = -1;
	wxString name;
	wxString iconName;
	bool isActive = true;
	ParseItemDefinition(value.substr(2), id, name, iconName, isActive);

	if (id == (unsigned long)-1)
	{
		LOGWARNING("Listctrl: Item without id cannot be selected.");
		Unselect();
		return false;
	}

	long itemId = -1;
	if (id < itemIdVector.size())
	{
		itemId = itemIdVector.at(id);
	}
	if (itemId == -1)
	{
		// Item does not exist
		Unselect();
		return false;
	}

	long index = wxListCtrl::FindItem(-1, itemId);
	if (index == wxNOT_FOUND)
	{
		LOGERROR("ListCtrl: Unkown error. Item not found");
		Unselect();
		return false;
	}

	wxListCtrl::EnsureVisible(index);
	wxListCtrl::SetItemState(index, wxLIST_STATE_SELECTED, wxLIST_STATE_SELECTED);

	return true;
}

void ListCtrl::RemoveAllItems()
{
	wxListCtrl::DeleteAllItems();
	imageList->RemoveAll();
	imageIndexVector.clear();
	idVector.clear();
	nameVector.clear();
	itemIdVector.clear();
}

void ListCtrl::SetOrder(ORDER order)
{
	if (this->order != order)
	{
		this->order = order;
		Sort();
	}
}

void ListCtrl::Sort()
{
	if (ORDER_NONE != order)
	{
		SortItems(ListCtrl::ItemCompare, reinterpret_cast<wxIntPtr>(this));
	}
}

void ListCtrl::UpdateColumns()
{
	if (wxListCtrl::InReportView())
	{
		wxListCtrl::SetColumnWidth(0, wxLIST_AUTOSIZE_USEHEADER);
		wxListCtrl::SetColumnWidth(1, wxLIST_AUTOSIZE_USEHEADER);
	}
}

void ListCtrl::UpdateListGui()
{
	Sort();
	UpdateColumns();
}

void ListCtrl::OnBeginDrag(wxListEvent& evt)
{
	wxString name = GetName();
	if (name.StartsWith(wxT("MaterialEditor.materialList.")))
	{
		// temporary this object will never be deleted!
		//			MaterialDataObject(materialID, sceneID);

		long item = GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
		if (item != -1)
		{
			// this item is selected - do whatever is needed with it
			smart_pointer_v2<Scene> scene;
			KERNEL->sceneManager->GetScene(scene, sceneID);
			if (scene)
			{
				long itemId = (long)wxListCtrl::GetItemData(item);
				MATERIAL_ID matID = idVector[itemId];
				wxString matName = nameVector[itemId];
				if (matID != MATERIAL_ID_NONE)
				{
					MaterialDataObject *object = new MaterialDataObject(matID, sceneID);
					wxDropSource drop_source(*object, this);
					drop_source.DoDragDrop(wxDrag_AllowMove);
					LOGDEBUG(wxString::Format(wxT("Item %ld is dragged, scene=%d, mat=%s, matID=%d."), item, sceneID, matName, matID));
				}
			}
		}
	}
}

void ListCtrl::OnListKeyDown(wxListEvent& evt)
{
	switch (evt.GetKeyCode())
	{
	case 'c':
	case 'C':
		{
			// temporary this object will never be deleted!
			//			MaterialDataObject(materialID, sceneID);
			long item = GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);

			// assume material list window - should be checked by the widget name
			if (item != -1)
			{
				wxString label = GetItemText(item);
				TextParser parser(label.utf8_str());
				int dummy;
				parser.ParseInt(&dummy);
				wxString matName = parser.GetWxString();

				// this item is selected - do whatever is needed with it

				smart_pointer_v2<Scene> scene;
				KERNEL->sceneManager->GetScene(scene, sceneID);
				if (scene)
				{
					MATERIAL_ID matID = scene->GetMaterialID(matName);
					if (matID != MATERIAL_ID_NONE)
					{
						MaterialDataObject *object = new MaterialDataObject(matID, sceneID);
						if (wxTheClipboard->Open())
						{
							wxTheClipboard->SetData(object);
							wxTheClipboard->Close();
							LOGDEBUG(wxString::Format(wxT("Item %ld is dragged, scene=%d, mat=%s, matID=%d."), item, sceneID, matName, matID));
						}
					}
				}
			}
			break;
		}
	}
}

void ListCtrl::OnRightDown(wxMouseEvent &evt)
{
	ListCtrlPopup *popup = new ListCtrlPopup(this, order);
	wxWindow *btn = (wxWindow*) evt.GetEventObject();
	wxPoint pos = btn->ClientToScreen(evt.GetLogicalPosition(wxWindowDC(btn)));
	wxSize sz = wxSize(0, 0);
	popup->Position(pos, sz);
	popup->Popup(this);
	evt.Skip(false);
}

////////////////////////////////////////////////////////////////////////////////
/// ListCtrlPopup
////////////////////////////////////////////////////////////////////////////////

ListCtrlPopup::ListCtrlPopup(ListCtrl *listControl, ListCtrl::ORDER order) : wxPopupTransientWindow(listControl, wxBORDER_NONE), listControl(listControl), order(order)
{
	wxPanel *panel = new wxPanel(this);
	panel->SetBackgroundColour(*wxLIGHT_GREY);
	wxBoxSizer *topSizer = new wxBoxSizer(wxVERTICAL);
	
	wxStaticText *text = new wxStaticText(panel, wxID_ANY, wxT("Sort by"));
	topSizer->Add(text, 0, wxALL, 5);

	wxCheckBox *cb0 = new wxCheckBox(panel, wxID_ANY, wxT("None"));
	topSizer->Add(cb0, 0, wxALL, 5);
	orderCheckBoxVector.push_back(cb0);

	wxCheckBox *cb1 = new wxCheckBox(panel, wxID_ANY, wxT("ID"));
	topSizer->Add(cb1, 0, wxALL, 5);
	orderCheckBoxVector.push_back(cb1);

	wxCheckBox *cb2 = new wxCheckBox(panel, wxID_ANY, wxT("Name"));
	topSizer->Add(cb2, 0, wxALL, 5);
	orderCheckBoxVector.push_back(cb2);

	panel->SetSizer(topSizer);
	topSizer->Fit(panel);
	topSizer->Fit(this);

	UpdateOrderGui();

	Bind(wxEVT_CHECKBOX, &ListCtrlPopup::OnCheckBox, this);
}

void ListCtrlPopup::UpdateOrderGui()
{
	for (size_t i = 0; i < orderCheckBoxVector.size(); ++i)
	{
		orderCheckBoxVector[i]->SetValue(order == i);
	}
}

void ListCtrlPopup::OnDismiss()
{
	wxPopupTransientWindow::OnDismiss();
}

void ListCtrlPopup::OnCheckBox(wxCommandEvent& event)
{
	// Simulate radio button
	// Radio button do not work on wxPopupWindow
	wxCheckBox *cb = wxDynamicCast(event.GetEventObject(), wxCheckBox);
	size_t pos = find(orderCheckBoxVector.begin(), orderCheckBoxVector.end(), cb) - orderCheckBoxVector.begin();
	if (pos < orderCheckBoxVector.size()) {
		ListCtrl::ORDER newOrder = (ListCtrl::ORDER)pos;
		if (newOrder != order)
		{
			order = newOrder;
			listControl->SetOrder(order);
		}
		UpdateOrderGui();
	}
}
