package ui

import (
	"image/color"

	"opslag.de/schobers/geom"
)

type overflow struct {
	Proxy

	Background color.Color

	barWidth float32
	desired  geom.PointF32
	bounds   geom.RectangleF32
	offset   geom.PointF32
	parent   Control
	content  Buffer

	hor *Scrollbar
	ver *Scrollbar
}

func Overflow(content Control) Control {
	return OverflowBackground(content, nil)
}

func OverflowBackground(content Control, back color.Color) Control {
	var o = &overflow{Proxy: Proxy{Content: content}, Background: back}
	o.hor = BuildScrollbar(OrientationHorizontal, func(*Scrollbar) {})
	o.ver = BuildScrollbar(OrientationVertical, func(*Scrollbar) {})
	return o
}

func (o *overflow) shouldScroll(bounds geom.RectangleF32) (hor bool, ver bool) {
	var scroll = func(need, actual float32) bool {
		return !geom.IsNaN32(need) && need > actual
	}
	var size = o.desired
	hor = scroll(size.X, bounds.Dx())
	ver = scroll(size.Y, bounds.Dy())
	if ver && !hor {
		hor = scroll(size.X+o.barWidth, bounds.Dx())
	}
	if hor && !ver {
		ver = scroll(size.Y+o.barWidth, bounds.Dy())
	}
	return
}

func (o *overflow) doOnVisibleBars(fn func(bar *Scrollbar)) {
	hor, ver := o.shouldScroll(o.bounds)
	if hor {
		fn(o.hor)
	}
	if ver {
		fn(o.ver)
	}
}

func (o *overflow) Arrange(ctx Context, bounds geom.RectangleF32, offset geom.PointF32, parent Control) {
	o.barWidth = ctx.Style().Dimensions.ScrollbarWidth
	o.desired = o.Content.DesiredSize(ctx)
	o.bounds = bounds
	o.offset = offset
	o.parent = parent

	var hor, ver = o.shouldScroll(bounds)
	var contentX, contentY float32 = 0, 0
	var contentW, contentH = bounds.Dx(), bounds.Dy()
	if hor {
		contentX -= o.hor.ContentOffset
		contentH = geom.Max32(0, contentH-o.barWidth)
	}
	if ver {
		contentY -= o.ver.ContentOffset
		contentW = geom.Max32(0, contentW-o.barWidth)
	}
	o.Content.Arrange(ctx, geom.RectF32(contentX, contentY, contentW, contentH), offset.Add(bounds.Min), o)
	if hor {
		o.hor.ContentLength = o.desired.X
		o.hor.Arrange(ctx, geom.RectF32(bounds.Min.X, bounds.Min.Y+contentH, bounds.Min.X+contentW, bounds.Max.Y), offset, o)
	}
	if ver {
		o.ver.ContentLength = o.desired.Y
		o.ver.Arrange(ctx, geom.RectF32(bounds.Min.X+contentW, bounds.Min.Y, bounds.Max.X, bounds.Max.Y), offset, o)
	}
}

func (o *overflow) Bounds() geom.RectangleF32 { return o.bounds }

func (o *overflow) DesiredSize(ctx Context) geom.PointF32 {
	return geom.PtF32(geom.NaN32(), geom.NaN32())
}

func (o *overflow) Handle(ctx Context, e Event) {
	hor, ver := o.shouldScroll(o.bounds)
	if hor {
		o.hor.Handle(ctx, e)
	}
	if ver {
		o.ver.Handle(ctx, e)
	}
	switch e := e.(type) {
	case *MouseMoveEvent:
		if ver {
			var contentO = o.Content.Offset()
			var content = o.Content.Bounds()
			content.Min = contentO
			content.Max = content.Max.Add(contentO)
			if e.MouseWheel != 0 && e.Pos().In(content) {
				o.ver.ContentOffset = geom.Max32(0, geom.Min32(o.ver.ContentLength-content.Dy(), o.ver.ContentOffset-36*e.MouseWheel))
			}
		}
	}
	o.Content.Handle(ctx, e)
}

func (o *overflow) IsInBounds(p geom.PointF32) bool { return p.Sub(o.offset).In(o.bounds) }

func (o *overflow) Offset() geom.PointF32 { return o.offset }

func (o *overflow) Parent() Control { return o.parent }

func (o *overflow) Render(ctx Context) {
	var renderer = ctx.Renderer()

	if o.Background != nil {
		renderer.FillRectangle(o.bounds, o.Background)
	}

	var content = o.Content.Bounds()
	content.Min = geom.ZeroPtF32
	err := o.content.Update(ctx, content.Size())
	if err != nil {
		panic(err)
	}
	o.content.Render(ctx, o.bounds.Min, func(Context, geom.PointF32) {
		renderer.Clear(color.Transparent)
		o.Content.Render(ctx)
	})

	o.doOnVisibleBars(func(bar *Scrollbar) {
		bar.Render(ctx)
	})
}