
/*hdr
**      FILE:           LView.cpp
**      AUTHOR:         Matthew Allen
**      DATE:           12/8/2015
**      DESCRIPTION:    SDL LView Implementation
**
**      Copyright (C) 2015, Matthew Allen
**              fret@memecode.com
*/

#include <stdio.h>
#include <time.h>


#include "Lgi.h"
#include "Base64.h"
#include "GDragAndDrop.h"
#include "LDropFiles.h"
#include "GViewPriv.h"
#include "LCss.h"

/// This hash contains all the LView* that exist.
/// It's used by LApp::OnSDLEvent to
/// check the validity of a LView pointer passed via
/// an asynchronous message.
LHashTbl<PtrKey<LView*>,bool> ViewMap(256);

////////////////////////////////////////////////////////////////////////////////////////////////////
LViewPrivate::LViewPrivate()
{
	Font = 0;
	FontOwnType = GV_FontPtr;
	CtrlId = -1;
	DropTarget = NULL;
	DropSource = NULL;
	Parent = 0;
	ParentI = 0;
	Notify = 0;
	IsThemed = true;
	PulseId = NULL;
	SinkHnd = -1;
}

LViewPrivate::~LViewPrivate()
{
	if (FontOwnType == GV_FontOwned)
	{
		DeleteObj(Font);
	}
}

////////////////////////////////////////////////////////////////////////////////////////////////////
// Helper Stuff

int MouseRollMsg = 0;


#define SetKeyFlag(v, k, f)		if (GetKeyState(k)&0xFF00) { v |= f; }


LKey::LKey(int v, int flags)
{
	const char *Cp = 0;

	vkey = v;
	c16 = 0;
}

////////////////////////////////////////////////////////////////////////////////////////////////////
LViewI *GWindowFromHandle(OsView Wnd)
{
	return 0;
}

//////////////////////////////////////////////////////////////////////////////
const char *LView::GetClass()
{
	return "LView";
}

void LView::_Delete()
{
	SetPulse();
	
	LViewI *c;
	#ifdef _DEBUG
	// Sanity check..
	// LArray<LViewI*> HasView;
	for (c = Children.First(); c; c = Children.Next())
	{
		// LAssert(!HasView.HasItem(c));
		// HasView.Add(c);
		LAssert(((LViewI*)c->GetParent()) == this || c->GetParent() == 0);
	}
	#endif

	// Delete myself out of my parent's list
	if (d->Parent)
	{
		d->Parent->OnChildrenChanged(this, false);
		d->Parent->DelView(this);
		d->Parent = 0;
		d->ParentI = 0;
	}

	// Delete all children
	while ((c = Children.First()))
	{
		// If it has no parent, remove the pointer from the child list
		if (c->GetParent() == 0)
			Children.Delete(c);

		// Delete the child view
		DeleteObj(c);
	}

	// Delete the OS representation of myself
	
	// NULL my handles and flags
	_View = 0;
	WndFlags = 0;

	// Remove static references to myself
	if (_Over == this) _Over = 0;
	if (_Capturing == this) _Capturing = 0;

	if (LAppInst && LAppInst->AppWnd == this)
	{
		LAppInst->AppWnd = 0;
	}

	LWindow *Wnd = GetWindow();
	if (Wnd)
	{
		Wnd->SetFocus(this, LWindow::ViewDelete);
	}

	// this should only exist in an ex-LWindow, due to the way
	// C++ deletes objects it needs to be here.
	DeleteObj(_Lock);
}

void LView::Quit(bool DontDelete)
{
	if (_View)
	{
		if (!DontDelete)
		{
			WndFlags |= GWF_QUIT_WND;
		}

		// DestroyWindow(_View);
	}
}

bool LView::IsAttached()
{
	#if LGI_SDL
	return d->Parent ? d->Parent->Children.HasItem(this) : false;
	#else
	return _View;
	#endif
}

