package gioui

import (
	"fmt"
	"image"
	"image/color"

	"golang.org/x/exp/shiny/materialdesign/icons"

	"gioui.org/f32"
	"gioui.org/op/clip"
	"gioui.org/op/paint"
	"gioui.org/widget"

	"gioui.org/gesture"
	"gioui.org/io/pointer"
	"gioui.org/layout"
	"gioui.org/op"
	"gioui.org/text"
	"gioui.org/unit"
	"gioui.org/widget/material"
)

type NumberInput struct {
	Value          int
	dragStartValue int
	dragStartXY    float32
	clickDecrease  gesture.Click
	clickIncrease  gesture.Click
}

type NumericUpDownStyle struct {
	NumberInput     *NumberInput
	Min             int
	Max             int
	Color           color.NRGBA
	Font            text.Font
	TextSize        unit.Value
	BorderColor     color.NRGBA
	IconColor       color.NRGBA
	BackgroundColor color.NRGBA
	CornerRadius    unit.Value
	Border          unit.Value
	ButtonWidth     unit.Value
	UnitsPerStep    unit.Value
	shaper          text.Shaper
}

func NumericUpDown(th *material.Theme, number *NumberInput, min, max int) NumericUpDownStyle {
	bgColor := th.Palette.Fg
	bgColor.R /= 4
	bgColor.G /= 4
	bgColor.B /= 4
	return NumericUpDownStyle{
		NumberInput:     number,
		Min:             min,
		Max:             max,
		Color:           white,
		BorderColor:     th.Palette.Fg,
		IconColor:       th.Palette.ContrastFg,
		BackgroundColor: bgColor,
		CornerRadius:    unit.Dp(4),
		ButtonWidth:     unit.Dp(16),
		Border:          unit.Dp(1),
		UnitsPerStep:    unit.Dp(8),
		TextSize:        th.TextSize.Scale(14.0 / 16.0),
		shaper:          th.Shaper,
	}
}

func (s NumericUpDownStyle) Layout(gtx C) D {
	size := gtx.Constraints.Min
	defer op.Save(gtx.Ops).Load()
	rr := float32(gtx.Px(s.CornerRadius))
	border := float32(gtx.Px(s.Border))
	clip.UniformRRect(f32.Rectangle{Max: f32.Point{
		X: float32(gtx.Constraints.Min.X),
		Y: float32(gtx.Constraints.Min.Y),
	}}, rr).Add(gtx.Ops)
	paint.Fill(gtx.Ops, s.BorderColor)
	op.Offset(f32.Pt(border, border)).Add(gtx.Ops)
	clip.UniformRRect(f32.Rectangle{Max: f32.Point{
		X: float32(gtx.Constraints.Min.X) - border*2,
		Y: float32(gtx.Constraints.Min.Y) - border*2,
	}}, rr-border).Add(gtx.Ops)
	gtx.Constraints.Min.X -= int(border * 2)
	gtx.Constraints.Min.Y -= int(border * 2)
	gtx.Constraints.Max = gtx.Constraints.Min
	layout.Flex{Axis: layout.Horizontal, Alignment: layout.Middle}.Layout(gtx,
		layout.Rigid(s.button(gtx.Constraints.Max.Y, widgetForIcon(icons.NavigationArrowBack), -1, &s.NumberInput.clickDecrease)),
		layout.Flexed(1, s.layoutText),
		layout.Rigid(s.button(gtx.Constraints.Max.Y, widgetForIcon(icons.NavigationArrowForward), 1, &s.NumberInput.clickIncrease)),
	)
	if s.NumberInput.Value < s.Min {
		s.NumberInput.Value = s.Min
	}
	if s.NumberInput.Value > s.Max {
		s.NumberInput.Value = s.Max
	}
	return layout.Dimensions{Size: size}
}

