package geo

import "fmt"

type Box struct {
	TopLeft *Point
	Width   float64
	Height  float64
}

func NewBox(tl *Point, width, height float64) *Box {
	return &Box{
		TopLeft: tl,
		Width:   width,
		Height:  height,
	}
}

func (b *Box) Copy() *Box {
	if b == nil {
		return nil
	}
	return NewBox(b.TopLeft.Copy(), b.Width, b.Height)
}

func (b *Box) Center() *Point {
	return NewPoint(b.TopLeft.X+b.Width/2, b.TopLeft.Y+b.Height/2)
}

// Intersects returns true if the segment comes within buffer of the box
func (b *Box) Intersects(s Segment, buffer float64) bool {
	tl := NewPoint(b.TopLeft.X-buffer, b.TopLeft.Y-buffer)
	tr := NewPoint(tl.X+b.Width+buffer*2, tl.Y)
	br := NewPoint(tr.X, tr.Y+b.Height+buffer*2)
	bl := NewPoint(tl.X, br.Y)

	if p := IntersectionPoint(s.Start, s.End, tl, tr); p != nil {
		return true
	}
	if p := IntersectionPoint(s.Start, s.End, tr, br); p != nil {
		return true
	}
	if p := IntersectionPoint(s.Start, s.End, br, bl); p != nil {
		return true
	}
	if p := IntersectionPoint(s.Start, s.End, bl, tl); p != nil {
		return true
	}
	return false
}

func (b *Box) Intersections(s Segment) []*Point {
	pts := []*Point{}

	tl := b.TopLeft
	tr := NewPoint(tl.X+b.Width, tl.Y)
	br := NewPoint(tr.X, tr.Y+b.Height)
	bl := NewPoint(tl.X, br.Y)

	if p := IntersectionPoint(s.Start, s.End, tl, tr); p != nil {
		pts = append(pts, p)
	}
	if p := IntersectionPoint(s.Start, s.End, tr, br); p != nil {
		pts = append(pts, p)
	}
	if p := IntersectionPoint(s.Start, s.End, br, bl); p != nil {
		pts = append(pts, p)
	}
	if p := IntersectionPoint(s.Start, s.End, bl, tl); p != nil {
		pts = append(pts, p)
	}
	return pts
}

func (b *Box) ToString() string {
	if b == nil {
		return ""
	}
	return fmt.Sprintf("{TopLeft: %s, Width: %.0f, Height: %.0f}", b.TopLeft.ToString(), b.Width, b.Height)
}

func (b *Box) Contains(p *Point) bool {
	return !(p.X < b.TopLeft.X || b.TopLeft.X+b.Width < p.X ||
		p.Y < b.TopLeft.Y || b.TopLeft.Y+b.Height < p.Y)
}

func (b1 Box) Overlaps(b2 Box) bool {
	// https://silentmatt.com/rectangle-intersection/
	return (b1.TopLeft.X < (b2.TopLeft.X + b2.Width)) && ((b1.TopLeft.X + b1.Width) > b2.TopLeft.X) &&
		(b1.TopLeft.Y < (b2.TopLeft.Y + b2.Height)) && ((b1.TopLeft.Y + b1.Height) > b2.TopLeft.Y)
}