bool LView::Attach(LViewI *p)
{
	bool Status = false;

	SetParent(p);
	LView *Parent = d->GetParent();
	if (Parent && !_Window)
		_Window = Parent->_Window;

	// Virtual window (no HWND)
	Status = true;

	if (Status && d->Parent)
	{
		if (!d->Parent->HasView(this))
		{
			d->Parent->AddView(this);
			OnAttach();
		}
		d->Parent->OnChildrenChanged(this, true);
	}

	return Status;
}

bool LView::Detach()
{
	bool Status = false;

	if (_Window)
	{
		LWindow *Wnd = dynamic_cast<LWindow*>(_Window);
		if (Wnd)
			Wnd->SetFocus(this, LWindow::ViewDelete);
		_Window = NULL;
	}
	if (d->Parent)
	{
		Visible(false);
		d->Parent->DelView(this);
		d->Parent->OnChildrenChanged(this, false);
		d->Parent = 0;
		d->ParentI = 0;
		Status = true;
		WndFlags &= ~GWF_FOCUS;

		if (_Capturing == this)
		{
			LAssert(!"Release capture");
			_Capturing = 0;
		}
		if (_View)
		{
			WndFlags &= ~GWF_QUIT_WND;
			// Status = DestroyWindow(_View);
			LAssert(Status);
		}
	}

	return Status;
}

LRect &LView::GetClient(bool InClientSpace)
{
	static LRect Client;

	Client = Pos;
	Client.Offset(-Client.x1, -Client.y1);

	return Client;
}

LCursor LView::GetCursor(int x, int y)
{
	return LCUR_Normal;
}

bool LgiToWindowsCursor(LCursor Cursor)
{
	return false;
}

void LView::PointToScreen(LPoint &p)
{
	for (LViewI *i = this; i; i = i->GetParent())
	{
		LRect pos = i->GetPos();
		// const char *cls = i->GetClass();
		p.x += pos.x1;
		p.y += pos.y1;
	}
}

void LView::PointToView(LPoint &p)
{
	for (LViewI *i = this; i; i = i->GetParent())
	{
		LRect pos = i->GetPos();
		// const char *cls = i->GetClass();
		p.x -= pos.x1;
		p.y -= pos.y1;
	}
}

bool LView::GetMouse(LMouse &m, bool ScreenCoords)
{
	Uint8 btn = SDL_GetMouseState(&m.x, &m.y);
	m.Left((SDL_BUTTON(SDL_BUTTON_LEFT) & btn) != 0);
	m.Middle((SDL_BUTTON(SDL_BUTTON_MIDDLE) & btn) != 0);
	m.Right((SDL_BUTTON(SDL_BUTTON_RIGHT) & btn) != 0);
	m.Down(m.Left() || m.Middle() || m.Right());
	if (m.Down() && !btn)
	{
		int asd=0;
	}
	return true;
}

bool LView::SetPos(LRect &p, bool Repaint)
{
	bool Status = true;
	LRect OldPos = Pos;

	if (Pos != p)
	{
		Pos = p;
		OnPosChange();
		
		if (Repaint)
		{
			Invalidate();
		}
	}

	return Status;
}

bool LView::Invalidate(LRect *r, bool Repaint, bool Frame)
{
	LRect Up;
	LViewI *p = this;

	if (r)
	{
		Up = *r;
	}
	else
	{
		if (!Pos.Valid())
			return false;
		Up.Set(0, 0, Pos.X()-1, Pos.Y()-1);
	}

	while (p && !dynamic_cast<LWindow*>(p))
	{
		LViewI *Par = p->GetParent();
		if (Par == p)
		{
			LAssert(!"Window can't be parent of itself.");
			break;
		}
		
		LView *VPar = Par ? Par->GetGView() : 0;
		LRect w = p->GetPos();
		LRect c = p->GetClient(false);
		
		if (Frame && p == this)
			Up.Offset(w.x1, w.y1);
		else				
			Up.Offset(w.x1 + c.x1, w.y1 + c.y1);
		
		p = Par;
	}

	return LAppInst->InvalidateRect(Up);
}

