/*hdr
**      FILE:           LgiRes.h
**      AUTHOR:         Matthew Allen
**      DATE:           22/10/00
**      DESCRIPTION:    Resource Editor App Header
**
*/

#include "lgi/common/Lgi.h"
#include "lgi/common/Net.h"
#include "lgi/common/DocApp.h"
#include "lgi/common/Properties.h"
#include "lgi/common/Variant.h"
#include "lgi/common/DataDlg.h"
#include "lgi/common/OptionsFile.h"

#include "resource.h"
#include "lgi/common/Tree.h"
#include "lgi/common/Box.h"
#include "lgi/common/EventTargetThread.h"

////////////////////////////////////////////////////////////////////////////////////////////
// Defines

// version
#define APP_VER						"4.1"

// window messages
#define	IDM_UNDO					201
#define	IDM_REDO					202
/*
#define	IDM_CUT						203
#define	IDM_COPY					204
#define	IDM_PASTE					205
*/

#define	IDM_DELETE					300
#define	IDM_RENAME					301
#define	IDM_SETTINGS				302
#define	IDM_IMPORT					303
#define	IDM_IMPORT_WIN32			304
#define	IDM_EXPORT					305
#define	IDM_EXPORT_WIN32			306
#define	IDM_NEW_LANG				307
#define	IDM_DELETE_LANG				308
#define	IDM_IMPORT_LANG				309
#define	IDM_PROPERTIES				310
#define IDM_NEW_SUB					311
#define IDM_NEW_ITEM				312
#define IDM_DELETE_ITEM				313
#define IDM_MOVE_LEFT				314
#define IDM_MOVE_RIGHT				315
#define IDM_SET_LANG				316
#define IDM_TAB_ORDER				317
#define	IDM_DUMP					318
#define IDM_COPY_TEXT				319
#define IDM_PASTE_TEXT				320
#define IDM_NEW_ID					321
#define IDM_COMPARE					322
#define IDM_MOVE_UP					324
#define IDM_MOVE_DOWN				325
#define IDM_REF_EQ_ID				326
#define IDM_ID_EQ_REF				327
#define IDM_UP						328
#define IDM_DOWN					329
#define IDM_SET_OK					330
#define IDM_SET_CANCEL				331

#define IDM_LANG_BASE				2000

#define	IDC_TABLE					999
#define	IDC_CHAT_MSG				1000

#define	STATUS_NORMAL				0
#define	STATUS_INFO					1
#define	STATUS_MAX					2

#define VAL_Ref						"Ref"
#define VAL_Id						"Id"
#define VAL_Define					"Define"
#define VAL_Tag						"Tag"
#define VAL_Text					"Text"
#define VAL_CellClass				"CellClass"
#define VAL_CellStyle				"CellStyle"
#define VAL_Text					"Text"
#define VAL_Pos						"Pos"
#define VAL_x1						"x1"
#define VAL_y1						"y1"
#define VAL_x2						"x2"
#define VAL_y2						"y2"
#define VAL_Visible					"Visible"
#define VAL_Enabled					"Enabled"
#define VAL_Class                   "Class"
#define VAL_Style                   "Style"
#define VAL_VerticalAlign			"valign"
#define VAL_HorizontalAlign			"align"
#define VAL_Children				"children"
#define VAL_Span					"span"
#define VAL_Image					"image"
#define VAL_Toggle					"toggle"

// Misc
class AppWnd;
#define	MainWnd						((AppWnd*)LApp::ObjInstance()->AppWnd)

// App
enum ObjectTypes {
    TYPE_CSS = 1,
    TYPE_DIALOG,
    TYPE_STRING,
    TYPE_MENU
};

enum IconTypes {
    ICON_FOLDER,
    ICON_IMAGE,
    ICON_ICON,
    ICON_CURSOR,
    ICON_DIALOG,
    ICON_STRING,
    ICON_MENU,
    ICON_DISABLED,
    ICON_CSS
};

#define OPT_ShowLanguages			"ShowLang"

#define StrDialogSymbols			"_Dialog Symbols_"
extern char TranslationStrMagic[];
extern char AppName[];

////////////////////////////////////////////////////////////////////////////////////////////
// Functions
extern char *EncodeXml(const char *s, int l = -1);
extern char *DecodeXml(const char *s, int l = -1);

////////////////////////////////////////////////////////////////////////////////////////////
// Classes
class AppWnd;
class ObjTreeItem;
class ResString;
typedef List<ResString> StringList;
class ResMenu;
class ResDialog;
class ResStringGroup;
class ResMenuItem;

