package layouts

import (
	"image"

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

// BorderLayout ia a gui.Layout which places its children onto a given gui.Side
type BorderLayout struct {
	gui.ViewTmpl
	views            map[Slot]gui.View
	horizontalLayout *LayoutResult
	verticalLayout   *LayoutResult

	viewDims map[Slot]gui.Dimension
}

var _ gui.Layout = &BorderLayout{}

func NewBorderLayout() *BorderLayout {
	return &BorderLayout{
		views:    map[Slot]gui.View{},
		viewDims: map[Slot]gui.Dimension{},
	}
}

func (g *BorderLayout) Views() []gui.View {
	s := make([]gui.View, 0, len(g.views))
	for _, view := range g.views {
		s = append(s, view)
	}
	return s
}

func (g *BorderLayout) SetView(v gui.View, slot Slot) {
	g.views[slot] = v
}

func (g *BorderLayout) View(slot Slot) gui.View {
	return g.views[slot]
}

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

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

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

	remainingVerticalSpacePerView := (img.Bounds().Dy() - verticalLayout.Sum.Height)
	if verticalLayout.VerticalNegativeCount > 0 {
		remainingVerticalSpacePerView /= verticalLayout.VerticalNegativeCount
	}

	remainingHorizontalSpacePerView := (img.Bounds().Dx() - horizontalLayout.Sum.Width)
	if horizontalLayout.HorizontalNegativeCount > 0 {
		remainingHorizontalSpacePerView /= horizontalLayout.HorizontalNegativeCount
	}

	fitsVertically := img.Bounds().Dy() >= verticalLayout.Sum.Height
	fitsHorizontally := img.Bounds().Dx() >= horizontalLayout.Sum.Width

	var topHeight int
	var bottomHeight int
	var leftWidth int
	var rightWidth int

	if view, ok := g.views[Top]; ok {
		_, topHeight = view.Layout(ctx)

		if fitsVertically {
			topHeight = iff(topHeight < 0, remainingVerticalSpacePerView, topHeight)
		} else {
			topHeight = int(float64(img.Bounds().Dy()) * float64(topHeight) / float64(verticalLayout.Sum.Height))
		}

		g.viewDims[Top] = gui.D(0, 0, img.Bounds().Dx(), topHeight)
		view.Draw(img.SubImage(image.Rect(0, 0, img.Bounds().Dx(), topHeight)).(*gui.Image), ctx)
	}

	if view, ok := g.views[Bottom]; ok {
		_, bottomHeight = view.Layout(ctx)

		if fitsVertically {
			bottomHeight = iff(bottomHeight < 0, remainingVerticalSpacePerView, bottomHeight)
		} else {
			bottomHeight = int(float64(img.Bounds().Dy()) * float64(bottomHeight) / float64(verticalLayout.Sum.Height))
		}

		g.viewDims[Bottom] = gui.D(0, img.Bounds().Dy()-bottomHeight, img.Bounds().Dx(), bottomHeight)
		view.Draw(img.SubImage(image.Rect(0, img.Bounds().Dy()-bottomHeight, img.Bounds().Dx(), img.Bounds().Dy())).(*gui.Image), ctx)
	}

	if view, ok := g.views[Left]; ok {
		leftWidth, _ = view.Layout(ctx)

		if fitsHorizontally {
			leftWidth = iff(leftWidth < 0, remainingHorizontalSpacePerView, leftWidth)
		} else {
			leftWidth = int(float64(img.Bounds().Dx()) * float64(leftWidth) / float64(horizontalLayout.Sum.Width))
		}

		g.viewDims[Left] = gui.D(0, topHeight, leftWidth, img.Bounds().Dy()-topHeight-bottomHeight)
		view.Draw(img.SubImage(image.Rect(0, topHeight, leftWidth, img.Bounds().Dy()-bottomHeight)).(*gui.Image), ctx)
	}

	if view, ok := g.views[Right]; ok {
		rightWidth, _ = view.Layout(ctx)

		if fitsHorizontally {
			rightWidth = iff(rightWidth < 0, remainingHorizontalSpacePerView, rightWidth)
		} else {
			rightWidth = int(float64(img.Bounds().Dx()) * float64(rightWidth) / float64(horizontalLayout.Sum.Width))
		}

		g.viewDims[Right] = gui.D(img.Bounds().Dx()-rightWidth, topHeight, rightWidth, img.Bounds().Dy()-topHeight-bottomHeight)
		view.Draw(img.SubImage(image.Rect(img.Bounds().Dx()-rightWidth, topHeight, img.Bounds().Dx(), img.Bounds().Dy()-bottomHeight)).(*gui.Image), ctx)
	}

	if view, ok := g.views[Center]; ok {
		g.viewDims[Center] = gui.D(leftWidth, topHeight, img.Bounds().Dx()-leftWidth-rightWidth, img.Bounds().Dy()-topHeight-bottomHeight)
		view.Draw(img.SubImage(image.Rect(leftWidth, topHeight, img.Bounds().Dx()-rightWidth, img.Bounds().Dy()-bottomHeight)).(*gui.Image), ctx)
	}

	g.verticalLayout = nil
	g.horizontalLayout = nil
}

func (g *BorderLayout) Layout(ctx gui.AppContext) (prefWidth, prefHeight int) {
	g.verticalLayout = CalculateLayoutResult(ctx, []gui.View{g.View(Top), g.View(Center), g.View(Bottom)})
	g.horizontalLayout = CalculateLayoutResult(ctx, []gui.View{g.View(Left), g.View(Center), g.View(Right)})
	return -1, -1
}

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

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

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

type Slot string

const (
	Top    Slot = "top"
	Bottom Slot = "bottom"
	Left   Slot = "left"
	Right  Slot = "right"
	Center Slot = "center"
)
