// 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/math32"
	"github.com/g3n/engine/window"
)

/*********************************************

 Window panel
 +-----------------------------------------+
 | Title panel                             |
 +-----------------------------------------+
 |  Content panel                          |
 |  +-----------------------------------+  |
 |  |                                   |  |
 |  |                                   |  |
 |  |                                   |  |
 |  |                                   |  |
 |  |                                   |  |
 |  |                                   |  |
 |  +-----------------------------------+  |
 |                                         |
 +-----------------------------------------+

*********************************************/

type Window struct {
	Panel      // Embedded Panel
	styles     *WindowStyles
	title      *WindowTitle // internal optional title panel
	client     Panel        // internal client panel
	resizable  Resizable
	overBorder string
	drag       bool
	mouseX     float32
	mouseY     float32
}

type WindowStyle struct {
	Border           BorderSizes
	Paddings         BorderSizes
	BorderColor      math32.Color4
	TitleBorders     BorderSizes
	TitleBorderColor math32.Color4
	TitleBgColor     math32.Color4
	TitleFgColor     math32.Color4
}

// All Window styles
type WindowStyles struct {
	Normal   WindowStyle
	Over     WindowStyle
	Focus    WindowStyle
	Disabled WindowStyle
}

type Resizable int

const (
	ResizeTop = Resizable(1 << (iota + 1))
	ResizeRight
	ResizeBottom
	ResizeLeft
	ResizeAll = ResizeTop | ResizeRight | ResizeBottom | ResizeLeft
)

// NewWindow creates and returns a pointer to a new window with the
// specified dimensions
func NewWindow(width, height float32) *Window {

	w := new(Window)
	w.styles = &StyleDefault.Window

	w.Panel.Initialize(width, height)
	w.Panel.Subscribe(OnMouseDown, w.onMouse)
	w.Panel.Subscribe(OnMouseUp, w.onMouse)
	w.Panel.Subscribe(OnCursor, w.onCursor)
	w.Panel.Subscribe(OnCursorEnter, w.onCursor)
	w.Panel.Subscribe(OnCursorLeave, w.onCursor)
	w.Panel.Subscribe(OnResize, func(evname string, ev interface{}) { w.recalc() })

	w.client.Initialize(0, 0)
	w.Panel.Add(&w.client)

	w.recalc()
	w.update()
	return w
}

// SetResizable set the borders which are resizable
func (w *Window) SetResizable(res Resizable) {

	w.resizable = res
}

// SetTitle sets the title of this window
func (w *Window) SetTitle(text string) {

	if w.title == nil {
		w.title = newWindowTitle(w, text)
		w.Panel.Add(w.title)
	} else {
		w.title.label.SetText(text)
	}
	w.update()
	w.recalc()
}

// Add adds a child panel to the client area of this window
func (w *Window) Add(ichild IPanel) *Window {

	w.client.Add(ichild)
	return w
}

// SetLayout set the layout of this window content area
func (w *Window) SetLayout(layout ILayout) *Window {

	w.client.SetLayout(layout)
	return w
}

// onMouse process subscribed mouse events over the window
func (w *Window) onMouse(evname string, ev interface{}) {

	mev := ev.(*window.MouseEvent)
	switch evname {
	case OnMouseDown:
		w.SetForeground()
		if w.overBorder != "" {
			w.drag = true
			w.mouseX = mev.Xpos
			w.mouseY = mev.Ypos
			w.root.SetMouseFocus(w)
		}
	case OnMouseUp:
		w.drag = false
		w.root.SetCursorNormal()
		w.root.SetMouseFocus(nil)
	default:
		return
	}
	w.root.StopPropagation(StopAll)
}

// onCursor process subscribed cursor events over the window
func (w *Window) onCursor(evname string, ev interface{}) {

	cev := ev.(*window.CursorEvent)
	switch evname {
	case OnCursor:
		if !w.drag {
			cx := cev.Xpos - w.pospix.X
			cy := cev.Ypos - w.pospix.Y
			if cy <= w.borderSizes.Top {
				if w.resizable&ResizeTop != 0 {
					w.overBorder = "top"
					w.root.SetCursorVResize()
				}
			} else if cy >= w.height-w.borderSizes.Bottom {
				if w.resizable&ResizeBottom != 0 {
					w.overBorder = "bottom"
					w.root.SetCursorVResize()
				}
			} else if cx <= w.borderSizes.Left {
				if w.resizable&ResizeLeft != 0 {
					w.overBorder = "left"
					w.root.SetCursorHResize()
				}
			} else if cx >= w.width-w.borderSizes.Right {
				if w.resizable&ResizeRight != 0 {
					w.overBorder = "right"
					w.root.SetCursorHResize()
				}
			} else {
				if w.overBorder != "" {
					w.root.SetCursorNormal()
					w.overBorder = ""
				}
			}
		} else {
			switch w.overBorder {
			case "top":
				delta := cev.Ypos - w.mouseY
				w.mouseY = cev.Ypos
				newHeight := w.Height() - delta
				if newHeight < w.MinHeight() {
					return
				}
				w.SetPositionY(w.Position().Y + delta)
				w.SetHeight(newHeight)
			case "right":
				delta := cev.Xpos - w.mouseX
				w.mouseX = cev.Xpos
				newWidth := w.Width() + delta
				w.SetWidth(newWidth)
			case "bottom":
				delta := cev.Ypos - w.mouseY
				w.mouseY = cev.Ypos
				newHeight := w.Height() + delta
				w.SetHeight(newHeight)
			case "left":
				delta := cev.Xpos - w.mouseX
				w.mouseX = cev.Xpos
				newWidth := w.Width() - delta
				if newWidth < w.MinWidth() {
					return
				}
				w.SetPositionX(w.Position().X + delta)
				w.SetWidth(newWidth)
			}
		}
	case OnCursorLeave:
		if !w.drag {
			w.root.SetCursorNormal()
		}
	default:
		return
	}
	w.root.StopPropagation(StopAll)
}