func (s NumericUpDownStyle) button(height int, icon *widget.Icon, delta int, click *gesture.Click) layout.Widget {
	return func(gtx C) D {
		btnWidth := gtx.Px(s.ButtonWidth)
		return layout.Stack{Alignment: layout.Center}.Layout(gtx,
			layout.Stacked(func(gtx layout.Context) layout.Dimensions {
				//paint.FillShape(gtx.Ops, black, clip.Rect(image.Rect(0, 0, btnWidth, height)).Op())
				return layout.Dimensions{Size: image.Point{X: btnWidth, Y: height}}
			}),
			layout.Expanded(func(gtx C) D {
				size := btnWidth
				if height < size {
					size = height
				}
				if size < 1 {
					size = 1
				}
				if icon != nil {
					icon.Color = s.IconColor
					return icon.Layout(gtx, unit.Px(float32(size)))
				}
				return layout.Dimensions{}
			}),
			layout.Expanded(func(gtx C) D {
				return s.layoutClick(gtx, delta, click)
			}),
		)
	}
}

func (s NumericUpDownStyle) layoutText(gtx C) D {
	return layout.Stack{Alignment: layout.Center}.Layout(gtx,
		layout.Stacked(func(gtx C) D {
			paint.FillShape(gtx.Ops, s.BackgroundColor, clip.Rect(image.Rect(0, 0, gtx.Constraints.Max.X, gtx.Constraints.Max.Y)).Op())
			return layout.Dimensions{Size: gtx.Constraints.Max}
		}),
		layout.Expanded(func(gtx layout.Context) layout.Dimensions {
			paint.ColorOp{Color: s.Color}.Add(gtx.Ops)
			return widget.Label{Alignment: text.Middle}.Layout(gtx, s.shaper, s.Font, s.TextSize, fmt.Sprintf("%v", s.NumberInput.Value))
		}),
		layout.Expanded(s.layoutDrag),
	)
}

func (s NumericUpDownStyle) layoutDrag(gtx layout.Context) layout.Dimensions {
	{ // handle dragging
		pxPerStep := float32(gtx.Px(s.UnitsPerStep))
		for _, ev := range gtx.Events(s.NumberInput) {
			if e, ok := ev.(pointer.Event); ok {
				switch e.Type {
				case pointer.Press:
					s.NumberInput.dragStartValue = s.NumberInput.Value
					s.NumberInput.dragStartXY = e.Position.X - e.Position.Y

				case pointer.Drag:
					var deltaCoord float32
					deltaCoord = e.Position.X - e.Position.Y - s.NumberInput.dragStartXY
					s.NumberInput.Value = s.NumberInput.dragStartValue + int(deltaCoord/pxPerStep+0.5)
				}
			}
		}

		// Avoid affecting the input tree with pointer events.
		stack := op.Save(gtx.Ops)
		// register for input
		dragRect := image.Rect(0, 0, gtx.Constraints.Min.X, gtx.Constraints.Min.Y)
		pointer.Rect(dragRect).Add(gtx.Ops)
		pointer.InputOp{
			Tag:   s.NumberInput,
			Types: pointer.Press | pointer.Drag | pointer.Release,
		}.Add(gtx.Ops)
		stack.Load()
	}
	return layout.Dimensions{Size: gtx.Constraints.Min}
}

func (s NumericUpDownStyle) layoutClick(gtx layout.Context, delta int, click *gesture.Click) layout.Dimensions {
	// handle clicking
	for _, e := range click.Events(gtx) {
		switch e.Type {
		case gesture.TypeClick:
			s.NumberInput.Value += delta
		}
	}
	// Avoid affecting the input tree with pointer events.
	stack := op.Save(gtx.Ops)
	// register for input
	clickRect := image.Rect(0, 0, gtx.Constraints.Min.X, gtx.Constraints.Min.Y)
	pointer.Rect(clickRect).Add(gtx.Ops)
	click.Add(gtx.Ops)
	stack.Load()
	return layout.Dimensions{Size: gtx.Constraints.Min}
}