from decimal import Decimal, getcontext

from vector import Vector

getcontext().prec = 30


class Line(object):

    NO_NONZERO_ELTS_FOUND_MSG = 'No nonzero elements found'

    def __init__(self, normal_vector=None, constant_term=None):
        self.dimension = 2

        if not normal_vector:
            all_zeros = ['0']*self.dimension
            normal_vector = Vector(all_zeros)
        self.normal_vector = [Decimal(x) for x in normal_vector]

        if not constant_term:
            constant_term = Decimal('0')
        self.constant_term = Decimal(constant_term)

        self.set_basepoint()


    def set_basepoint(self):
        try:
            n = self.normal_vector
            c = self.constant_term
            basepoint_coords = ['0']*self.dimension

            initial_index = Line.first_nonzero_index(n)
            initial_coefficient = n[initial_index]

            basepoint_coords[initial_index] = c/initial_coefficient
            self.basepoint = Vector(*basepoint_coords)

        except Exception as e:
            if str(e) == Line.NO_NONZERO_ELTS_FOUND_MSG:
                self.basepoint = None
            else:
                raise e


    def __str__(self):

        num_decimal_places = 3

        def write_coefficient(coefficient, is_initial_term=False):
            coefficient = round(coefficient, num_decimal_places)
            if coefficient % 1 == 0:
                coefficient = int(coefficient)

            output = ''

            if coefficient < 0:
                output += '-'
            if coefficient > 0 and not is_initial_term:
                output += '+'

            if not is_initial_term:
                output += ' '

            if abs(coefficient) != 1:
                output += '{}'.format(abs(coefficient))

            return output

        n = self.normal_vector

        try:
            initial_index = Line.first_nonzero_index(n)
            terms = [write_coefficient(n[i], is_initial_term=(i==initial_index)) + 'x_{}'.format(i+1)
                     for i in range(self.dimension) if round(n[i], num_decimal_places) != 0]
            output = ' '.join(terms)

        except Exception as e:
            if str(e) == self.NO_NONZERO_ELTS_FOUND_MSG:
                output = '0'
            else:
                raise e

        constant = round(self.constant_term, num_decimal_places)
        if constant % 1 == 0:
            constant = int(constant)
        output += ' = {}'.format(constant)

        return output


    @staticmethod
    def first_nonzero_index(iterable):
        for k, item in enumerate(iterable):
            if not MyDecimal(item).is_near_zero():
                return k
        raise Exception(Line.NO_NONZERO_ELTS_FOUND_MSG)

    def __eq__(self, rhs):
        if not are_parallel(self, rhs):
            return False

        connecting_coordinates = []
        for i in range(self.dimension):
            connecting_coordinates.append(rhs.normal_vector[i] - self.normal_vector[i])
        connecting_vector = Vector(*connecting_coordinates)

        return connecting_vector.is_orthogonal(Vector(*self.normal_vector)) and \
               connecting_vector.is_orthogonal(Vector(*rhs.normal_vector))


def are_parallel(lhs, rhs):
    lhs_normal = Vector(*lhs.normal_vector)
    rhs_normal = Vector(*rhs.normal_vector)
    return lhs_normal.is_parallel(rhs_normal)

def get_intersection(lhs, rhs):
    if lhs == rhs:
        raise ValueError("{} is the same line as {}, no interection.".format(lhs, rhs))
    if are_parallel(lhs, rhs):
        raise ValueError("{} is parallel to {}, no intersection.".format(lhs, rhs))

    A = lhs.normal_vector[0]
    B = lhs.normal_vector[1]
    C = rhs.normal_vector[0]
    D = rhs.normal_vector[1]

    k1 = lhs.constant_term
    k2 = rhs.constant_term

    AD_BC = (A * D) - (B * C)
    x = (D * k1 - B * k2) / AD_BC
    y = (-1 * C * k1 + A*k2) / AD_BC

    return (x, y)

class MyDecimal(Decimal):
    def is_near_zero(self, eps=1e-10):
        return abs(self) < eps