Uint32 SDL_PulseCallback(Uint32 interval, LView *v)
{
	SDL_Event e;
	e.type = SDL_USEREVENT;
	e.user.code = M_PULSE;
	e.user.data1 = v;
	e.user.data2 = NULL;
	SDL_PushEvent(&e);
	return v->d->PulseLength;
}

void LView::SetPulse(int Length)
{
	if (d->PulseId > 0)
	{
		SDL_RemoveTimer(d->PulseId);
		d->PulseId = 0;
	}
	
	if (Length > 0)
	{
		d->PulseLength = Length;
		d->PulseId = SDL_AddTimer(Length, (SDL_NewTimerCallback)SDL_PulseCallback, this);
	}
}

void LView::DrawThemeBorder(LSurface *pDC, LRect &r)
{
	LWideBorder(pDC, r, DefaultSunkenEdge);
}

bool IsKeyChar(LKey &k, int vk)
{
	if (k.Ctrl() || k.Alt() || k.System())
		return false;

	switch (vk)
	{
		case VK_TAB:
		case VK_RETURN:
		case VK_SPACE:

		case 0xba: // ;
		case 0xbb: // =
		case 0xbc: // ,
		case 0xbd: // -
		case 0xbe: // .
		case 0xbf: // /

		case 0xc0: // `

		case 0xdb: // [
		case 0xdc: // |
		case 0xdd: // ]
		case 0xde: // '
			return true;
	}

	if (vk >= '0' && vk <= '9')
		return true;

	if (vk >= 'A' && vk <= 'Z')
		return true;

	return false;
}

#define KEY_FLAGS		(~(MK_LBUTTON | MK_MBUTTON | MK_RBUTTON))

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

LViewI *LView::FindControl(OsView hCtrl)
{
	if (_View == hCtrl)
	{
		return this;
	}

	for (auto i : Children)
	{
		LViewI *Ctrl = i->FindControl(hCtrl);
		if (Ctrl)
		{
			return Ctrl;
		}
	}
	return 0;
}

LView *&LView::PopupChild()
{
	return d->Popup;
}

bool LView::_Mouse(LMouse &m, bool Move)
{
	LWindow *Wnd = GetWindow();
	if (Wnd && !Wnd->HandleViewMouse(this, m))
		return false;

	LViewI *o = WindowFromPoint(m.x, m.y);
	if (_Over != o)
	{
		if (_Over)
		{
			// LgiTrace("From Over %s\n", _Over->GetClass());
			_Over->OnMouseExit(lgi_adjust_click(m, _Over));
		}
		_Over = o;
		if (_Over)
		{
			_Over->OnMouseEnter(lgi_adjust_click(m, _Over));
			// LgiTrace("To Over %s\n", _Over->GetClass());
		}
	}

	LViewI *cap = _Capturing;
	if (_Capturing)
	{
		if (Move)
		{
			LMouse Local = lgi_adjust_click(m, _Capturing);
			_Capturing->OnMouseMove(Local); // This can set _Capturing to NULL
		}
		else
		{
			_Capturing->OnMouseClick(lgi_adjust_click(m, _Capturing));
		}
	}
	else
	{
		LView *Target = dynamic_cast<LView*>(_Over ? _Over : this);

		LRect Client = Target->LView::GetClient(false);
		
		m = lgi_adjust_click(m, Target);
		if (!Client.Valid() || Client.Overlap(m.x, m.y))
		{
			if (Move)
			{
				Target->OnMouseMove(m);
			}
			else
			{
				#if 0
				char Msg[256];
				sprintf_s(Msg, sizeof(Msg), "%s::OnMouseClick", Target->GetClass());
				m.Trace(Msg);
				#endif
				
				Target->OnMouseClick(m);
			}
		}
		else if (!Move)
		{
			printf("%s:%i - Click outside %s %s %i,%i\n", _FL, Target->GetClass(), Client.GetStr(), m.x, m.y);
		}
	}
	
	return true;
}

void LView::_Focus(bool f)
{
}
