#include "lgi/common/Lgi.h"
#include "lgi/common/TextView3.h"
#include "lgi/common/Tree.h"
#include "lgi/common/TabView.h"
#include "lgi/common/Box.h"
#include "resource.h"
#include "lgi/common/SpellCheck.h"
#include "lgi/common/DisplayString.h"
#include "lgi/common/Button.h"
#include "lgi/common/Http.h"
#include "lgi/common/OptionsFile.h"
#include "lgi/common/List.h"
#include "lgi/common/Menu.h"
#include "lgi/common/FileSelect.h"

#if 1
#include "lgi/common/RichTextEdit.h"
typedef LRichTextEdit EditCtrl;
#else
#include "LHtmlEdit.h"
typedef LHtmlEdit EditCtrl;
#endif

enum Ctrls
{
	IDC_EDITOR = 100,
	IDC_HTML,
	IDC_HTML_BOX,
	IDC_IMAGES,
	IDC_TABS,
	IDC_TREE,
	IDC_TO_HTML,
	IDC_TO_NODES,
	IDC_VIEW_IN_BROWSER,
	IDC_SAVE_FILE,
	IDC_SAVE,
	IDC_EXIT,
	IDC_INSTALL
};

enum Messages
{
	M_INSTALL = M_USER + 200,
};

const char *AppName = "HtmlEdit";
#define LOAD_DOC 1
#define SrcFileName	"Reply4.html"

#if 0

char Src[] =
	"This is a test. A longer string for the purpose of inter-line cursor testing.<br>\n"
	"A longer string for the <b>purpose</b> of inter-line cursor testing.<br>\n"
	"--<br>"
	"<a href=\"http://web/~matthew\">Matthew Allen</a>";

#else

char Src[] =	
	"<html>\n"
	"<body style=\"font-size: 9pt;\">Hi Suzy,<br/>\n"
	"<br/>\n"
	"The aircon is a little warm in our area of the woods. There seems to be nothing coming out of the 2nd AC unit.<br/>\n"
	"<br/>\n"
	"Regards<br/>\n"
	"--<br/>\n"
	"<a href=\"http://web/~matthew\">Matthew Allen</a>\n"
	"</body>\n"
	"</html>";

#endif

class GCapabilityInstallTarget : public LCapabilityTarget
{
public:
	virtual void StartInstall(CapsHash *Caps) = 0;
};

class CapsBar : public LView
{
	GCapabilityInstallTarget *App;
	LCapabilityTarget::CapsHash *Caps;
	LButton *Ok, *Install;

public:
	CapsBar(GCapabilityInstallTarget *Parent, LCapabilityTarget::CapsHash *caps)
	{
		App = Parent;
		Caps = caps;
		Ok = new LButton(IDOK, 0, 0, -1, -1, "Ok");
		Install = new LButton(IDC_INSTALL, 0, 0, -1, -1, "Install");
	}

	bool Pour(LRegion &r)
	{
		LRect *rc = FindLargestEdge(r, GV_EDGE_TOP);
		if (!rc)
			return false;

		LRect p = *rc;
		p.y2 = p.y1 + LSysFont->GetHeight() + 11;
		SetPos(p);
		return true;	
	}

	int OnNotify(LViewI *Ctrl, LNotification n)
	{
		switch (Ctrl->GetId())
		{
			case IDOK:
			{
				App->OnCloseInstaller();
				break;
			}
			case IDC_INSTALL:
			{
				App->StartInstall(Caps);
				break;
			}
		}
		return 0;
	}

