#include "pgAdmin3.h"
#include "ctl/ctlTreeJSON.h"
#include <wx/wx.h>
#include <wx/event.h>
#include <map>
#include <wx/colordlg.h>



BEGIN_EVENT_TABLE(ctlTreeJSON, wxTreeCtrl)
EVT_CHAR(ctlTreeJSON::OnChar)
//EVT_MOUSE_EVENTS(ctlTreeJSON::OnMouse)
EVT_TREE_BEGIN_LABEL_EDIT(wxID_ANY, ctlTreeJSON::OnBeginEdit)
EVT_TREE_END_LABEL_EDIT(wxID_ANY, ctlTreeJSON::OnEndEdit)
EVT_LEFT_DCLICK(ctlTreeJSON::OnDoubleClick)
EVT_TREE_DELETE_ITEM(wxID_ANY, ctlTreeJSON::OnDeleteItem)
END_EVENT_TABLE()
IMPLEMENT_DYNAMIC_CLASS(ctlTreeJSON, wxTreeCtrl)

void ctlTreeJSON::OnChar(wxKeyEvent& event) {
	wxTreeItemId id = GetSelection();
	if (!id.IsOk()) return;
	if (event.GetKeyCode() == WXK_INSERT) {
		CopyNode(id);
		return;
	}
	if (event.GetKeyCode() == WXK_DELETE) {
		if (HasChildren(id)) DeleteChildren(id);
		Delete(id);
		return;
	}
	if (event.GetKeyCode() == WXK_CONTROL_Z) {
		if (GetItemBackgroundColour(id) != GetBackgroundColour()) {
			if (orig.find(id) != orig.end()) {
				conf[id] = orig[id];
				wxString t = GetItemText(id);
				wxString newtext = orig[id].AsString();
				wxString key = t.BeforeFirst(':');
				if (key !=t) {
					newtext = key + ":" + newtext;
				}
				SetItemText(id, newtext);
				SetItemBackgroundColour(id, GetBackgroundColour());
				wxColour empty;
				int rez = 0;
				if (newtext != t && newtext.length() > 0) 
					rez=RefreshImages(id, empty, newtext);
				if (rez!=1) orig.erase(id);

				return;
			}

		}

	}
	if (event.GetKeyCode() == WXK_F1) {
		int x=50;
		int y=50;
		this->ClientToScreen(&x,&y);
		wxPoint screenPos(x,y);
		wxString helpstr=R"(<html>
<head>
  <meta charset="utf-8">
</head>
<body>
  <p><b>Ctrl-F</b> - find and colorize nodes</p>
  <p><b>F3</b> - next find node</p>
  <p><b>Shift-F3</b> - previos find node</p>
  <p><b>Insert</b> - copy select node</p>
  <p><b>Delete</b> - delete select node</p>
  <p><b>Ctrl-Z</b> - Return source context node.</p>
</body>
</html>)";
		showHelpHtml((wxWindow *)winMain,helpstr,screenPos, wxSize(450,300));
		return;
	}
	if (event.GetKeyCode() == WXK_CONTROL_F) {
		wxTextEntryDialog dialog(this,
			_("Please enter find string\n")
			,
			_("Find"),
			m_FindString,
			wxOK | wxCANCEL);		//setName( dlg.GetValue().wc_str() );
		if (dialog.ShowModal() == wxID_OK) {
			m_FindString = dialog.GetValue();
			BuildFind();
		}
		return;
	}
	int find_next = 0;
	if (event.GetKeyCode() == WXK_F3) {
		find_next = 1;
	}
	if (event.GetKeyCode() == WXK_F3 && event.ShiftDown()) {
		find_next = -1;
	}
	if ((find_next == 1 || find_next == -1) && findsId.size() > 0) {
		wxTreeItemId sel, next, item = GetRootItem();
		while (item.IsOk()) {
			item = nextTreeItem(item);
			if (item.IsOk()) {
				if (id == item) {
					sel = id;
					if (find_next == -1 && next.IsOk()) {
						SelectItem(next);
						return;
					}
				}
				if (findsId.count(item) > 0) {
					next = item;
					if (sel.IsOk() && find_next == 1 && id != item) {
						SelectItem(next);
						return;
					}

				}
			}
		}
		return;
	}
	event.Skip();
};
void ctlTreeJSON::BuildFind() {
	for (const auto& [id, tmp] : findsId) {
		SetItemBackgroundColour(id, GetBackgroundColour());
	}
	findsId.clear();
	wxTreeItemId item;
	wxTreeItemId sel;
	wxTreeItemIdValue cookie;
	if (m_FindString.IsEmpty()) return;
	sel = GetSelection();
	item = GetFirstChild(GetRootItem(), cookie);
	if (!GetCount()) return; // empty tree control - i.e. just cleared it?
	wxString searchtext = m_FindString.Lower();
	wxTreeItemId last;
	bool flag = false;
	while (item.IsOk()) {
		wxString itemtext = GetItemText(item).Lower();
		if (sel == item) flag = true;
		if (itemtext.Contains(searchtext)) {
			//findsId.push_back(item);
			findsId.emplace(item, 0);
			SetItemBackgroundColour(item, wxColour("#FFFF00"));
			if (flag)
			{
				last = item;
				flag = false;
			}
		}
		item = nextTreeItem(item);
	}
	if (findsId.size() > 0)
	{
		if (last.IsOk())
			SelectItem(last);
		else
			wxBell();
	}
}
wxTreeItemId ctlTreeJSON::nextTreeItem(const wxTreeItemId& item) {
	wxTreeItemId child;
	wxTreeItemIdValue cookie;
	if (HasChildren(item))
		child = GetFirstChild(item, cookie);
	else
	{
		// Try a sibling of this or ancestor instead
		wxTreeItemId p = item;
		wxTreeItemId toFind;
		do
		{
			toFind = GetNextSibling(p);
			p = GetItemParent(p);
			if (GetRootItem() == p && !toFind.IsOk()) {
				return child;
			}
		} while (p.IsOk() && !toFind.IsOk());
		child = toFind;
	}
	return child;
}
wxTreeItemId ctlTreeJSON::findTreeItem(const wxTreeItemId& root, const wxString& text, bool bCaseSensitive, bool bExactMatch)
{
	wxTreeItemId item = root, child;
	wxTreeItemIdValue cookie;
	wxString findtext(text), itemtext;
	bool bFound;
	if (!bCaseSensitive) findtext.MakeLower();
	while (item.IsOk())
	{
		itemtext = GetItemText(item);
		if (!bCaseSensitive) itemtext.MakeLower();
		bFound = bExactMatch ? (itemtext == findtext) : itemtext.Contains(findtext);
		if (bFound) return item;
		child = GetFirstChild(item, cookie);
		if (child.IsOk()) child = findTreeItem(child, text, bCaseSensitive, bExactMatch);
		if (child.IsOk()) return child;
		item = GetNextSibling(item);

	} // while(item.IsOk())

	return item;
}
void ctlTreeJSON::OnDeleteItem(wxTreeEvent& event) {
	wxTreeItemId id = event.GetItem();
	if (colors.find(id) != colors.end()) {
		colors.erase(id);
	}
	if (conf.find(id) != conf.end()) {
		conf.erase(id);
		m_change = true;
	}

};
wxColour getColorFromString(const wxString& str) {
	wxString strcolor = str.AfterFirst('#');
	wxColour empty;
	if (!strcolor.IsEmpty() && strcolor.length() > 5) {
		wxString strc = "#" + strcolor.Mid(0, 6);
		unsigned long tmp;
		int scanned = wxSscanf(strcolor, "%lx", &tmp);
		if (scanned == 1) {
			wxColour c;
			c.Set((tmp>>16) & 0xFF | (tmp  & 0x00FF00)| (tmp & 0xFF)<<16);
			if (c.IsOk()) return c;
		}
	}
	return empty;
}
int ctlTreeJSON::RefreshImages(const wxTreeItemId& id, wxColour newColour, wxString textLabel) {
	//SetIma
	int rez = 0;
	if (colors.find(id) != colors.end()) {
		rez = -1;
		auto old_color = colors.at(id);
		if (textLabel.length() > 0) {
			// change label
			newColour = getColorFromString(textLabel);
			if (!newColour.IsOk()) return rez;
		}
		if (old_color.GetRGB() != newColour.GetRGB()) {
			colors.erase(id);
			colors[id] = newColour;
			wxString oldt = GetItemText(id);
			wxString newtextcolor = newColour.GetAsString(wxC2S_HTML_SYNTAX);
			int pos = oldt.Find('#');
			if (pos < 0) {
				pos = 0;
			}
			wxString newlabel = oldt.Left(pos) + newtextcolor;
			SetItemText(id, newlabel);
			wxJSONValue v = conf[id];
			conf[id] = newtextcolor;
			m_change = true;
			if (orig.find(id) != orig.end()) {
				if (conf[id].AsString() == orig[id].AsString()) {
					SetItemBackgroundColour(id, GetBackgroundColour());
					orig.erase(id);
				}
			}
			else {
				// save original value
				orig[id] = v;
				SetItemBackgroundColour(id, wxColour("#c0ffff"));
			}

			RefreshImageList();
			rez = 1;
		}
	}
	return rez;
}
void ctlTreeJSON::OnDoubleClick(wxMouseEvent& event) {
	wxPoint p = event.GetPosition();
	int flags;
	wxTreeItemId id = HitTest(p, flags);
	wxString s;
	wxJSONValue v;
	if (id.IsOk()) {
		s = GetItemText(id);

		if (conf.count(id) > 0) v = conf[id];
	}

	if (flags & wxTREE_HITTEST_ONITEMLABEL) {
		wxString s = GetItemText(id);
		wxString str = s.AfterFirst(':');
		wxString key = s.BeforeFirst(':');
		if (str.IsEmpty()) str = s;
		int im = GetItemImage(id);
		s = wxString::Format("val=%s n_image=%d", str, im);
		if (key!=s) {
			if (v.GetType() == wxJSONTYPE_BOOL) {
				bool b = !v.AsBool();
				wxString newstr = "true";
				if (!b) newstr = "false";
				wxTreeEvent e;
				wxString nv = key + ":" + newstr;
				e.SetLabel(nv);
				e.SetItem(id);
				e.Allow();
				OnEndEdit(e);
				if (e.IsAllowed()) {
					SetItemText(id, nv);
				}
			}
		}
		//wxMessageBox(s);
	}
	else if (flags & wxTREE_HITTEST_ONITEMICON) {
		//if (v.AsString())

		if (colors.find(id) != colors.end()) {
			wxColourData data;
			wxColour initialColourToUse = colors.at(id);
			data.SetColour(initialColourToUse);
			wxColourDialog dlg(this, &data);
			dlg.Bind(wxEVT_COLOUR_CHANGED, [](wxColourDialogEvent& event) {
				//Redraw(event.GetColour());
				});
			if (dlg.ShowModal() == wxID_OK) {
				// Colour did change.
				RefreshImages(id, dlg.GetColourData().GetColour(), "");
			}
			else {
				// Colour didn't change.
			}
		}
	}
};
void ctlTreeJSON::OnBeginEdit(wxTreeEvent& event) {
	//wxTrap();
};
void ctlTreeJSON::OnEndEdit(wxTreeEvent& event) {
	if (event.IsEditCancelled()) return;
	wxString newtext = event.GetLabel();
	auto id = event.GetItem();
	wxString oldtext = GetItemText(id);
	auto parent_id = GetItemParent(id);
	wxString name;
	wxJSONValue v = conf[id];
	if (v.IsArray() || v.IsObject()) {
		event.Veto();
		return;
	}
	if (parent_id.IsOk()) {
		wxJSONValue p = conf[parent_id];
		wxString newvaluetext = newtext;
		wxString n; // name parameter
		int idx = -1; // index array
		if (p.IsObject()) {
			n = oldtext.BeforeFirst(':');
			if (!(n.length() > 0 && p.HasMember(n))) {
				event.Veto();
				return;
			}
			// 
			if (!(n.length() > 0 && newtext.StartsWith(n + ":"))) {
				// name need equals
				event.Veto();
				return;
			}
			newvaluetext = newtext.Mid(n.length() + 1);
		}
		else if (p.IsArray()) {
			for (int j = 0; j < conf[parent_id].Size(); ++j) {
				if (conf[parent_id][j].GetRefData() == conf[id].GetRefData()) { idx = j; break; }
			}
			if (idx == -1) {
				//	wxTrap();
			}
		}

		//change jsonvalue
		if (v.IsBool()) {
			bool bb = false;
			if (newvaluetext == "true") bb = true;
			//if (n.IsEmpty()) p[idx] = bb;  else p[n] = bb;
			conf[id] = bb;
			m_change = true;
		}
		else if (v.IsInt()) {
			int ii = 0;
			int scaned = wxSscanf(newvaluetext, "%d", &ii);
			if (scaned != 1) {
				event.Veto();
				return;
			}
			//if (n.IsEmpty()) p[idx] = ii;  else p[n] = ii;
			conf[id] = ii;
			m_change = true;
		}
		else {
			// string value
			//if (n.IsEmpty()) p[idx] = newvaluetext;  else p[n] = newvaluetext;
			conf[id] = newvaluetext;
			m_change = true;
			//if (n.IsEmpty()) conf[parent_id][idx] = conf[id];  else conf[parent_id][n] = conf[id];
		}
		//p[n] = newtext.Mid(n.length());
		if (orig.find(id) != orig.end()) {
			if (conf[id].AsString() == orig[id].AsString()) {
				SetItemBackgroundColour(id, GetBackgroundColour());
				orig.erase(id);
			}
		}
		else {
			// save original value
			orig[id] = v;
			SetItemBackgroundColour(id, wxColour("#c0ffff"));
		}




	}

	wxColour empty;
	if (newtext != oldtext && newtext.length() > 0) {
		int rez = RefreshImages(id, empty, newtext);
		if (rez > 0) return;
		if (rez < 0)event.Veto(); // bad colour
		else
		{
			// no colour

		}
	}
};
wxTreeItemId ctlTreeJSON::NodeToJSON(const wxTreeItemId& id, wxJSONValue& newjson) {
	wxTreeItemId item = id, child;
	wxTreeItemIdValue cookie;
	wxString itemtext;
	int i = 0;
	while (item.IsOk())
	{
		itemtext = GetItemText(item);
		wxJSONValue j = conf.at(item);
		wxJSONValue n;
		n.SetType(j.GetType());
		//		wxJSONValue v = conf.at(item);
		//		j = v;
				//wxJSONType t = v.GetType();
		child = GetFirstChild(item, cookie);

		if (child.IsOk()) child = NodeToJSON(child, n);
		if (newjson.IsObject()) {
			wxString key = itemtext.BeforeFirst(':');
			if (key.Right(2) == "{}" || key.Right(2) == "[]") key = key.Left(key.length() - 2);
			wxString vl = itemtext.AfterFirst(':');
			if (n.IsArray() || n.IsObject()) newjson[key] = n;
			else
				newjson[key] = j;
		}
		else if (newjson.IsArray()) {
			if (n.IsArray() || n.IsObject()) newjson[i] = n;
			else
				newjson[i] = j;
			i++;
		}

		//if (child.IsOk()) return child;
		item = GetNextSibling(item);
	} // while(item.IsOk())

	return item;
}
wxJSONValue ctlTreeJSON::copyjson(wxJSONValue& src) {
	wxJSONValue v;
	v.SetType(src.GetType());
	if (src.IsArray()) {
		for (int j = 0; j < src.Size(); ++j) {
			wxJSONValue m = copyjson(src[j]);
			v.Append(m);
		}
	}
	else if (src.IsObject()) {
		wxArrayString arr = src.GetMemberNames();
		for (int j = 0; j < arr.Count(); ++j) {
			v[arr[j]] = copyjson(src[arr[j]]);
		}
	}
	else {
		bool b = false;
		int i = 0;
		wxString s;
		src.AsBool(b);
		src.AsInt(i);
		src.AsString(s);
		if (src.IsBool()) v = b;
		if (src.IsInt())
			v = i;
		if (src.IsString()) v = s;
	}
	return v;
}
void ctlTreeJSON::CopyNode(const wxTreeItemId& idSource) {
	wxJSONValue v = conf[idSource];
	wxTreeItemId pid = GetItemParent(idSource);
	wxJSONValue pv = conf.at(pid);
	wxJSONValue n = copyjson(conf[idSource]);

	wxTreeItemId nid;
	if (pv.IsArray()) {
		//if (v.IsObject()) nid = NodeToJSON(idSource, n);
		int vsize = pv.Size();
		wxString label = wxString::Format("[%d]", vsize);
		if (n.IsObject() || n.IsArray()) {
			nid = addtree(pid, label, &n);
			conf[nid] = n;
			LoadInTree(n, nid);
			SelectItem(nid);
			//EnsureVisible(nid);
			m_change = true;
		}
		else {
			//conf[pid][vsize]=n;
			//LoadInTree(conf[pid], pid);
			nid = addtree(pid, n.AsString(), &n);
			conf[nid] = n;

			m_change = true;
		}


	}
	else if (pv.IsObject()) {
		wxString label = GetItemText(idSource);
		wxString suf = label.Right(2);
		label = label.Mid(0, label.length() - 2);
		int vsize = v.Size();
		label = wxString::Format("%s%d", label, vsize);
		label += suf;
		if (v.IsObject() || v.IsArray()) {
			//nid = NodeToJSON(idSource, n);
			if (vsize == 0) {
				//copy empty array 
				wxJSONValue newString;
				wxString text = "new string value";
				newString = text;
				newString = conf[idSource].Append(newString);
				nid = addtree(idSource, text, &newString);
				conf[nid] = newString;
				m_change = true;
				return;
			}
			else {
				if (v.IsObject() || v.IsArray()) return;
				nid = addtree(pid, label, &n);
				conf[nid] = n;
				LoadInTree(n, nid);
				m_change = true;
			}
		}
		else {
			//wxString key = GetItemText(idSource).BeforeFirst(':');

			return;
		}
		// copy only Object or Array
		nid = addtree(pid, label, &n);
		LoadInTree(n, nid);
	}
	else {
		return;
	}
	RefreshImageList();
}
//void ctlTreeJSON::AddJSONValue(const wxJSONValue& parent, const wxJSONValue& jval);
wxTreeItemId ctlTreeJSON::addtree(const wxTreeItemId& idParent, wxString text, wxJSONValue* jval) {
	wxTreeItemId item;
	if (idParent.IsOk()) {
		item = AppendItem(idParent, text, -1, -1, 0);
	}
	else {
		item = AddRoot(text, -1, -1, 0);
	}
	if (m_root == idParent) {
		SetItemBackgroundColour(item, *wxLIGHT_GREY);
	}
	if (text.length() >= 7) {
		wxColour c = getColorFromString(text);
		if (c.IsOk()) {
			colors[item] = c;
		}
	}
	return item;
};
void ctlTreeJSON::RefreshImageList() {
	auto it = GetFirstVisibleItem();
	wxRect r;
	wxSize sz(15, 15);
	if (GetBoundingRect(it, r)) {
	#ifdef __WXGTK__
		sz.x=r.height;
		sz.y=r.height;
	#else
		sz.x = r.height - 2;
		sz.y = r.height - 2;
	#endif // __WXMSW__
	}
	//else return;
	wxVector<wxBitmapBundle> images;
	int n = 0;
	for (const auto& [item, color] : colors) {
		wxMemoryDC dc;
		wxBitmap bmp(sz.x, sz.y);
		wxMask* mask = new wxMask();
		int w = sz.x, h = sz.y;
		bmp.SetMask(mask);
		dc.SelectObject(bmp);
		if (color == wxNullColour)
			dc.SetBrush(wxBrush(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)));
		else
			dc.SetBrush(wxBrush(color));

		dc.DrawRectangle(0, 0, w, h);
		images.push_back(wxBitmapBundle(bmp));
		//SetItemImage(item, n);
		//wxString s = GetItemText(item);
		n++;
	}
	if (images.size() > 0) {
		SetImages(images);
		n = 0;
		for (const auto& [item, color] : colors) {
			SetItemImage(item, n);
			n++;
		}

	}
}
void ctlTreeJSON::LoadInTree(wxJSONValue& jval, const wxTreeItemId& idParent) {
	wxJSONValue def;
	if (jval.AsArray()) {
		for (int i = 0; i < jval.Size(); ++i) {
			wxString key = "";
			wxJSONValue jv = jval.Item(i);
			wxTreeItemId item;
			if (!(jv.IsObject() || jv.IsArray())) key += "" + jv.AsString();
			else
				key = wxString::Format("[%d]", i);
			item = addtree(idParent, key, &jv);
			conf[item] = jv;
			if ((jv.IsObject() || jv.IsArray()))
				LoadInTree(jv, item);
		}
	}
	else
		if (jval.IsObject()) {
			wxArrayString arr = jval.GetMemberNames();
			// sort
			struct ss {
				int sort_id;
				wxString key;
			};
			std::vector<ss> ord;
			for (int i = 0; i < arr.Count(); ++i) {
				const wxJSONValue& jv = jval.Get(arr[i], def);
				if (jv.AsArray()) ord.push_back({ 5000,arr[i] });
				else if (jv.IsObject())  ord.push_back({ 4000,arr[i] });
				else ord.push_back({ 3000,arr[i] });
			}
			std::sort(ord.begin(), ord.end(),
				[](const ss& lhs, const ss& rhs) {return std::tie(lhs.sort_id, lhs.key) < std::tie(rhs.sort_id, rhs.key); });
			// append tree
			for (const auto& j : ord) {
				wxJSONValue jv = jval.Item(j.key);
				wxTreeItemId item;
				wxString key = j.key;
				if (!(jv.IsObject() || jv.IsArray())) key += ":" + jv.AsString();
				if ((jv.IsArray())) key += "[]";
				if ((jv.IsObject())) key += "{}";
				item = addtree(idParent, key, &jv);
				conf[item] = jv;
				if ((jv.IsObject() || jv.IsArray())) LoadInTree(jv, item);
			}
		}
		else {
			wxTreeItemId item;
			wxJSONValue* vv = &jval;
			wxString text = jval.AsString();
			item = addtree(idParent, text, vv);
			conf[item] = jval;
		}
};