struct ErrorInfo
{
	ResString *Str;
	LAutoString Msg;
};

class ErrorCollection
{
public:
	LArray<ErrorInfo> StrErr;
};

struct SerialiseContext
{
	ResFileFormat Format;
	LStringPipe Log;
	LArray<ResString*> FixId;
	
	SerialiseContext() : Log(512)
	{
		Format = Lr8File;
	}
	
	void PostLoad(AppWnd *App);
};

class Resource
{
	friend class AppWnd;
	friend class ObjTreeItem;

protected:
	int ResType;
	AppWnd *AppWindow;
	ObjTreeItem *Item;
	bool SysObject;

public:
	Resource(AppWnd *w, int t = 0, bool enabled = true);
	virtual ~Resource();

	AppWnd *App() { return AppWindow; }
	bool SystemObject() { return SysObject; }
	void SystemObject(bool i) { SysObject = i; }
	bool IsSelected();

	virtual LView *Wnd() { return NULL; }
	virtual bool Attach(LViewI *Parent);
	virtual int Type() { return ResType; }
	virtual void Type(int i) { ResType = i; }
	virtual void Create(LXmlTag *load, SerialiseContext *ctx) = 0; // called when users creates
	virtual ResStringGroup *GetStringGroup() { return 0; }
	
	// Sub classes
	virtual ResStringGroup *IsStringGroup() { return 0; }
	virtual ResDialog *IsDialog() { return 0; }
	virtual ResMenu *IsMenu() { return 0; }
	
	// Serialization
	virtual bool Test(ErrorCollection *e) = 0;
	virtual bool Read(LXmlTag *t, SerialiseContext &Ctx) = 0;
	virtual bool Write(LXmlTag *t, SerialiseContext &Ctx) = 0;
	virtual StringList *GetStrs() { return NULL; }

	// Clipboard
	virtual void Delete() {}
	virtual void Copy(bool Delete = false) {}
	virtual void Paste() {}

	// UI
	virtual LView *CreateUI() { return 0; }
	virtual void OnRightClick(LSubMenu *RClick) {}
	virtual void OnCommand(int Cmd) {}
	virtual void OnShowLanguages() {}
};

class ResFolder : public Resource, public LView
{
public:
	ResFolder(AppWnd *w, int t, bool enabled = true);
	LView *Wnd() { return dynamic_cast<LView*>(this); }

	void Create(LXmlTag *load, SerialiseContext *ctx) { LAssert(0); }
	bool Test(ErrorCollection *e) { return false; }
	bool Read(LXmlTag *t, SerialiseContext &Ctx) { return false; }
	bool Write(LXmlTag *t, SerialiseContext &Ctx) { return false; }
};

class ResFrame : public LLayout
{
	Resource *Child;

public:
	ResFrame(Resource *child);
	~ResFrame();

	bool Pour(LRegion &r);
	bool OnKey(LKey &k);
	void OnPaint(LSurface *pDC);
	bool Attach(LViewI *p);
	void OnFocus(bool b);
};

class ObjTreeItem : public LTreeItem
{
	friend class Resource;

	Resource *Obj;

public:
	ObjTreeItem(Resource *Object);
	~ObjTreeItem();

	Resource *GetObj() { return Obj; }
	const char *GetText(int i=0);
	void OnSelect();
	void OnMouseClick(LMouse &m);
};

class ObjContainer : public LTree
{
	friend class AppWnd;

	ObjTreeItem *Style;
	ObjTreeItem *Dialogs;
	ObjTreeItem *Strings;
	ObjTreeItem *Menus;

	LImageList *Images;
	AppWnd *Window;

	bool AppendChildren(ObjTreeItem *Item, List<Resource> &Lst);

public:
	ObjContainer(AppWnd *w);
	~ObjContainer();

	Resource *CurrentResource();
	bool ListObjects(List<Resource> &Lst);
};

class FieldTree
{
public:
	enum FieldMode
	{
		None,
		UiToObj,
		ObjToUi,
		ObjToStore,
		StoreToObj,
	};

	struct Field
	{
		// Global
		FieldTree *Tree;
		LAutoString Label;
		LAutoString Name;
		int Type;
		int Id;
		bool Multiline;
		void *Token;

		Field(FieldTree *tree)
		{
			Tree = tree;
			Type = 0;
			Id = 0;
			Token = 0;
			Multiline = false;
		}
	};

	typedef LArray<Field*> FieldArr;

protected:
	int &NextId;
	FieldMode Mode;
	LViewI *View;
	LDom *Store;
	bool Deep;

	LHashTbl<PtrKey<void*>, FieldArr*> f;

