#include <stdlib.h>
#include <stdio.h>

#include "lgi/common/Lgi.h"
#include "lgi/common/Variant.h"
#include "lgi/common/SkinEngine.h"
#include "lgi/common/Combo.h"
#include "lgi/common/DisplayString.h"
#include "lgi/common/LgiRes.h"
#include "lgi/common/Menu.h"

#define COMBO_HEIGHT			20

int IntCompare(char *a, char *b, NativeInt c)
{
	int A = atoi(a);
	int B = atoi(b);
	return A - B;
}

int DblCompare(char *a, char *b, NativeInt c)
{
	double A = atof(a);
	double B = atof(b);
	return A > B ? 1 : A < B ? -1 : 0;
}

int StringCompare(char *a, char *b, NativeInt c)
{
	return stricmp(a, b);
}

class LComboPrivate
{
	LDisplayString *Text;

public:
	ssize_t Current;
	bool SortItems;
	int Sub;
	LRect Arrow;
	List<char> Items;
	uint64 LastKey;
	LAutoString Find;
	LSubMenu *Menu;
	LCombo::SelectedState SelState;
	bool LayoutDirty;

	#if defined LGI_CARBON
	ThemeButtonDrawInfo Cur;
	#endif

	LComboPrivate()
	{
		LayoutDirty = false;
		SelState = LCombo::SelectedDisable;
		SortItems = false;
		Sub = false;
		Current = 0;
		LastKey = 0;
		Menu = 0;
		Text = 0;

		#if defined LGI_CARBON
		Cur.state = kThemeStateInactive;
		Cur.value = kThemeButtonOff;
		Cur.adornment = kThemeAdornmentNone;
		#endif
	}

	~LComboPrivate()
	{
		Items.DeleteArrays();
		DeleteObj(Menu);
		DeleteObj(Text);
	}
	
	LDisplayString *GetText(const char *File, int Line)
	{
		return Text;
	}

	void SetText(LDisplayString *ds, const char *File, int Line)
	{
		DeleteObj(Text);
		Text = ds;
	}
	
	LDisplayString **GetTextPtr()
	{
		return &Text;
	}
};

LRect LCombo::Pad(8, 4, 24, 4);

LCombo::LCombo(int id, int x, int y, int cx, int cy, const char *name) :
	ResObject(Res_ComboBox)
{
	d = new LComboPrivate;
	
	SetId(id);
	LRect e(x, y, x+cx, y+cy);
	SetPos(e);
	Name(name);
	SetTabStop(true);

	#if WINNATIVE
	SetDlgCode(DLGC_WANTARROWS);
	SetStyle(GetStyle() | WS_CHILD | WS_VISIBLE);
	#endif
	LResources::StyleElement(this);
}

LCombo::~LCombo()
{
	DeleteObj(d);
}

bool LCombo::Sort()
{
	return d->SortItems;
}

void LCombo::Sort(bool s)
{
	d->SortItems = s;
}

int LCombo::Sub()
{
	return d->Sub;
}

void LCombo::Sub(int s) // GV_???
{
	d->Sub = s;
}

LSubMenu *LCombo::GetMenu()
{
	return d->Menu;
}

void LCombo::SetMenu(LSubMenu *m)
{
	d->Menu = m;
}

size_t LCombo::Length()
{
	return d->Items.Length();
}

char *LCombo::operator [](ssize_t i)
{
	return d->Items.ItemAt(i);
}

bool LCombo::Name(const char *n)
{
	if (ValidStr(n))
	{
		int Index = 0;
		for (auto i: d->Items)
		{
			if (stricmp(n, i) == 0)
			{
				Value(Index);
				return true;
			}
			Index++;
		}

		char *New = NewStr(n);
		if (New)
		{
			d->Items.Insert(New);
			d->Current = d->Items.Length() - 1;
			d->SetText(NULL, _FL);
			Invalidate();
		}
		else return false;
	}
	else
	{
		d->Current = 0;
		d->SetText(NULL, _FL);
		Invalidate();
	}

	return true;
}

const char *LCombo::Name()
{
	return d->Items.ItemAt(d->Current);
}

