package layouts

import (
	"image"

	"git.milar.in/milarin/gui"
)

// FlowLayout ia a gui.Layout which places its children in a linear layout
type FlowLayout struct {
	gui.ViewTmpl
	views           []gui.View
	lastLayoutPhase *LayoutResult

	viewDims map[gui.View]gui.Dimension

	// Orientation defines in which direction the children will be placed
	Orientation gui.Orientation
}

var _ gui.Layout = &FlowLayout{}

func NewFlowLayout(orientation gui.Orientation) *FlowLayout {
	return &FlowLayout{
		views:       make([]gui.View, 0),
		viewDims:    map[gui.View]gui.Dimension{},
		Orientation: orientation,
	}
}

func (g *FlowLayout) Views() []gui.View {
	return g.views
}

func (g *FlowLayout) AppendViews(v ...gui.View) {
	g.views = append(g.views, v...)
}

func (g *FlowLayout) PrependViews(v ...gui.View) {
	g.views = append(v, g.views...)
}

func (g *FlowLayout) InsertView(v gui.View, index int) {
	g.views = append(g.views[:index], append([]gui.View{v}, g.views[index:]...)...)
}

func (g *FlowLayout) removeView(v gui.View) {
	for index, view := range g.Views() {
		if v == view {
			delete(g.viewDims, view)
			g.views = append(g.views[:index], g.views[index+1:]...)
			return
		}
	}
}

func (g *FlowLayout) RemoveViews(v ...gui.View) {
	views := append(make([]gui.View, 0, len(v)), v...)
	for _, view := range views {
		g.removeView(view)
	}
}

func (g *FlowLayout) Draw(img *gui.Image, ctx gui.AppContext) {
	g.ViewTmpl.Draw(img, ctx)

	if g.lastLayoutPhase == nil {
		g.Layout(ctx)
	}
	layout := g.lastLayoutPhase

	if g.Orientation == gui.Horizontal {
		remainingSpacePerView := img.Bounds().Dx() - layout.Sum.Width
		if remainingSpacePerView < 0 {
			remainingSpacePerView = 0
		}
		if layout.HorizontalNegativeCount > 0 {
			remainingSpacePerView /= layout.HorizontalNegativeCount
		}

		x := 0
		for _, view := range g.views {
			size := layout.Sizes[view]

			size.Height = iff(size.Height < 0, img.Bounds().Dy(), size.Height)
			if size.Width < 0 {
				size.Width = iff(layout.Sum.Width > img.Bounds().Dx(), 0, remainingSpacePerView)
			}

			g.viewDims[view] = gui.D(x, 0, size.Width, size.Height)
			view.Draw(img.SubImage(image.Rect(x, 0, x+size.Width, size.Height)).(*gui.Image), ctx)

			x += size.Width
			if x >= img.Bounds().Dx() {
				break
			}
		}
	} else if g.Orientation == gui.Vertical {
		remainingSpacePerView := img.Bounds().Dy() - layout.Sum.Height
		if remainingSpacePerView < 0 {
			remainingSpacePerView = 0
		}
		if layout.VerticalNegativeCount > 0 {
			remainingSpacePerView /= layout.VerticalNegativeCount
		}

		y := 0
		for _, view := range g.views {
			size := layout.Sizes[view]

			size.Width = iff(size.Width < 0, img.Bounds().Dx(), size.Width)
			if size.Height < 0 {
				size.Height = iff(layout.Sum.Height > img.Bounds().Dy(), 0, remainingSpacePerView)
			}

			g.viewDims[view] = gui.D(0, y, size.Width, size.Height)
			view.Draw(img.SubImage(image.Rect(0, y, size.Width, y+size.Height)).(*gui.Image), ctx)

			y += size.Height
			if y >= img.Bounds().Dy() {
				break
			}
		}
	}

	g.lastLayoutPhase = nil
}

func (g *FlowLayout) Layout(ctx gui.AppContext) (prefWidth, prefHeight int) {
	layout := CalculateLayoutResult(ctx, g.Views())
	g.lastLayoutPhase = layout

	if g.Orientation == gui.Horizontal {
		prefWidth = iff(layout.HorizontalNegativeCount == 0, layout.Sum.Width, -1)
		prefHeight = iff(layout.VerticalNegativeCount == 0, layout.Max.Height, -1)
	} else if g.Orientation == gui.Vertical {
		prefWidth = iff(layout.HorizontalNegativeCount == 0, layout.Max.Width, -1)
		prefHeight = iff(layout.VerticalNegativeCount == 0, layout.Sum.Height, -1)
	}

	layout.Pref = gui.Size{Width: prefWidth, Height: prefHeight}
	return
}

func (g *FlowLayout) OnMouseClick(event gui.MouseEvent) (consumed bool) {
	for view, dim := range g.viewDims {
		if event.Position.In(dim) {
			if view.OnMouseClick(event.SubtractPos(dim.Point)) {
				return true
			}
		}
	}
	return false
}

func (g *FlowLayout) OnMouseMove(event gui.MouseEvent) (consumed bool) {
	for view, dim := range g.viewDims {
		if event.Position.In(dim) {
			if view.OnMouseMove(event.SubtractPos(dim.Point)) {
				return true
			}
		}
	}
	return false
}

func (g *FlowLayout) OnMouseScroll(event gui.MouseEvent) (consumed bool) {
	for view, dim := range g.viewDims {
		if event.Position.In(dim) {
			if view.OnMouseScroll(event.SubtractPos(dim.Point)) {
				return true
			}
		}
	}
	return false
}