	FieldArr *Get(void *Token, bool Create = false)
	{
		FieldArr *a = f.Find(Token);
		if (!a)
		{
			if (Create)
				f.Add(Token, a = new FieldArr);
			else
				LAssert(0);
		}
		return a;
	}

	Field *GetField(void *Token, const char *FieldName)
	{
		if (!Token || !FieldName) return 0;

		FieldArr *a = Get(Token);
		if (!a) return 0;

		for (int i=0; i<a->Length(); i++)
		{
			if (!stricmp((*a)[i]->Name, FieldName))
				return (*a)[i];
		}

		return 0;
	}

public:
	FieldTree(int &next, bool deep) : NextId(next)
	{
		Mode = None;
		View = 0;
		Store = 0;
		Deep = deep;
	}

	~FieldTree()
	{
		Empty();
	}

	FieldMode GetMode()
	{
		return Mode;
	}
	
	bool GetDeep()
	{
		return Deep;
	}

	void SetMode(FieldMode m)
	{
		Mode = m;
	}

	void SetView(LViewI *v)
	{
		View = v;
	}

	void SetStore(LDom *p)
	{
		Store = p;
	}

	void Empty()
	{
		// for (FieldArr *a = f.First(); a; a = f.Next())
		for (auto a : f)
		{
			a.value->DeleteObjects();
		}
		f.DeleteObjects();
		View = 0;
	}

	void Insert(void *Token, int Type, int Reserved, const char *Name, const char *Label, int Idx = -1, bool Multiline = false)
	{
		FieldArr *a = Get(Token, true);
		if (!a) return;

		Field *n = new Field(this);
		if (n)
		{
			n->Token = Token;
			n->Label.Reset(NewStr(Label));
			n->Name.Reset(NewStr(Name));
			n->Id = NextId++;
			n->Type = Type;
			n->Multiline = Multiline;
			a->Add(n);
		}
	}

	void Serialize(void *Token, const char *FieldName, int &i)
	{
		Field *f = GetField(Token, FieldName);
		if (!f) return;
		LVariant v;

		switch (Mode)
		{
			case ObjToUi:
				View->SetCtrlValue(f->Id, i);
				break;
			case UiToObj:
				i = (int)View->GetCtrlValue(f->Id);
				break;
			case StoreToObj:
				if (Store->GetValue(FieldName, v))
					i = v.CastInt32();
				break;
			case ObjToStore:
				Store->SetValue(FieldName, v = i);
				break;
			default:
				LAssert(0);
		}
	}

	void Serialize(void *Token, const char *FieldName, bool &b, int Default = -1)
	{
		Field *f = GetField(Token, FieldName);
		if (!f) return;
		LVariant i;

		switch (Mode)
		{
			case ObjToUi:
				View->SetCtrlValue(f->Id, b);
				break;
			case UiToObj:
				b = View->GetCtrlValue(f->Id);
				break;
			case StoreToObj:
			{
				if (Store->GetValue(FieldName, i))
				{
					b = i.CastInt32() != 0;
				}
				break;
			}
			case ObjToStore:
				if (Default < 0 || (bool)Default != b)
					Store->SetValue(FieldName, i = b);
				break;
			default:
				LAssert(0);
		}
	}

	void Serialize(void *Token, const char *FieldName, char *&s)
	{
		Field *f = GetField(Token, FieldName);
		if (!f) return;
		LVariant v;

		switch (Mode)
		{
			case ObjToUi:
				View->SetCtrlName(f->Id, s);
				break;
			case UiToObj:
			{
				DeleteArray(s);
				auto t = View->GetCtrlName(f->Id);
				if (ValidStr(t))
					s = NewStr(t);
				break;
			}
			case StoreToObj:
			{
				DeleteArray(s);
				if (Store->GetValue(FieldName, v))
					s = v.ReleaseStr();
				break;
			}
			case ObjToStore:
				Store->SetValue(FieldName, v = s);
				break;
			default:
				LAssert(0);
		}
	}

	void Serialize(void *Token, const char *FieldName, LAutoString &s)
	{
		Field *f = GetField(Token, FieldName);
		if (!f) return;
		LVariant v;

		switch (Mode)
		{
			case ObjToUi:
				View->SetCtrlName(f->Id, s);
				break;
			case UiToObj:
			{
				auto t = View->GetCtrlName(f->Id);
				s.Reset(ValidStr(t) ? NewStr(t) : 0);
				break;
			}
			case StoreToObj:
			{
				s.Reset(Store->GetValue(FieldName, v) ? v.ReleaseStr() : 0);
				break;
			}
			case ObjToStore:
				Store->SetValue(FieldName, v = s);
				break;
			default:
				LAssert(0);
		}
	}