void LCombo::Value(int64 i)
{
	if (d->Current != i)
	{
		d->Current = (int)i;
		d->SetText(NULL, _FL);
		Invalidate();

		SendNotify(LNotifyValueChanged);
	}
}

int64 LCombo::Value()
{
	return d->Current;
}

LMessage::Result LCombo::OnEvent(LMessage *Msg)
{
	return LView::OnEvent(Msg);
}

void LCombo::Empty()
{
	while (Delete((size_t)0))
		;
	Name(0);
}

bool LCombo::Delete()
{
	return Delete(d->Current);
}

bool LCombo::Delete(size_t i)
{
	char *c = d->Items.ItemAt(i);
	if (c)
	{
		d->Items.Delete(c);
		DeleteArray(c);
		return true;
	}
	return false;
}

bool LCombo::Delete(char *p)
{
	if (p && d->Items.HasItem(p))
	{
		d->Items.Delete(p);
		DeleteArray(p);
		d->LayoutDirty = true;
		return true;
	}
	return false;
}

bool LCombo::Insert(const char *p, int Index)
{
	if (!p)
		return false;

	if (!d->Items.Insert(NewStr(p), Index))
		return false;
	
	d->LayoutDirty = true;
	Invalidate();
	return true;
}

void LCombo::DoMenu()
{
	LPoint p(0, Y());
	PointToScreen(p);

	if (d->Menu)
	{
		int Result = d->Menu->Float(this, p.x, p.y, LSubMenu::BtnLeft);
		if (Result)
		{
			GetWindow()->OnCommand(Result, 0,
				#if !LGI_VIEW_HANDLE
				NULL
				#else
				Handle()
				#endif
				);
		}
	}
	else
	{
		LSubMenu RClick;
		int Base = 1000;
		int i=0;

		if (d->Sub)
		{
			if (d->Sub == GV_INT32)
			{
				d->Items.Sort(IntCompare);
			}
			else if (d->Sub == GV_DOUBLE)
			{
				d->Items.Sort(DblCompare);
			}
			else
			{
				d->Items.Sort(StringCompare);
			}
		}
		else if (d->SortItems)
		{
			d->Items.Sort(StringCompare);
		}

		auto It = d->Items.begin();
		char *c = *It;
		if (c)
		{
			if (d->Sub)
			{
				int n = 0;
				double dbl = 0;
				char16 f = 0;
				LSubMenu *m = 0;
				for (; c; c = *(++It), i++)
				{
					LUtf8Ptr u(c);
					if (d->Sub == GV_INT32)
					{
						int ci = atoi(c);
						if (!m || n != ci)
						{
							char Name[16];
							sprintf_s(Name, sizeof(Name), "%i...", n = ci);
							m = RClick.AppendSub(Name);
						}
					}
					else if (d->Sub == GV_DOUBLE)
					{
						double cd = atof(c);
						if (!m || dbl != cd)
						{
							char Name[16];
							sprintf_s(Name, sizeof(Name), "%f", dbl = cd);
							char *e = Name+strlen(Name)-1;
							while (e > Name && e[0] == '0') *e-- = 0;
							strcat(Name, "...");
							m = RClick.AppendSub(Name);
						}
					}
					else
					{
						if (!m || f != u)
						{
							char16 Name[16], *n = Name;
							*n++ = (f = u);
							*n++ = '.';
							*n++ = '.';
							*n++ = '.';
							*n++ = 0;
							LAutoString a(WideToUtf8(Name));
							m = RClick.AppendSub(a);
						}
					}

					switch (d->SelState)
					{
						default:
							m->AppendItem(c, Base+i, i != d->Current);
							break;
						case SelectedHide:
							if (i != d->Current)
								m->AppendItem(c, Base+i, true);
							break;
						case SelectedShow:
							m->AppendItem(c, Base+i, true);
							break;
					}
				}
			}
			else
			{
				for (; c; c = *(++It), i++)
				{
					switch (d->SelState)
					{
						default:
							RClick.AppendItem(c, Base+i, i != d->Current);
							break;
						case SelectedHide:
							if (i != d->Current)
								RClick.AppendItem(c, Base+i, true);
							break;
						case SelectedShow:
							RClick.AppendItem(c, Base+i, true);
							break;
					}
				}
			}
		}
		else
		{
			RClick.AppendItem("", Base+i, false);
		}

		int Result = RClick.Float(this, p.x, p.y, LSubMenu::BtnLeft);
		if (Result >= Base)
		{
			d->Current = Result - Base;
			d->SetText(NULL, _FL);
			Invalidate();

			SendNotify(LNotifyValueChanged);
		}
	}
}