// update updates the button visual state
func (w *Window) update() {

	if !w.Enabled() {
		w.applyStyle(&w.styles.Disabled)
		return
	}
	w.applyStyle(&w.styles.Normal)
}

func (w *Window) applyStyle(s *WindowStyle) {

	w.SetBordersColor4(&s.BorderColor)
	w.SetBordersFrom(&s.Border)
	w.SetPaddingsFrom(&s.Paddings)
	if w.title != nil {
		w.title.applyStyle(s)
	}
}

// recalc recalculates the sizes and positions of the internal panels
// from the outside to the inside.
func (w *Window) recalc() {

	// Window title
	height := w.content.Height
	width := w.content.Width
	cx := float32(0)
	cy := float32(0)
	if w.title != nil {
		w.title.SetWidth(w.content.Width)
		w.title.recalc()
		height -= w.title.height
		cy = w.title.height
	}

	// Content area
	w.client.SetPosition(cx, cy)
	w.client.SetSize(width, height)
}

type WindowTitle struct {
	Panel   // Embedded panel
	win     *Window
	label   Label
	pressed bool
	drag    bool
	mouseX  float32
	mouseY  float32
}

// newWindowTitle creates and returns a pointer to a window title panel
func newWindowTitle(win *Window, text string) *WindowTitle {

	wt := new(WindowTitle)
	wt.win = win

	wt.Panel.Initialize(0, 0)
	wt.label.initialize(text, StyleDefault.Font)
	wt.Panel.Add(&wt.label)

	wt.Subscribe(OnMouseDown, wt.onMouse)
	wt.Subscribe(OnMouseUp, wt.onMouse)
	wt.Subscribe(OnCursor, wt.onCursor)
	wt.Subscribe(OnCursorEnter, wt.onCursor)
	wt.Subscribe(OnCursorLeave, wt.onCursor)

	wt.recalc()
	return wt
}

// onMouse process subscribed mouse button events over the window title
func (wt *WindowTitle) onMouse(evname string, ev interface{}) {

	mev := ev.(*window.MouseEvent)
	switch evname {
	case OnMouseDown:
		wt.pressed = true
		wt.mouseX = mev.Xpos
		wt.mouseY = mev.Ypos
		wt.win.root.SetMouseFocus(wt)
	case OnMouseUp:
		wt.pressed = false
		wt.win.root.SetMouseFocus(nil)
	default:
		return
	}
	wt.win.root.StopPropagation(Stop3D)
}

// onCursor process subscribed cursor events over the window title
func (wt *WindowTitle) onCursor(evname string, ev interface{}) {

	cev := ev.(*window.CursorEvent)
	switch evname {
	case OnCursorEnter:
		wt.win.root.SetCursorDrag()
	case OnCursorLeave:
		wt.win.root.SetCursorNormal()
	case OnCursor:
		if !wt.pressed {
			wt.win.root.StopPropagation(Stop3D)
			return
		}
		dy := wt.mouseY - cev.Ypos
		dx := wt.mouseX - cev.Xpos
		wt.mouseX = cev.Xpos
		wt.mouseY = cev.Ypos
		posX := wt.win.Position().X - dx
		posY := wt.win.Position().Y - dy
		wt.win.SetPosition(posX, posY)
	default:
		return
	}
	wt.win.root.StopPropagation(Stop3D)
}

// applyStyles sets the specified window title style
func (wt *WindowTitle) applyStyle(s *WindowStyle) {

	wt.SetBordersFrom(&s.TitleBorders)
	wt.SetBordersColor4(&s.TitleBorderColor)
	wt.SetColor4(&s.TitleBgColor)
	wt.label.SetColor4(&s.TitleFgColor)
}

// recalc recalculates the height and position of the label in the title bar.
func (wt *WindowTitle) recalc() {

	xpos := (wt.width - wt.label.width) / 2
	wt.label.SetPositionX(xpos)
	wt.SetContentHeight(wt.label.Height())
}