	void OnPaint(LSurface *pDC)
	{
		pDC->Colour(LColour::Red);
		pDC->Rectangle();

		LSysFont->Colour(LColour::White, LColour::Red);
		LSysFont->Transparent(true);

		LString s = "Missing components: ";
		// const char *k;
		// for (bool b = Caps->First(&k); b; b = Caps->Next(&k))
		for (auto k : *Caps)
		{
			s += k.key;
			s += " ";
		}

		LDisplayString d(LSysFont, s);
		d.Draw(pDC, 6, 6);

		if (Ok && Install)
		{
			int x = GetClient().x2 - 6;
			if (!Ok->IsAttached())
				Ok->Attach(this);
			LRect r;
			r.Set(0, 0, Ok->X()-1, Ok->Y()-1);
			r.Offset(x - r.X(), (Y() - r.Y()) >> 1);
			Ok->SetPos(r);			

			if (!Install->IsAttached())
				Install->Attach(this);
			r.Set(0, 0, Install->X()-1, Install->Y()-1);
			r.Offset(Ok->GetPos().x1 - 6 - r.X(), (Y() - r.Y()) >> 1);
			Install->SetPos(r);
		}
			
	}
};

class InstallThread : public LEventTargetThread
{
	int AppHnd;

public:
	InstallThread(int appHnd) : LEventTargetThread("InstallThread")
	{
		AppHnd = appHnd;
	}

	LMessage::Result OnEvent(LMessage *m)
	{
		switch (m->Msg())
		{
			case M_INSTALL:
			{
				LAutoPtr<LString> Component((LString*)m->A());
				const char *Base = "http://memecode.com/components/lookup.php?app=Scribe&wordsize=%i&component=%s&os=win64&version=2.2.0";
				LString s;
				s.Printf(Base, sizeof(NativeInt)*8, Component->Get());
				#if _MSC_VER == _MSC_VER_VS2013
				s += "&tags=vc12";
				#elif _MSC_VER == _MSC_VER_VS2008
				s += "&tags=vc9";
				#endif

				GMemStream o(1024);
				LString err;
				int Installed = 0;
				if (!LgiGetUri(this, &o, &err, s))
				{
					LgiTrace("%s:%i - Get URI failed.\n", _FL);
					break;
				}

				LXmlTree t;
				LXmlTag r;
				o.SetPos(0);
				if (!t.Read(&r, &o))
				{
					LgiTrace("%s:%i - Bad XML.\n", _FL);
					break;
				}

				if (!r.IsTag("components"))
				{
					LgiTrace("%s:%i - No components tag.\n", _FL);
					break;
				}

				for (auto c: r.Children)
				{
					if (c->IsTag("file"))
					{
						const char *Link = c->GetContent();
						GMemStream File(1024);
						if (LgiGetUri(this, &File, &err, Link))
						{
							char p[MAX_PATH];
							LMakePath(p, sizeof(p), LGetExeFile(), "..");
							LMakePath(p, sizeof(p), p, LGetLeaf(Link));

							LFile f;
							if (f.Open(p, O_WRITE))
							{
								f.SetSize(0);
								if (f.Write(File.GetBasePtr(), File.GetSize()) == File.GetSize())
								{
									LAutoPtr<LString> comp(new LString(*Component));
									PostObject(AppHnd, M_INSTALL, comp);
									Installed++;
								}
								else
									LgiTrace("%s:%i - Couldn't write to '%p'.\n", _FL, p);
							}
							else LgiTrace("%s:%i - Can't open '%s' for writing.\n", _FL, p);
						}
						else LgiTrace("%s:%i - Link download failed.\n", _FL);
					}
				}

				if (Installed == 0)
				{
					LgiTrace("%s:%i - No installed components from URI:\n%s\n", _FL, s.Get());
				}
				break;
			}
		}

		return 0;
	}
};

class MediaItem : public LListItem
{
	LDocView::ContentMedia *Cm;
	LString sSize;

public:
	MediaItem(LDocView::ContentMedia *cm)
	{
		Cm = cm;
	}

