// Copyright 2016 The G3N Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package gui

import (
	"github.com/g3n/engine/gui/assets"
	"github.com/g3n/engine/math32"
	"github.com/g3n/engine/window"
)

type DropDown struct {
	Panel                        // Embedded panel
	icon         *Label          // internal label with icon
	list         *List           // internal list
	styles       *DropDownStyles // pointer to dropdown styles
	litem        *ImageLabel     // Item shown in drop box (copy of selected)
	selItem      *ImageLabel     // selected item from list
	overDropdown bool
	overList     bool
	focus        bool
	clickOut     bool
}

// DropDown list style
type DropDownStyle struct {
	Border      BorderSizes
	Paddings    BorderSizes
	BorderColor math32.Color4
	BgColor     math32.Color
	FgColor     math32.Color
}

// DropDown list styles
type DropDownStyles struct {
	Normal   *DropDownStyle
	Over     *DropDownStyle
	Focus    *DropDownStyle
	Disabled *DropDownStyle
}

// NewDropDown creates and returns a pointer to a new drop down widget with the specified width.
func NewDropDown(width float32, item *ImageLabel) *DropDown {

	dd := new(DropDown)
	dd.styles = &StyleDefault.DropDown
	dd.litem = item

	dd.Panel.Initialize(width, 0)
	dd.Panel.Subscribe(OnKeyDown, dd.onKeyEvent)
	dd.Panel.Subscribe(OnMouseDown, dd.onMouse)
	dd.Panel.Subscribe(OnCursorEnter, dd.onCursor)
	dd.Panel.Subscribe(OnCursorLeave, dd.onCursor)

	// ListItem
	dd.Panel.Add(dd.litem)

	// Create icon
	dd.icon = NewIconLabel(" ")
	dd.icon.SetFontSize(StyleDefault.Font.Size() * 1.3)
	dd.icon.SetText(string(assets.ArrowDropDown))
	dd.Panel.Add(dd.icon)

	/// Create list
	dd.list = NewVList(0, 0)
	dd.list.bounded = false
	dd.list.dropdown = true
	dd.list.SetVisible(false)

	dd.list.Subscribe(OnMouseDown, dd.onListMouse)
	dd.list.Subscribe(OnMouseOut, dd.onListMouse)
	dd.list.Subscribe(OnChange, dd.onListChangeEvent)
	dd.Panel.Add(dd.list)

	dd.update()
	dd.recalc()
	return dd
}

// Add add a list item at the end of the list
func (dd *DropDown) Add(item *ImageLabel) {

	dd.list.Add(item)
}

// InsertAt inserts a list item at the specified position
// Returs true if the item was successfuly inserted
func (dd *DropDown) InsertAt(pos int, item *ImageLabel) {

	dd.list.InsertAt(pos, item)
}

// RemoveAt removes the list item from the specified position
// Returs true if the item was successfuly removed
func (dd *DropDown) RemoveAt(pos int) {

	dd.list.RemoveAt(pos)
}

// ItemAt returns the list item at the specified position
func (dd *DropDown) ItemAt(pos int) *ImageLabel {

	return dd.list.ItemAt(pos).(*ImageLabel)
}

// Returns the currently selected item or nil if not item
// was selected
func (dd *DropDown) Selected() *ImageLabel {

	return dd.selItem
}

// SetSelected sets the selected item
func (dd *DropDown) SetSelected(item *ImageLabel) {

	dd.list.SetSelected(item, true)
}

// SelectPos selects the item at the specified position
func (dd *DropDown) SelectPos(pos int) {

	dd.list.SelectPos(pos, true)
}

// onKeyEvent is called when key event is received when this dropdown has the key focus.
func (dd *DropDown) onKeyEvent(evname string, ev interface{}) {

	kev := ev.(*window.KeyEvent)
	switch kev.Keycode {
	case window.KeyF1:
		if dd.list.Visible() {
			dd.list.SetVisible(false)
		}
	default:
		return
	}
}