void LCombo::OnMouseClick(LMouse &m)
{
	if (m.Down() &&
		Enabled())
	{
		Focus(true);

		if (m.Left())
		{
			DoMenu();
		}
	}
}

bool LCombo::OnKey(LKey &k)
{
	bool Status = false;

	if (k.Down())
	{
		switch (k.vkey)
		{
			case LK_SPACE:
			{
				// Open the submenu
				Status = true;
				DoMenu();
				break;
			}
			case LK_UP:
			{
				// Previous value in the list
				int64 i = Value();
				if (i > 0)
				{
					Value(i-1);
				}
				Status = true;
				break;
			}
			case LK_DOWN:
			{
				// Next value in the list...
				int i = (int)Value();
				if (i < d->Items.Length()-1)
				{
					Value(i+1);
				}
				Status = true;
				break;
			}
			default:
			{
				// Search for value
				if (k.IsChar && k.c16 > ' ')
				{
					uint64 Now = LCurrentTime();
					if (d->LastKey + 2000 < Now)
					{
						d->Find.Reset();
					}

					size_t Len = d->Find ? strlen(d->Find) : 0;
					LAutoString n(new char[Len+2]);
					if (n)
					{
						if (d->Find) strcpy(n, d->Find);
						n[Len++] = k.c16;
						n[Len] = 0;
						d->Find = n;
					}

					if (Len > 0 && d->Find)
					{
						int Index = 0;
						for (auto i: d->Items)
						{
							if (strnicmp(i, d->Find, Len) == 0)
							{
								Value(Index);
								break;
							}
							Index++;
						}
					}
					d->LastKey = Now;
					Status = true;
				}
				break;
			}
		}
	}

	return Status;
}

void LCombo::OnFocus(bool f)
{
	Invalidate();
}

void LCombo::SetFont(LFont *Fnt, bool OwnIt)
{
	LView::SetFont(Fnt, OwnIt);
	d->SetText(new LDisplayString(GetFont(), Name()), _FL);
	Invalidate();
}

void LCombo::OnPosChange()
{
	LDisplayString *ds = d->GetText(_FL);
	if (ds && ds->IsTruncated())
		d->SetText(NULL, _FL);
}