	void OnMouseClick(LMouse &m)
	{
		if (m.Left() && m.Double())
		{
			LFile::Path p(LSP_TEMP);
			p += Cm->FileName;
			LFile f;
			if (f.Open(p, O_WRITE))
			{
				if (Cm->Stream)
				{
					GCopyStreamer Cp;
					Cp.Copy(Cm->Stream, &f);
				}
				else if (Cm->Data.Type == GV_BINARY)
					f.Write(Cm->Data.Value.Binary.Data, Cm->Data.Value.Binary.Length);
				f.Close();
			}

			LExecute(p);
		}
	}

	const char *GetText(int i)
	{
		switch (i)
		{
			case 0: // Filename
			{
				return Cm->FileName;
			}
			case 1: // Size
			{
				int64 Sz = 0;

				if (Cm->Stream.Get())
					Sz = Cm->Stream->GetSize();
				else if (Cm->Data.Type == GV_BINARY)
					Sz = Cm->Data.Value.Binary.Length;

				char s[64];
				LFormatSize(s, sizeof(s), Sz);
				sSize = s;
				return sSize;
			}
			case 2:
			{
				return Cm->MimeType;
			}
			case 3:
			{
				return Cm->Id;
			}
		}

		return NULL;
	}
};

class App : public LWindow, public GCapabilityInstallTarget
{
	LBox *Split;
	LTextView3 *Txt;
	LList *Imgs;
	LTabView *Tabs;
	LTree *Tree;
	uint64 LastChange;

	EditCtrl *Edit;

	LAutoPtr<LSpellCheck> Speller;
	CapsBar *Bar;
	LCapabilityTarget::CapsHash Caps;
	LAutoPtr<LEventTargetThread> Installer;
	LOptionsFile Options;
	LArray<LDocView::ContentMedia> Media;

public:
	App() : Options(LOptionsFile::PortableMode, AppName)
	{
		LastChange = 0;
		Edit = 0;
		Txt = 0;
		Bar = NULL;
		Tabs = NULL;
		Tree = NULL;
		Imgs = NULL;
		Name("Rich Text Testbed");

		if (!Options.SerializeFile(false) ||
			!SerializeState(&Options, "WndState", true))
		{
			LRect r(0, 0, 1200, 800);
			SetPos(r);
			MoveToCenter();
		}

		SetQuitOnClose(true);
		#ifdef WINNATIVE
		SetIcon((const char*)IDI_APP);
		#endif
		
		#if defined(WINNATIVE)
		Speller = CreateWindowsSpellCheck();
		#elif defined(LINUX)
		// This is currently in the Scribe code base... should be moved into Lgi?
		// Speller = CreateAspellObject();
		#elif defined(MAC) && !defined(__GTK_H__)
		Speller = CreateAppleSpellCheck();
		#endif
		
		if (Attach(0))
		{
			Menu = new LMenu;
			if (Menu)
			{
				Menu->Attach(this);

				auto s = Menu->AppendSub("&File");
				s->AppendItem("To &HTML", IDC_TO_HTML, true, -1, "F5");
				s->AppendItem("To &Nodes", IDC_TO_NODES, true, -1, "F6");
				s->AppendItem("View In &Browser", IDC_VIEW_IN_BROWSER, true, -1, "F7");
				s->AppendItem("&Save", IDC_SAVE, true, -1, "Ctrl+S");
				s->AppendItem("Save &As", IDC_SAVE_FILE, true, -1, "Ctrl+Shift+S");
				s->AppendSeparator();
				s->AppendItem("E&xit", IDC_EXIT, true, -1, "Ctrl+W");
			}

			AddView(Split = new LBox);
			if (Split)
			{
				// Split->Debug();
				
				Split->AddView(Edit = new EditCtrl(IDC_EDITOR));
				if (Edit)
				{
					if (Speller)
					{
						LVariant v;
						Edit->SetValue("SpellCheckLanguage", v = "English");
						Edit->SetValue("SpellCheckDictionary", v = "AU");
						Edit->SetSpellCheck(Speller);
					}
					Edit->Sunken(true);
					Edit->SetId(IDC_EDITOR);
					Edit->Register(this);
					LVariant v;
					Edit->SetValue("HtmlImagesLinkCid", v = true);

					#if LOAD_DOC
					#ifndef SrcFileName
					Edit->Name(Src);
					#else
					char p[MAX_PATH];
					LGetSystemPath(LSP_APP_INSTALL, p, sizeof(p));
					LMakePath(p, sizeof(p), p, "Test");
					LMakePath(p, sizeof(p), p, SrcFileName);
					LAutoString html(LReadTextFile(p));
					if (html)
						Edit->Name(html);
					#endif
					#endif
				}

				Split->AddView(Tabs = new LTabView(IDC_TABS));
				if (Tabs)
				{
					// Tabs->Debug();
					
					LTabPage *p = Tabs->Append("Html Output");
					if (p)
					{
						LBox *b = new LBox(IDC_HTML_BOX);
						b->SetVertical(true);
						
						b->AddView(Txt = new LTextView3(IDC_HTML, 0, 0, 100, 100));
						Txt->SetPourLargest(true);
						b->AddView(Imgs = new LList(IDC_IMAGES));
						Imgs->GetCss(true)->Height(LCss::Len("150px"));
						Imgs->AddColumn("Filename", 200);
						Imgs->AddColumn("Size", 100);
						Imgs->AddColumn("MimeType", 100);
						Imgs->AddColumn("Cid", 100);

						p->Append(b);
					}
					
					p = Tabs->Append("Node View");
					if (p)
					{
						p->AddView(Tree = new LTree(IDC_TREE, 0, 0, 100, 100));
						Tree->SetPourLargest(true);
					}
				}

				Split->Value(GetClient().X()/2);
			}

			AttachChildren();
			PourAll();
			Visible(true);

			if (Edit)
				Edit->Focus(true);
		}
	}

