package geo

import (
	"fmt"
	"math"
)

type Intersectable interface {
	Intersections(segment Segment) []*Point
}

type Segment struct {
	Start *Point
	End   *Point
}

func NewSegment(from, to *Point) *Segment {
	return &Segment{from, to}
}

func (s Segment) Overlaps(otherS Segment, isHorizontal bool, buffer float64) bool {
	if isHorizontal {
		if math.Min(s.Start.Y, s.End.Y)-math.Max(otherS.Start.Y, otherS.End.Y) >= buffer {
			return false
		}
		if math.Min(otherS.Start.Y, otherS.End.Y)-math.Max(s.Start.Y, s.End.Y) >= buffer {
			return false
		}
		return true
	} else {
		if math.Min(s.Start.X, s.End.X)-math.Max(otherS.Start.X, otherS.End.X) >= buffer {
			return false
		}
		if math.Min(otherS.Start.X, otherS.End.X)-math.Max(s.Start.X, s.End.X) >= buffer {
			return false
		}
		return true
	}
}

func (segment Segment) Intersects(otherSegment Segment) bool {
	return IntersectionPoint(segment.Start, segment.End, otherSegment.Start, otherSegment.End) != nil
}

//nolint:unused
func (s Segment) ToString() string {
	return fmt.Sprintf("%v -> %v", s.Start.ToString(), s.End.ToString())
}

func (segment Segment) Intersections(otherSegment Segment) []*Point {
	point := IntersectionPoint(segment.Start, segment.End, otherSegment.Start, otherSegment.End)
	if point == nil {
		return nil
	}
	return []*Point{point}
}

// getBounds takes a segment and returns the floor and ceil of where it can shift to
// If there is no floor or ceiling, negative or positive infinity is used, respectively
// The direction is inferred, e.g. b/c the passed in segment is vertical, it's inferred we want horizontal bounds
// buffer says how close the segment can be, on both axes, to other segments given
// .    │              │
// .    │              │
// .    │              │
// .    │              │
// .    │           non-overlap
// .    │
// .    │
// .    │
// .    │     segment
// .    │       │
// .    │       │         ceil
// .    │       │            │
// .            │            │
// . floor      │            │
// .                         │
// .                         │
// .                         │
// .                         │
// NOTE: the assumption is that all segments given are orthogonal
func (segment *Segment) GetBounds(segments []*Segment, buffer float64) (float64, float64) {
	ceil := math.Inf(1)
	floor := math.Inf(-1)
	if segment.Start.X == segment.End.X && segment.Start.Y == segment.End.Y {
		// single point, no segment
		return floor, ceil
	}
	isHorizontal := segment.Start.X == segment.End.X
	for _, otherSegment := range segments {
		if isHorizontal {
			// Exclude segments that don't overlap (non-overlap in above diagram)
			if otherSegment.End.Y < segment.Start.Y-buffer {
				continue
			}
			if otherSegment.Start.Y > segment.End.Y+buffer {
				continue
			}
			if otherSegment.Start.X <= segment.Start.X {
				floor = math.Max(floor, otherSegment.Start.X)
			}
			if otherSegment.Start.X > segment.Start.X {
				ceil = math.Min(ceil, otherSegment.Start.X)
			}
		} else {
			if otherSegment.End.X < segment.Start.X-buffer {
				continue
			}
			if otherSegment.Start.X > segment.End.X+buffer {
				continue
			}
			if otherSegment.Start.Y <= segment.Start.Y {
				floor = math.Max(floor, otherSegment.Start.Y)
			}
			if otherSegment.Start.Y > segment.Start.Y {
				ceil = math.Min(ceil, otherSegment.Start.Y)
			}
		}
	}
	return floor, ceil
}

func (segment Segment) Length() float64 {
	return EuclideanDistance(segment.Start.X, segment.Start.Y, segment.End.X, segment.End.Y)
}

func (segment Segment) ToVector() Vector {
	return NewVector(segment.End.X-segment.Start.X, segment.End.Y-segment.Start.Y)
}
