
# segment.py


import math, random
from point import Point
from vector import Vector

import matplotlib.pyplot as plt


class Segment():
  '''
    A line between two points p1 and p2
  '''

  def __init__(self, *args):
    if len(args) == 2:
      self.p1 = Point(args[0])
      self.p2 = Point(args[1])
    elif len(args) == 4:
      self.p1 = Point(args[0], args[1])
      self.p2 = Point(args[2], args[3])
    else:
      raise TypeError('The construct accepts two points, '
              'or four numbers, but got: {0}'.format(args))

    self.slope = self.calcSlope()


  def calcSlope(self):
    if self.p1.x != self.p2.x:
      return (self.p1.y - self.p2.y) / (self.p1.x - self.p2.x)
    else:
      return None


  def get(self):
    return [ self.p1.get(), self.p2.get() ]

  def xs(self):
    return [self.p1.x, self.p2.x]

  def ys(self):
    return [self.p1.y, self.p2.y]


  def __str__(self):
    return f"[{self.p1}, {self.p2}]"


  def __eq__(self, other):
    if other == None:
      return False
    return self.p1 == other.p1 and self.p2 == other.p2


  def length(self):
    return self.p1.distance_to(self.p2)

  def angle(self):
    return Math.atan2(self.p2.y-self.p1.y, self.p2.x-self.p1.x)



  def minDist(self, p) : 
    # return the minimum distance 
    # between a line segment and a point p 
    # https://www.geeksforgeeks.org/minimum-distance-from-a-point-to-the-line-segment-using-vectors/
  
    # vector p1-p2 
    v12 = Vector.segToVec(self) 
  
    # vector p2-p 
    v2p = Vector(p.x - self.p2.x, p.y - self.p2.y) 
  
    # vector p1-p 
    v1p = Vector(p.x - self.p1.x, p.y - self.p1.y) 
  
  
    dot2p = Vector.dot(v12, v2p)
    dot1p = Vector.dot(v12, v1p)
  
    minDist = 0; 
    if (dot2p > 0):
      minDist = p.distance_to(self.p2)
    elif (dot1p < 0):
      minDist = p.distance_to(self.p1)
    else:
      # perpendicular distance
      minDist = Vector.cross(v12, v1p)/self.length()
    return minDist; 


  # check if point p lies on line segment
  def onSegment(self, p): 
    return ( (p.x <= max(self.p1.x, self.p2.x)) and 
             (p.x >= min(self.p1.x, self.p2.x)) and
             (p.y <= max(self.p1.y, self.p2.y)) and 
             (p.y >= min(self.p1.y, self.p2.y)))



  def intersects(self, seg): 
    # does seg intersect with this one?
  
    # Find the 4 orientations required
    d1 = Vector.ccw(self.p1, self.p2, seg.p1) 
    d2 = Vector.ccw(self.p1, self.p2, seg.p2) 
    d3 = Vector.ccw(seg.p1, seg.p2, self.p1) 
    d4 = Vector.ccw(seg.p1, seg.p2, self.p2) 
  
    # General case 
    if ((d1 != d2) and (d3 != d4)): 
        return True
  
    # Special Cases 
    # collinear if seg.p1 lies on this segment
    if ((d1 == 0) and self.onSegment(seg.p1)): 
        return True
  
    # collinear if seg.p2 lies on this segment 
    if ((d2 == 0) and self.onSegment(seg.p2)): 
        return True
  
    # collinear if this p1 lies on other segment 
    if ((d3 == 0) and seg.onSegment(self.p1)): 
        return True
  
    # collinear if this p2 lies on other segment
    if ((d4 == 0) and seg.onSegment(self.p2)): 
        return True
  
    # If none of the cases 
    return False


  def intersectPt(self, seg):
    # where does seg intersect with this segment?

    # Segment AB represented as a1x + b1y = c1
    a1 = self.p2.y - self.p1.y
    b1 = self.p1.x - self.p2.x
    c1 = self.p1.y * b1 + self.p1.x * a1

    # Segment CD represented as a2x + b2y = c2
    a2 = seg.p2.y - seg.p1.y
    b2 = seg.p1.x - seg.p2.x
    c2 = seg.p1.y * b2 + seg.p1.x * a2

    det = a1 * b2 - a2 * b1
    if det != 0:
      x = (b2 * c1 - b1 * c2) / det
      y = (a1 * c2 - a2 * c1) / det
      return Point(x,y)

    return None


  def draw(self, label=None):
    plt.plot(self.xs(), self.ys(), "g")
    self.p1.draw(label=label)
    self.p2.draw()


  @staticmethod
  def isSegment(ln):
    return isinstance(ln, Segment)


  @staticmethod
  def allIntersects(segs):
    pairs = []
    n = len(segs)
    for i in range(n-1):
      for j in range(i+1, n):
        if Segment.intersects(segs[i], segs[j]):
          pairs.append((segs[i], segs[j]))
    return pairs


