require 'mathn'

require_relative 'point'

module Geometry

=begin rdoc
An edge. It's a line segment between 2 points. Generally part of a {Polygon}.

== Usage
    edge = Geometry::Edge([1,1], [2,2])

=end

    class Edge
	attr_reader :first, :last

	# Construct a new {Edge} object from any two things that can be converted
	# to a {Point}.
	def initialize(point0, point1)
	    @first, @last = [Point[point0], Point[point1]]
	end

	# Two Edges are equal if both have equal {Point}s in the same order
	def ==(other)
	    (@first == other.first) && (@last == other.last)
	end

	# @param [Point] point	A {Point} to spaceship with
	# @return [Boolean]	Returns 1 if the {Point} is strictly to the left of the receiver, -1 to the right, and 0 if the point is on the receiver
	def <=>(point)
	    case point
		when Point
		    k = (@last.x - @first.x) * (point.y - @first.y) - (point.x - @first.x) * (@last.y - @first.y)
		    if 0 == k
			(((@first.x <=> point.x) + (@last.x <=> point.x)).abs <= 1) && (((@first.y <=> point.y) + (@last.y <=> point.y)).abs <= 1) ? 0 : nil
		    else
			k <=> 0
		    end
		else
		    raise ArgumentError, "Can't spaceship with #{point.class}"
	    end
	end

	# Return a new {Edge} with swapped endpoints
	def reverse
	    Edge.new(@last, @first)
	end

	# In-place swap the endpoints
	def reverse!
	    @first, @last = @last, @first
	    self
	end

	# Return the {Edge}'s length along the Y axis
	def height
	    (@first.y - @last.y).abs
	end

	# Return the {Edge}'s length along the X axis
	def width
	    (@first.x - @last.x).abs
	end

	def inspect
	    'Edge(' + @first.inspect + ', ' + @last.inspect + ')'
	end
	alias :to_s :inspect

	# @return [Bool] Returns true if the passed {Edge} is parallel to the receiver
	def parallel?(edge)
	    v1, v2 = self.direction, edge.direction
	    winding = v1[0]*v2[1] - v1[1]*v2[0]
	    if 0 == winding	# collinear?
		if v1 == v2
		    1		# same direction
		else
		    -1		# opposite direction
		end
	    else
		false
	    end
	end

	# @param [Edge] other	The other {Edge} to check
	# @return [Bool] Returns true if the receiver and the passed {Edge} share an endpoint
	def connected?(other)
	    (@first == other.last) || (@last == other.first) || (@first == other.first) || (@last == other.last)
	end

	# @return [Vector]  A unit {Vector} pointing from first to last
	def direction
	    self.vector.normalize
	end

	# Find the intersection of two {Edge}s (http://bloggingmath.wordpress.com/2009/05/29/line-segment-intersection/)
	# @param [Edge] other	The other {Edge}
	# @return [Point] The intersection of the two {Edge}s, nil if they don't intersect, true if they're collinear and overlapping, and false if they're collinear and non-overlapping
	def intersection(other)
	    return self.first if (self.first == other.first) or (self.first == other.last)
	    return self.last if (self.last == other.first) or (self.last == other.last)

	    p0, p1 = self.first, self.last
	    p2, p3 = other.first, other.last
	    v1, v2 = self.vector, other.vector

	    denominator = v1[0] * v2[1] - v2[0] * v1[1]		# v1 x v2
	    p = p0 - p2
	    if denominator == 0	    # collinear, so check for overlap
		if 0 == (-v1[1] * p.x + v1[0] * p.y)	# collinear?
		    # The edges are collinear, but do they overlap?
		    # Project them onto the x and y axes to find out
		    left1, right1 = [self.first[0], self.last[0]].sort
		    bottom1, top1 = [self.first[1], self.last[1]].sort
		    left2, right2 = [other.first[0], other.last[0]].sort
		    bottom2, top2 = [other.first[1], other.last[1]].sort

		    !((left2 > right1) || (right2 < left1) || (top2 < bottom1) || (bottom2 > top1))
		else
		    nil
		end
	    else
		s = (-v1[1] * p.x + v1[0] * p.y) / denominator	# v1 x (p0 - p2) / denominator
		t = ( v2[0] * p.y - v2[1] * p.x) / denominator	# v2 x (p0 - p2) / denominator

		p0 + v1 * t if ((0..1) === s) && ((0..1) === t)
	    end
	end

	# @return [Vector]  A {Vector} pointing from first to last
	def vector
	    Vector[*((last-first).to_a)]
	end

	def to_a
	    [@first, @last]
	end
    end
end