	void Serialize(void *Token, const char *FieldName, LString &s)
	{
		Field *f = GetField(Token, FieldName);
		if (!f) return;
		LVariant v;

		switch (Mode)
		{
			case ObjToUi:
				View->SetCtrlName(f->Id, s);
				break;
			case UiToObj:
				s = View->GetCtrlName(f->Id);
				break;
			case StoreToObj:
				if (Store->GetValue(FieldName, v))
					s = v.Str();
				else
					s.Empty();
				break;
			case ObjToStore:
				Store->SetValue(FieldName, v = s.Get());
				break;
			default:
				LAssert(0);
		}
	}

	void Serialize(void *Token, const char *FieldName, LRect &r)
	{
		Field *f = GetField(Token, FieldName);
		if (!f) return;
		LVariant v;

		switch (Mode)
		{
			case ObjToUi:
				View->SetCtrlName(f->Id, r.GetStr());
				break;
			case UiToObj:
			{
				r.SetStr(View->GetCtrlName(f->Id));
				break;
			}
			case StoreToObj:
			{
				if (Store->GetValue(FieldName, v))
				{
					r.SetStr(v.Str());
				}
				break;
			}
			case ObjToStore:
			{
				Store->SetValue(FieldName, v = r.GetStr());
				break;
			}
			default:
				LAssert(0);
		}
	}

	static int FieldArrCmp(FieldArr **a, FieldArr **b)
	{
		return (**a)[0]->Id - (**b)[0]->Id;
	}

	void GetAll(LArray<FieldArr*> &Fields)
	{
		// for (FieldArr *a = f.First(0); a; a = f.Next(0))
		for (auto a : f)
		{
			Fields.Add(a.value);
		}
		Fields.Sort(FieldArrCmp);
	}
};

#define M_OBJECT_CHANGED (M_USER + 4567)
class FieldSource
{
	friend class FieldView;
	int _FieldView;

public:
	FieldSource()
	{
		_FieldView = -1;
	}
	
	virtual ~FieldSource() {}

	virtual bool GetFields(FieldTree &Fields) = 0;
	virtual bool Serialize(FieldTree &Fields) = 0;
	
	void OnFieldChange()
	{
		if (_FieldView >= 0)
			PostThreadEvent(_FieldView, M_OBJECT_CHANGED, (LMessage::Param)this);
	}
};

#include "LgiRes_Dialog.h"

class FieldView : public LLayout
{
protected:
	FieldSource *Source;
	FieldTree Fields;
	int NextId;
	bool Ignore;
	AppWnd *App;

public:
	FieldView(AppWnd *app);
	~FieldView();

	void Serialize(bool Write);

	void OnPosChange();
	void OnSelect(FieldSource *s);
	void OnDelete(FieldSource *s);
	LMessage::Result OnEvent(LMessage *m);
	void OnPaint(LSurface *pDC);
	int OnNotify(LViewI *Ctrl, LNotification n);
};

class ShortCutView : public LWindow
{
	AppWnd *App;
	LList *Lst;

public:
	ShortCutView(AppWnd *app);
	~ShortCutView();

	void OnDialogChange(ResDialog *Dlg);
	int OnNotify(LViewI *Ctrl, LNotification n);
};

#include "LgiRes_String.h"

class AppWnd : public LDocApp<LOptionsFile>
{
protected:
	// UI
	LBox			*HBox;
	LBox			*VBox;
	LView			*ContentView;

	LSubMenu		*Edit;
	LSubMenu		*Help;
	LSubMenu		*ViewMenu;

	LStatusBar		*Status;
	LStatusPane		*StatusInfo[STATUS_MAX];

	ShortCutView	*ShortCuts;

	// App
	ObjContainer	*Objs;
	Resource		*LastRes;
	FieldView		*Fields;
	
	// Languages
	int				CurLang;
	LArray<LLanguage*> Languages;
	LHashTbl<ConstStrKey<char,false>, bool> ShowLanguages;

	void SortDialogs();
	void GetFileTypes(LFileSelect *Dlg, bool Write);

public:
	AppWnd();
	~AppWnd();

	// Languages
	bool ShowLang(LLanguageId Lang);
	void ShowLang(LLanguageId Lang, bool Show);
	LLanguage *GetCurLang();
	void SetCurLang(LLanguage *L);
	LArray<LLanguage*> *GetLanguages();
	void OnLanguagesChange(LLanguageId Lang, bool Add, bool Update = false);