	~App()
	{
		SerializeState(&Options, "WndState", false);
		Options.SerializeFile(true);
		Installer.Reset();
	}

	bool NeedsCapability(const char *Name, const char *Param = NULL)
	{
		if (Caps.Find(Name))
			return true;

		Caps.Add(Name, true);

		if (!Bar)
		{
			Bar = new CapsBar(this, &Caps);
			if (Bar)
			{
				AddView(Bar, 0);
				AttachChildren();
				
				PourAll();
				Invalidate();
			}
		}
		return true;
	}
	
	void StartInstall(CapsHash *Caps)
	{
		if (!Installer)
			Installer.Reset(new InstallThread(AddDispatch()));
		if (Installer)
		{
			// const char *c;
			// for (bool b = Caps->First(&c); b; b = Caps->Next(&c))
			for (auto c : *Caps)
			{
				LAutoPtr<LString> s(new LString(c.key));
				Installer->PostObject(Installer->GetHandle(), M_INSTALL, s);
			}
		}
	}

	void OnInstall(CapsHash *Caps, bool Status)
	{
		DeleteObj(Bar);
		if (Edit && Caps && Status)
		{
			// const char *k;
			// for (bool b = Caps->First(&k); b; b = Caps->Next(&k))
			for (auto k : *Caps)
				Edit->PostEvent(M_COMPONENT_INSTALLED, new LString(k.key));
		}
		PourAll();
	}

	void OnCloseInstaller()
	{
		DeleteObj(Bar);
		PourAll();
	}