// onMouse receives subscribed mouse events over the dropdown
func (dd *DropDown) onMouse(evname string, ev interface{}) {

	if evname == OnMouseDown {
		// If clickOut list already closed
		if dd.clickOut {
			dd.clickOut = false
			return
		}
		dd.list.SetVisible(true)
		dd.root.SetKeyFocus(dd.list)
		return
	}
}

// onCursor receives subscribed cursor events over the dropdown
func (dd *DropDown) onCursor(evname string, ev interface{}) {

	if evname == OnCursorEnter {
		dd.overDropdown = true
		dd.update()
		return
	}
	if evname == OnCursorLeave {
		dd.overDropdown = false
		dd.update()
		return
	}
}

// onListMouseEvent receives mouse events over the list
func (dd *DropDown) onListMouse(evname string, ev interface{}) {

	mev := ev.(*window.MouseEvent)
	// List was clicked
	if evname == OnMouseDown {
		// If click occurred inside the list scrollbar ignore it
		if dd.list.vscroll != nil {
			if dd.list.vscroll.ContainsPosition(mev.Xpos, mev.Ypos) {
				return
			}
		}
		// Otherwise, closes the list
		dd.list.SetVisible(false)
		//dd.copySelected()
		dd.overList = false
		dd.update()
		return
	}
	// Hide list when clicked out
	if evname == OnMouseOut {
		if dd.list.Visible() {
			dd.list.SetVisible(false)
		}
		// If list clickout occurred inside the dropdown, set 'clickOut' to
		// indicate that the list was already closed
		if dd.Panel.ContainsPosition(mev.Xpos, mev.Ypos) {
			dd.clickOut = true
		}
	}
}

// onListCursor receives subscribed events over the list
func (dd *DropDown) onListCursor(evname string, ev interface{}) {

	if evname == OnCursorEnter {
		dd.overList = true
		dd.update()
		return
	}
	if evname == OnCursorLeave {
		dd.overList = false
		dd.update()
		return
	}

}

// copySelected copy to the dropdown panel the selected item
// from the list.
func (dd *DropDown) copySelected() {

	dd.selItem = dd.list.Selected()[0].(*ImageLabel)
	dd.litem.CopyFields(dd.selItem)
	dd.recalc()
	dd.Dispatch(OnChange, nil)
}

// onListChangeEvent is called when an item in the list is selected
func (dd *DropDown) onListChangeEvent(evname string, ev interface{}) {

	dd.copySelected()
}

// recalc recalculates the dimensions and positions of the dropdown
// panel, children and list
func (dd *DropDown) recalc() {

	// Dropdown icon position
	posx := dd.Panel.ContentWidth() - dd.icon.Width()
	dd.icon.SetPosition(posx, 0)

	// List item position and width
	ipan := dd.litem.GetPanel()
	ipan.SetPosition(0, 0)
	//ipan.SetWidth(posx)
	height := ipan.Height()
	dd.Panel.SetContentHeight(height)

	// List position
	dd.list.SetWidth(dd.Panel.Width())
	dd.list.SetHeight(6*height + 1)
	dd.list.SetPositionX(0)
	dd.list.SetPositionY(dd.Panel.Height())
}

// update updates the visual state
func (dd *DropDown) update() {

	if dd.overDropdown || dd.overList {
		dd.applyStyle(dd.styles.Over)
		dd.list.ApplyStyle(OverStyle)
		return
	}
	if dd.focus {
		dd.applyStyle(dd.styles.Focus)
		dd.list.ApplyStyle(FocusStyle)
		return
	}
	dd.applyStyle(dd.styles.Normal)
	dd.list.ApplyStyle(NormalStyle)
}

// applyStyle applies the specified style
func (dd *DropDown) applyStyle(s *DropDownStyle) {

	dd.SetBordersFrom(&s.Border)
	dd.SetBordersColor4(&s.BorderColor)
	dd.SetPaddingsFrom(&s.Paddings)
	dd.SetColor(&s.BgColor)
}