void LCombo::OnPaint(LSurface *pDC)
{
	if (d->LayoutDirty)
	{
		d->LayoutDirty = false;
		SendNotify(LNotifyTableLayoutRefresh);
	}
	
	if (!d->GetText(_FL))
	{
		auto n = Name();
		if (n)
			d->SetText(new LDisplayString(GetFont(), n), _FL);
	}

	LColour cBack = StyleColour(LCss::PropBackgroundColor, LColour(L_MED));

	#if defined LGI_CARBON

		pDC->Colour(cBack);
		pDC->Rectangle();

		LRect Cli = GetClient();
		LRect c = Cli;
	
		c.Size(1, 1);
		c.y2 -= 1;
		HIRect Bounds = c;
		HIThemeButtonDrawInfo Info;
		HIRect LabelRect;

		Info.version = 0;
		Info.state = Enabled() ? kThemeStateActive : kThemeStateInactive;
		Info.kind = kThemePopupButton;
		Info.value = Focus() ? kThemeButtonOn : kThemeButtonOff;
		Info.adornment = Focus() ? kThemeAdornmentFocus : kThemeAdornmentNone;

		OSStatus e = HIThemeDrawButton(	  &Bounds,
										  &Info,
										  pDC->Handle(),
										  kHIThemeOrientationNormal,
										  &LabelRect);

		if (e) printf("%s:%i - HIThemeDrawButton failed %li\n", _FL, e);
		else if (d->GetText(_FL))
		{
			LRect Txt;
			Txt = LabelRect;
			LDisplayString *Ds = d->GetText(_FL);
			int y = Cli.y1 + ((Cli.Y() - Ds->Y()) / 2) + 1;

			auto f = Ds->GetFont();
			f->Transparent(true);
			f->Fore(L_TEXT);
			Ds->Draw(pDC, Txt.x1, y);
			
			#if 0
			pDC->Colour(LColour(255, 0, 0));
			pDC->Box(Txt.x1, y, Txt.x1 + Ds->X()-1, y + Ds->Y() - 1);
			#endif
		}
	
	#else
	
	if (LApp::SkinEngine &&
		TestFlag(LApp::SkinEngine->GetFeatures(), GSKIN_COMBO))
	{
		LSkinState State;
		State.pScreen = pDC;
		State.ptrText = d->GetTextPtr();
		State.Enabled = Enabled();
		LApp::SkinEngine->OnPaint_LCombo(this, &State);
	}
	else
	{
		LRect r(0, 0, X()-1, Y()-1);

		// draw shadowed border
		pDC->Colour(L_SHADOW);
		pDC->Line(r.x1+2, r.y2, r.x2, r.y2);
		pDC->Line(r.x2, r.y1+2, r.x2, r.y2);

		pDC->Colour(L_MED);
		pDC->Set(r.x1, r.y2);
		pDC->Set(r.x1+1, r.y2);
		pDC->Set(r.x2, r.y1);
		pDC->Set(r.x2, r.y1+1);

		r.x2--;
		r.y2--;
		pDC->Colour(L_LOW);
		pDC->Box(&r);
		r.Inset(1, 1);
		pDC->Colour(L_HIGH);
		pDC->Line(r.x1, r.y1, r.x2, r.y1);
		pDC->Line(r.x1, r.y1+1, r.x1, r.y2);
		r.x1++;
		r.y1++;

		// fill the background
		pDC->Colour(cBack);
		pDC->Rectangle(&r);

		// draw the drop down arrow
		d->Arrow = r;
		d->Arrow.x1 = d->Arrow.x2 - 10;
		pDC->Colour(Enabled() ? L_SHADOW : L_LOW);
		int Cx = r.x2 - 5;
		int Cy = r.y1 + (r.Y()/2) + 2;
		for (int i=0; i<4; i++)
		{
			pDC->Line(Cx-i, Cy, Cx+i, Cy);
			Cy--;
		}
		r.x2 = d->Arrow.x1 - 1;
		pDC->Colour(L_LOW);
		pDC->Line(r.x2, r.y1+1, r.x2, r.y2-1);
		r.x2--;

		// draw the text
		r.Inset(1, 1);
		if (r.Valid())
		{
			LDisplayString *ds = d->GetText(_FL);
			if (ds)
			{
				if (Enabled())
				{
					bool f = Focus();
					LSysFont->Colour(f ? L_FOCUS_SEL_FORE : L_TEXT, f ? L_FOCUS_SEL_BACK : L_MED);
					LSysFont->Transparent(false);
					ds->Draw(pDC, r.x1, r.y1, &r);
				}
				else
				{
					LSysFont->Transparent(false);
					LSysFont->Colour(LColour(L_LIGHT), cBack);
					ds->Draw(pDC, r.x1+1, r.y1+1, &r);

					LSysFont->Transparent(true);
					LSysFont->Colour(L_LOW);
					ds->Draw(pDC, r.x1, r.y1, &r);
				}
			}
			else
			{
				pDC->Colour(Focus() ? L_FOCUS_SEL_BACK : L_MED);
				pDC->Rectangle(&r);
			}
		}
	}
	#endif
}

void LCombo::OnAttach()
{
}

LCombo::SelectedState LCombo::GetSelectedState()
{
	return d->SelState;
}

void LCombo::SetSelectedState(SelectedState s)
{
	d->SelState = s;
}