//ctlTreeJSON::ctlTreeJSON(wxWindow* parent, wxWindowID id, const wxPoint& pos, const wxSize& size, long style): wxTreeCtrl(parent, id, pos, size, style) {};

void ctlTreeJSON::InitMy() {
	//ReadJSON();
	settings->ReloadJsonFileIfNeed();

	wxJSONValue r(wxJSONTYPE_NULL);
	DeleteAllItems();
	colors.clear();
	conf.clear();
	orig.clear();
	findsId.clear();
	m_FindString = "";
	m_tree =settings->jsoncfg;
	if (m_tree.IsNull())  {
	} else {
		m_root = addtree(m_root, "root", &r);
		LoadInTree(m_tree, m_root);
		RefreshImageList();
#ifdef __WXMSW__
	// GTK 3 bug visualization
        ExpandAll();
#endif // __WXMSW__
	}
	m_change = false;
}
void ctlTreeJSON::Save() {
	if (m_change) {
		wxTreeItemId item = m_root, child;
		wxTreeItemIdValue cookie, cookie2;
		wxJSONValue v(wxJSONTYPE_OBJECT);
		wxString itemtext;
		int i = 0;
		child = GetFirstChild(m_root, cookie);
		while (child.IsOk())
		{
			wxString name = GetItemText(child);
			name = name.Left(name.length() - 2);
			wxJSONValue v2(wxJSONTYPE_OBJECT);
			child = GetFirstChild(child, cookie2);
			NodeToJSON(child, v2);
			v[name] = v2;
			settings->WriteJsonObect(name, v2);
			child = GetNextChild(m_root, cookie);
		}
		settings->WriteJsonFile();
		m_change = false;
	}

}
ctlTreeJSON::~ctlTreeJSON()
{
//	Save();
}