	int OnCommand(int Cmd, int Event, OsView Wnd)
	{
		switch (Cmd)
		{
			case IDC_EXIT:
				LCloseApp();
				break;
			case IDC_TO_HTML:
			{
				Tabs->Value(0);

				LString Out;
				Imgs->Empty();
				Media.Empty();
				if (Edit->GetFormattedContent("text/html", Out, &Media))
				{
					Txt->Name(Out);

					for (auto &m : Media)
					{
						Imgs->Insert(new MediaItem(&m));
					}
					Imgs->ResizeColumnsToContent();
				}
				break;
			}
			case IDC_TO_NODES:
				Tabs->Value(1);
				#ifdef _DEBUG
				Tree->Empty();
				Edit->DumpNodes(Tree);
				#endif
				break;
			case IDC_VIEW_IN_BROWSER:
			{
				LFile::Path p(LSP_TEMP);
				p += "export.html";
				if (Edit->Save(p))
				{
					LExecute(p);
				}
				break;
			}
			case IDC_SAVE_FILE:
			{
				LFileSelect s;
				s.Parent(this);
				s.Type("HTML", "*.html");
				if (s.Save())
					Edit->Save(s.Name());
				break;
			}
			case IDC_SAVE:
			{
				if (Edit)
				{
					LString Html;
					LArray<LDocView::ContentMedia> Media;
					if (Edit->GetFormattedContent("text/html", Html, &Media))
					{
						LFile::Path p(LSP_APP_INSTALL);
						p += "Output";
						if (!p.IsFolder())
							FileDev->CreateFolder(p);
						p += "output.html";
						LFile f;
						if (f.Open(p, O_WRITE))
						{
							f.SetSize(0);
							f.Write(Html);
							f.Close();
							
							LString FileName = p.GetFull();
							
							for (unsigned i=0; i<Media.Length(); i++)
							{
								LDocView::ContentMedia &Cm = Media[i];
								
								p--;
								p += Cm.FileName;
								if (f.Open(p, O_WRITE))
								{
									f.SetSize(0);
									if (Cm.Data.IsBinary())
										f.Write(Cm.Data.CastVoidPtr(), Cm.Data.Value.Binary.Length);
									else if (Cm.Stream)
									{
										GCopyStreamer Cp;
										Cp.Copy(Cm.Stream, &f);
									}
									else
										LAssert(0);
									f.Close();
								}
							}
							
							LExecute(FileName);
						}
					}
				}
				break;
			}
		}

		return LWindow::OnCommand(Cmd, Event, Wnd);
	}

	int OnNotify(LViewI *c, LNotification n)
	{
		switch (c->GetId())
		{
			case IDC_EDITOR:
			{
				if ((n.Type == LNotifyDocChanged || n.Type == LNotifyCursorChanged) &&
					Edit)
				{
					LastChange = LCurrentTime();
					Tree->Empty();
				}
				break;
			}
			case IDC_TREE:
			{
				LTreeItem *i = Tree->Selection();
				
				LHashTbl<ConstStrKey<char>,LString> vars;
				if (i)
				{
					auto p = LString(i->GetText()).Split(",");
					for (auto i : p)
					{
						LString::Array a = i.Strip().Split("=");
						if (a.Length() == 2)
							vars.Add(a[0], a[1]);
					}
				}

				switch (n.Type)
				{
					case LNotifyItemSelect:
					{
						#ifdef _DEBUG
						LString Ptr = vars.Find("ptr");
						if (Ptr)
							Edit->SelectNode(Ptr);
						#endif
						break;
					}
					default:
						break;
				}
				break;
			}
		}

		return 0;
	}
	
	void OnReceiveFiles(LArray<const char*> &Files)
	{
		if (Edit && Files.Length() > 0)
		{
			LAutoString t(LReadTextFile(Files[0]));
			if (t)
				Edit->Name(t);
		}
	}

	LMessage::Result OnEvent(LMessage *m)
	{
		if (m->Msg() == M_INSTALL)
		{
			LAutoPtr<LString> c((LString*)m->A());
			if (c)
			{
				LCapabilityTarget::CapsHash h;
				h.Add(*c, true);
				OnInstall(&h, true);
			}
		}

		return LWindow::OnEvent(m);
	}
};

int LgiMain(OsAppArguments &AppArgs)
{
	LApp a(AppArgs, "RichEditTest");
	a.AppWnd = new App;
	a.Run();
	return 0;
}
