package ui

import (
	"fmt"
	"image/color"
	"strings"
	"time"
	"unicode"

	"opslag.de/schobers/geom"
)

type TextSelection struct {
	Caret int
	Start int
	End   int
}

func (s *TextSelection) SetSelectionToCaret() {
	s.Start = s.Caret
	s.End = s.Caret
}

func (s *TextSelection) MoveCaret(delta int) {
	s.Caret = s.Caret + delta
}

func (s TextSelection) HasSelection() bool {
	return s.Start != s.End
}

type TextBox struct {
	ControlBase

	box   BufferControl
	blink time.Time

	Focus     bool
	Text      string
	Selection TextSelection
}

func BuildTextBox(fn func(*TextBox)) *TextBox {
	var b = &TextBox{}
	if fn != nil {
		fn(b)
	}
	return b
}

func (b *TextBox) pad(ctx Context) float32 {
	return ctx.Style().Dimensions.TextPadding
}

func (b *TextBox) Arrange(ctx Context, bounds geom.RectangleF32, offset geom.PointF32, parent Control) {
	b.ControlBase.Arrange(ctx, bounds, offset, parent)
	b.box.Arrange(ctx, bounds.Inset(b.pad(ctx)), offset, b)
}

func (b *TextBox) DesiredSize(ctx Context) geom.PointF32 {
	var fontName = b.FontName(ctx)
	var font = ctx.Renderer().Font(fontName)
	var width = font.WidthOf(b.Text)
	var height = font.Height()
	var pad = b.pad(ctx)
	return geom.PtF32(width+pad*2, height+pad*2)
}

func (b *TextBox) mousePosToCaretPos(ctx Context, e MouseEvent) int {
	p := b.ToControlPosition(e.Pos())
	offset := p.X - b.box.bounds.Min.X
	f := ctx.Renderer().Font(b.FontName(ctx))
	var carets = [3]int{0, 0, len(b.Text)}
	var offsets = [3]float32{0, 0, f.WidthOf(b.Text)}
	var updateCenter = func() {
		var c = (carets[0] + carets[2]) / 2
		carets[1] = c
		offsets[1] = f.WidthOf(b.Text[:c])
	}
	updateCenter()
	for (carets[2] - carets[0]) > 1 {
		if offset > offsets[1] {
			carets[0] = carets[1]
			offsets[0] = offsets[1]
			updateCenter()
		} else {
			carets[2] = carets[1]
			offsets[2] = offsets[1]
			updateCenter()
		}
	}
	if geom.Abs32(offsets[0]-offset) < geom.Abs32(offsets[2]-offset) {
		return carets[0]
	}
	return carets[2]
}

func (b *TextBox) cut() string {
	start, end := b.selectionRange()
	if end == 0 {
		return ""
	}
	cut := b.Text[start:end]
	b.Text = b.Text[:start] + b.Text[end:]
	b.Selection.Caret = start
	b.Selection.SetSelectionToCaret()
	return cut
}

func (b *TextBox) selection() string {
	start, end := b.selectionRange()
	if end == 0 {
		return ""
	}
	return b.Text[start:end]
}

func (b *TextBox) selectionRange() (int, int) {
	start, end := b.Selection.Start, b.Selection.End
	if start == end {
		return 0, 0
	}
	if start > end {
		start, end = end, start
	}
	return start, end
}