	// ---------------------------------------------------------------------
	// Application
	Resource *NewObject(SerialiseContext ctx, LXmlTag *load, int Type, bool Select = true);
	bool InsertObject(int Type, Resource *r, bool Select = true);
	void DelObject(Resource *r);
	bool ListObjects(List<Resource> &Lst);
	int GetUniqueStrRef(int Start = 1);
	int GetUniqueCtrlId();
	void FindStrings(List<ResString> &Strs, char *Define = 0, int *CtrlId = 0);
	ResString *GetStrFromRef(int Ref);
	ResStringGroup *GetDialogSymbols();

	bool Empty();
	void OnObjChange(FieldSource *r);
	void OnObjSelect(FieldSource *r);
	void OnObjDelete(FieldSource *r);
	void OnResourceSelect(Resource *r);
	void OnResourceDelete(Resource *r);
	void GotoObject(class ResString *s,
					ResStringGroup *g,
					ResDialog *d,
					ResMenuItem *m,
					ResDialogCtrl *c);
	
	ShortCutView *GetShortCutView();
	void OnCloseView(ShortCutView *v);

	// ---------------------------------------------------------------------
	// Methods
	void SetStatusText(char *Text, int Pane = 0);
	bool TestLgi(bool Quite = true);
	bool LoadLgi(const char *FileName = 0);
	bool SaveLgi(const char *FileName = 0);
	bool LoadWin32(const char *FileName = 0);
	bool SaveWin32();
	void ImportLang();
	void Compare();
	bool WriteDefines(LFile &Defs);

	bool OpenFile(const char *FileName, bool Ro);
	bool SaveFile(const char *FileName);

	// ---------------------------------------------------------------------
	// Window
	int OnNotify(LViewI *Ctrl, LNotification n);
	LMessage::Result OnEvent(LMessage *m);
	int OnCommand(int Cmd, int Event, OsView Handle);
	void OnReceiveFiles(LArray<const char*> &Files);
	void OnCreate();
};

#define INVALID_INT			-10000
#define NEW_UI				1

struct SearchParams
{
	LString::Array Text;
	#if NEW_UI
	bool LimitToText;
	bool LimitToDefine;
	#else
	LString Define;
	#endif
	LLanguageId InLang;
	LLanguageId NotInLang;
	int Ref;
	int CtrlId;

	SearchParams()
	{
		InLang = NULL;
		NotInLang = NULL;
		Ref = INVALID_INT;
		CtrlId = INVALID_INT;
		#if NEW_UI
		LimitToText = false;
		LimitToDefine = false;
		#else
		#endif
	}
};

class SearchThread : public LEventTargetThread
{
	List<Resource> Res;
	AppWnd *App;
	LList *Results;
	SearchParams Params;
	LCancel State;

	bool HasContent(char *s);
	class Result *Test(class ResString *s);
	class Result *Test(class ResMenuItem *mi);

public:
	SearchThread(AppWnd *app, LList *results);

	void Search(SearchParams &p);
	LMessage::Result OnEvent(LMessage *Msg);
};

class Search : public LDialog, public SearchParams
{
	AppWnd *App;
	LAutoPtr<SearchThread> Thread;
	void OnCheck();

public:
	Search(AppWnd *app);
	int OnNotify(LViewI *c, LNotification n);
};

class Results : public LWindow
{
	class ResultsPrivate *d;

public:
	Results(AppWnd *app, Search *search);
	~Results();

	void OnPosChange();
	int OnNotify(LViewI *v, LNotification n);

};

class ShowLanguagesDlg : public LDialog
{
	class ShowLanguagesDlgPriv *d;

public:
	ShowLanguagesDlg(AppWnd *app);
	~ShowLanguagesDlg();

	int OnNotify(LViewI *v, LNotification n);
};

class ResCss : public Resource, public LLayout
{
    friend class ResCssUi;
    
protected:
	class ResCssUi *Ui;
	LAutoString Style;

public:
	ResCss(AppWnd *w, int type = TYPE_CSS);
	~ResCss();

	void Create(LXmlTag *Load, SerialiseContext *Ctx);
	LView *Wnd() { return dynamic_cast<LView*>(this); }
	void OnShowLanguages();

	// Resource
	LView *CreateUI();
	void OnRightClick(LSubMenu *RClick);
	void OnCommand(int Cmd);
	int OnCommand(int Cmd, int Event, OsView hWnd);

	// Serialize
	bool Test(ErrorCollection *e);
	bool Read(LXmlTag *t, SerialiseContext &Ctx);
	bool Write(LXmlTag *t, SerialiseContext &Ctx);
};

extern void OpenTableLayoutTest(LViewI *p);
