// 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"
)

type Splitter struct {
	Panel                     // Embedded panel
	P0        Panel           // Left/Top panel
	P1        Panel           // Right/Bottom panel
	styles    *SplitterStyles // pointer to current styles
	spacer    Panel           // spacer panel
	horiz     bool            // horizontal or vertical splitter
	pos       float32         // central position of the spacer panel bar in pixels
	posLast   float32         // last position of the mouse cursor when dragging
	pressed   bool            // mouse button is pressed and dragging
	mouseOver bool            // mouse is over the spacer panel
}

type SplitterStyle struct {
	SpacerBorderColor math32.Color4
	SpacerColor       math32.Color
	SpacerSize        float32
}

type SplitterStyles struct {
	Normal SplitterStyle
	Over   SplitterStyle
	Drag   SplitterStyle
}

// NewHSplitter creates and returns a pointer to a new horizontal splitter
// widget with the specified initial dimensions
func NewHSplitter(width, height float32) *Splitter {

	return newSplitter(true, width, height)
}

// NewVSplitter creates and returns a pointer to a new vertical splitter
// widget with the specified initial dimensions
func NewVSplitter(width, height float32) *Splitter {

	return newSplitter(false, width, height)
}

// newSpliter creates and returns a pointer of a new splitter with
// the specified orientation and initial dimensions.
func newSplitter(horiz bool, width, height float32) *Splitter {

	s := new(Splitter)
	s.horiz = horiz
	s.styles = &StyleDefault.Splitter
	s.Panel.Initialize(width, height)

	// Initialize left/top panel
	s.P0.Initialize(0, 0)
	s.Panel.Add(&s.P0)

	// Initialize right/bottom panel
	s.P1.Initialize(0, 0)
	s.Panel.Add(&s.P1)

	// Initialize spacer panel
	s.spacer.Initialize(0, 0)
	s.Panel.Add(&s.spacer)

	if horiz {
		s.spacer.SetBorders(0, 1, 0, 1)
		s.spacer.SetWidth(6)
		s.pos = width / 2
	} else {
		s.spacer.SetBorders(1, 0, 1, 0)
		s.spacer.SetHeight(6)
		s.pos = height / 2
	}

	s.spacer.Subscribe(OnMouseDown, s.onMouse)
	s.spacer.Subscribe(OnMouseUp, s.onMouse)
	s.spacer.Subscribe(OnCursor, s.onCursor)
	s.spacer.Subscribe(OnCursorEnter, s.onCursor)
	s.spacer.Subscribe(OnCursorLeave, s.onCursor)
	s.update()
	s.recalc()
	return s
}

// SetSplit sets the position of the splitter bar.
// It accepts a value from 0.0 to 1.0
func (s *Splitter) SetSplit(pos float32) {

	if s.horiz {
		s.setSplit(pos * s.Width())
	} else {
		s.setSplit(pos * s.Height())
	}
	s.recalc()
}

// Split returns the current position of the splitter bar.
// It returns a value from 0.0 to 1.0
func (s *Splitter) Split() float32 {

	var pos float32
	if s.horiz {
		pos = s.pos / s.Width()
	} else {
		pos = s.pos / s.Height()
	}
	return pos
}

// onMouse receives subscribed mouse events over the spacer panel
func (s *Splitter) onMouse(evname string, ev interface{}) {

	mev := ev.(*window.MouseEvent)
	switch evname {
	case OnMouseDown:
		s.pressed = true
		if s.horiz {
			s.posLast = mev.Xpos
		} else {
			s.posLast = mev.Ypos
		}
		s.root.SetMouseFocus(&s.spacer)
	case OnMouseUp:
		s.pressed = false
		s.root.SetCursorNormal()
		s.root.SetMouseFocus(nil)
	default:
	}
	s.root.StopPropagation(Stop3D)
}

// onCursor receives subscribed cursor events over the spacer panel
func (s *Splitter) onCursor(evname string, ev interface{}) {

	cev := ev.(*window.CursorEvent)
	switch evname {
	case OnCursorEnter:
		if s.horiz {
			s.root.SetCursorHResize()
		} else {
			s.root.SetCursorVResize()
		}
		s.mouseOver = true
		s.update()
	case OnCursorLeave:
		s.root.SetCursorNormal()
		s.mouseOver = false
		s.update()
	case OnCursor:
		if !s.pressed {
			return
		}
		var delta float32
		if s.horiz {
			delta = cev.Xpos - s.posLast
			s.posLast = cev.Xpos
		} else {
			delta = cev.Ypos - s.posLast
			s.posLast = cev.Ypos
		}
		s.setSplit(s.pos + delta)
		s.recalc()
	default:
		return
	}
	s.root.StopPropagation(Stop3D)
}

// setSplit sets the validated and clamped split position from the received value.
func (s *Splitter) setSplit(pos float32) {

	var max float32
	var halfspace float32
	if s.horiz {
		halfspace = s.spacer.Width() / 2
		max = s.Width()
	} else {
		halfspace = s.spacer.Height() / 2
		max = s.Height()
	}

	if pos > max-halfspace {
		s.pos = max - halfspace
	} else if pos <= halfspace {
		s.pos = halfspace
	} else {
		s.pos = pos
	}
}

// update updates the splitter visual state
func (s *Splitter) update() {

	if s.pressed {
		s.applyStyle(&s.styles.Drag)
		return
	}
	if s.mouseOver {
		s.applyStyle(&s.styles.Over)
		return
	}
	s.applyStyle(&s.styles.Normal)
}

// applyStyle applies the specified splitter style
func (s *Splitter) applyStyle(ss *SplitterStyle) {

	s.spacer.SetBordersColor4(&ss.SpacerBorderColor)
	s.spacer.SetColor(&ss.SpacerColor)
	if s.horiz {
		s.spacer.SetWidth(ss.SpacerSize)
	} else {
		s.spacer.SetHeight(ss.SpacerSize)
	}
}

// recalc relcalculates the position and sizes of the internal panels
func (s *Splitter) recalc() {

	width := s.Width()
	height := s.Height()
	if s.horiz {
		halfspace := s.spacer.Width() / 2
		// First panel
		s.P0.SetPosition(0, 0)
		s.P0.SetSize(s.pos-halfspace, height)
		// Spacer panel
		s.spacer.SetPosition(s.pos-halfspace, 0)
		s.spacer.SetHeight(height)
		// Second panel
		s.P1.SetPosition(s.pos+halfspace, 0)
		s.P1.SetSize(width-s.pos-halfspace, height)
	} else {
		halfspace := s.spacer.Height() / 2
		// First panel
		s.P0.SetPosition(0, 0)
		s.P0.SetSize(width, s.pos-halfspace)
		// Spacer panel
		s.spacer.SetPosition(0, s.pos-halfspace)
		s.spacer.SetWidth(width)
		// Second panel
		s.P1.SetPosition(0, s.pos+halfspace)
		s.P1.SetSize(width, height-s.pos-halfspace)
	}
}
