// Copyright © 2018 Intel Corporation
//
// SPDX-License-Identifier: GPL-3.0-only

package tui

import (
	"sync/atomic"
	"time"

	"github.com/VladimirMarkelov/clui"
	xs "github.com/huandu/xstrings"
	term "github.com/nsf/termbox-go"
)

// SimpleButton is the implementation of a clui button with simpler
// visual elements (i.e no shadows).
type SimpleButton struct {
	clui.BaseControl
	autoWidth, autoHeight bool
	pressed               int32
	onClick               func(clui.Event)
	forceActiveStyle      bool
}

// CreateSimpleButton returns an instance of SimpleButton
// parent - is the parent control this button is attached to
// width - the minimum width size
// height - the minimum height size
// title - the button's label
// scale - the amount of space to use whenever used in an adjustable layout
func CreateSimpleButton(parent clui.Control, width, height int, title string, scale int) *SimpleButton {
	b := new(SimpleButton)
	b.BaseControl = clui.NewBaseControl()

	b.SetParent(parent)
	b.SetAlign(clui.AlignCenter)

	if height == clui.AutoSize {
		b.autoHeight = true
		height = 1
	}

	if width == clui.AutoSize {
		b.autoWidth = true
		width = xs.Len(title) + 2
	}

	b.SetTitle(title)
	b.SetSize(width, height)
	b.SetConstraints(width, height)
	b.SetScale(scale)

	if parent != nil {
		parent.AddChild(b)
	}

	return b
}

// ResizeChildren handles parent window resizing
func (b *SimpleButton) ResizeChildren() {
	if !b.Visible() {
		return
	}

	width, height := b.Size()

	if b.autoHeight {
		height = 1
	}

	if b.autoWidth {
		width = xs.Len(b.Title()) + 2
	}

	b.SetSize(width, height)
	b.SetConstraints(width, height)
}

// Draw paints the button in the screen and adjust colors depending on the button state
func (b *SimpleButton) Draw() {
	if !b.Visible() {
		return
	}

	clui.PushAttributes()
	defer clui.PopAttributes()

	x, y := b.Pos()
	w, h := b.Size()

	fg, bg := b.TextColor(), b.BackColor()

	if !b.Enabled() {
		fg = clui.RealColor(fg, b.Style(), "ButtonDisabledText")
		bg = clui.RealColor(bg, b.Style(), "ButtonDisabledBack")
	} else if b.Active() || b.ForceActiveStyle() {
		fgActive, bgActive := b.ActiveColors()

		fg = clui.RealColor(fgActive, b.Style(), "ButtonActiveText")
		bg = clui.RealColor(bgActive, b.Style(), "ButtonActiveBack")
	} else {
		fg = clui.RealColor(fg, b.Style(), "ButtonText")
		bg = clui.RealColor(bg, b.Style(), "ButtonBack")
	}

	clui.SetTextColor(fg)
	shift, text := clui.AlignColorizedText(b.Title(), w, b.Align())
	px, py := b.Paddings()

	if b.isPressed() == 0 {
		clui.SetBackColor(bg)
		clui.FillRect(x, y, w, h, ' ')
		clui.DrawText(x+shift+px, y+py, text)
	} else {
		clui.SetBackColor(bg)
		clui.FillRect(x, y, w, h, ' ')
		clui.DrawText(x+shift+px, y+py, b.Title())
	}
}

func (b *SimpleButton) isPressed() int32 {
	return atomic.LoadInt32(&b.pressed)
}

func (b *SimpleButton) setPressed(pressed int32) {
	atomic.StoreInt32(&b.pressed, pressed)
}

func (b *SimpleButton) processKeyEvent(event clui.Event) bool {
	ekey := event.Key

	if (ekey == term.KeySpace || ekey == term.KeyEnter) && b.isPressed() == 0 {
		b.setPressed(1)
		ev := clui.Event{Type: clui.EventRedraw}

		go func() {
			clui.PutEvent(ev)
			time.Sleep(100 * time.Millisecond)
			b.setPressed(0)
			clui.PutEvent(ev)
		}()

		if b.onClick != nil {
			b.onClick(event)
		}
		return true
	} else if ekey == term.KeyEsc && b.isPressed() != 0 {
		b.setPressed(0)
		clui.ReleaseEvents()
		return true
	}

	return false
}

func (b *SimpleButton) processMouseEvent(event clui.Event) bool {
	if event.Key == term.MouseLeft {
		b.setPressed(1)
		clui.GrabEvents(b)
		return true
	} else if event.Key == term.MouseRelease && b.isPressed() != 0 {
		clui.ReleaseEvents()
		bX, bY := b.Pos()
		bw, bh := b.Size()

		if event.X >= bX && event.Y >= bY && event.X < bX+bw && event.Y < bY+bh {
			if b.onClick != nil {
				b.onClick(event)
			}
		}
		b.setPressed(0)
		return true
	}

	return false
}

// ProcessEvent will process the events triggered by clui mainloop
func (b *SimpleButton) ProcessEvent(event clui.Event) bool {
	if !b.Enabled() {
		return false
	}

	if event.Type == clui.EventKey {
		return b.processKeyEvent(event)
	} else if event.Type == clui.EventMouse {
		return b.processMouseEvent(event)
	}

	return false
}

// OnClick sets the button's onClick callback
func (b *SimpleButton) OnClick(fn func(clui.Event)) {
	b.onClick = fn
}

// SetForceActiveStyle even if the button is not active the user may want
// to force setting the active style
func (b *SimpleButton) SetForceActiveStyle(f bool) {
	b.forceActiveStyle = f
}

// ForceActiveStyle returns the forceActiveStyle flag
func (b *SimpleButton) ForceActiveStyle() bool {
	return b.forceActiveStyle
}