func (b *TextBox) Handle(ctx Context, e Event) {
	b.ControlBase.Handle(ctx, e)
	b.box.Handle(ctx, e)
	switch e := e.(type) {
	case *MouseButtonDownEvent:
		if b.over {
			b.Focus = true
			b.Selection.Caret = b.mousePosToCaretPos(ctx, e.MouseEvent)
			b.Selection.SetSelectionToCaret()
			b.blink = time.Now()
		} else {
			b.Focus = false
		}
	case *MouseMoveEvent:
		if b.Focus && b.pressed && b.over {
			b.Selection.Caret = b.mousePosToCaretPos(ctx, e.MouseEvent)
			b.Selection.End = b.Selection.Caret
		}
	case *KeyPressEvent:
		if !b.Focus {
			break
		}
		selectAfterMove := func() {
			if e.Modifiers&KeyModifierShift == KeyModifierShift {
				b.Selection.End = b.Selection.Caret
			} else {
				b.Selection.SetSelectionToCaret()
			}
		}
		switch {
		case e.Key == KeyDelete:
			if b.Selection.HasSelection() {
				b.cut()
			} else {
				caret := b.Selection.Caret
				if caret < len(b.Text) {
					b.Text = b.Text[:caret] + b.Text[caret+1:]
				}
			}
		case e.Key == KeyBackspace:
			if b.Selection.HasSelection() {
				b.cut()
			} else {
				caret := b.Selection.Caret
				if caret > 0 {
					b.Text = b.Text[:caret-1] + b.Text[caret:]
					b.Selection.Caret = caret - 1
					b.Selection.SetSelectionToCaret()
				}
			}
		case e.Key == KeyEnd:
			b.Selection.Caret = len(b.Text)
			selectAfterMove()
		case e.Key == KeyHome:
			b.Selection.Caret = 0
			selectAfterMove()
		case e.Key == KeyLeft:
			if b.Selection.Caret > 0 {
				b.Selection.MoveCaret(-1)
				selectAfterMove()
			}
		case e.Key == KeyRight:
			if b.Selection.Caret < len(b.Text) {
				b.Selection.MoveCaret(1)
				selectAfterMove()
			}
		case !unicode.IsControl(e.Character):
			if e.Modifiers == KeyModifierNone {
				b.cut()
				caret := b.Selection.Caret
				b.Text = fmt.Sprintf("%s%c%s", b.Text[:caret], e.Character, b.Text[caret:])
				b.Selection.Caret = caret + 1
			} else if e.Modifiers&KeyModifierControl == KeyModifierControl {
				switch e.Character {
				case 'a':
					b.Selection.Start = 0
					b.Selection.End = len(b.Text)
					b.Selection.Caret = b.Selection.End
				case 'c':
					DefaultClipboard.WriteText(b.selection())
				case 'v':
					text, err := DefaultClipboard.ReadText()
					text = strings.Map(func(r rune) rune {
						if unicode.IsControl(r) {
							return -1
						}
						return r
					}, text)
					if err == nil {
						b.cut()
						caret := b.Selection.Caret
						b.Text = b.Text[:caret] + text + b.Text[caret:]
						b.Selection.Caret = caret + len(text)
					}
				case 'x':
					DefaultClipboard.WriteText(b.cut())
				}
			}
		}
	}
	if b.over {
		ctx.Renderer().SetMouseCursor(MouseCursorText)
	}
	ctx.Animate()
}

func (b *TextBox) Render(ctx Context) {
	b.RenderBackground(ctx)
	b.RenderOutline(ctx)

	c := b.FontColor(ctx)
	f := b.FontName(ctx)
	style := ctx.Style()
	var caretWidth float32 = 1
	b.box.RenderFn(ctx, func(_ Context, size geom.PointF32) {
		var renderer = ctx.Renderer()
		renderer.Clear(color.Transparent)
		if b.Selection.Start != b.Selection.End {
			left, right := renderer.Font(f).WidthOf(b.Text[:b.Selection.Start]), renderer.Font(f).WidthOf(b.Text[:b.Selection.End])
			renderer.FillRectangle(geom.RectF32(left, 0, right, size.Y), style.Palette.PrimaryHighlight)
		}
		renderer.Text(geom.ZeroPtF32, f, c, b.Text)
		const interval = 500 * time.Millisecond
		if b.Focus && (time.Since(b.blink)/interval)%2 == 0 {
			var w = renderer.Font(f).WidthOf(b.Text[:b.Selection.Caret])
			var caret = w + .5*caretWidth
			renderer.Rectangle(geom.RectF32(caret, 0, caret, size.Y), c, caretWidth)
		}
	})
}