from OrderedPair import OrderedPair
from ComplexOrderedPair import ComplexOrderedPair
import math

class Quadratic:
    """f(x) = ax\u00B2 + bx + c  (a \u2260 0).
    Demonstrates PNCICOTGSU class anatomy in Python 3.
    Uses OrderedPair (composition) and ComplexOrderedPair for complex roots."""

    def __init__(self, a=1.0, b=0.0, c=0.0):
        """Default + full constructor via default params."""
        print("...Quadratic constructor...")
        if a == 0:
            raise ValueError("a cannot be zero in a quadratic")
        self._a = a
        self._b = b
        self._c = c
        self._discriminant = 0.0
        self._vertex       = None
        self._compute_discriminant()
        self._compute_vertex()

    @classmethod
    def from_quadratic(cls, orig):
        """Copy constructor."""
        print("...Quadratic copy constructor (classmethod)...")
        return cls(orig._a, orig._b, orig._c)

    # __str__ = Python's toString()
    def __str__(self):
        return (f"f(x) = {self._a}x\u00B2 + {self._b}x + {self._c}"
                f" | vertex: {self._vertex}"
                f" | disc: {self._discriminant}")

    # getters
    def get_a(self):            return self._a
    def get_b(self):            return self._b
    def get_c(self):            return self._c
    def get_discriminant(self): return self._discriminant
    def get_vertex(self):       return self._vertex

    # setters — validate then trigger side-effect recompute
    def set_a(self, a):
        print("...set_a...")
        if a == 0: raise ValueError("a cannot be zero")
        self._a = a
        self._compute_discriminant()
        self._compute_vertex()

    def set_b(self, b):
        print("...set_b...")
        self._b = b
        self._compute_discriminant()
        self._compute_vertex()

    def set_c(self, c):
        print("...set_c...")
        self._c = c
        self._compute_discriminant()
        self._compute_vertex()

    # utility methods
    def f(self, x):
        return self._a * x ** 2 + self._b * x + self._c

    def _compute_discriminant(self):
        print("...compute_discriminant...")
        self._discriminant = self._b ** 2 - 4 * self._a * self._c

    def _compute_vertex(self):
        print("...compute_vertex...")
        h = -self._b / (2 * self._a)
        k = self.f(h)
        self._vertex = OrderedPair(h, k)
        self._vertex.set_label("V")

    def get_roots_description(self):
        d = self._discriminant
        if d > 0:
            r1 = (-self._b + math.sqrt(d)) / (2 * self._a)
            r2 = (-self._b - math.sqrt(d)) / (2 * self._a)
            return f"Two real roots:  x = {r1}  and  x = {r2}"
        elif d == 0:
            r = -self._b / (2 * self._a)
            return f"One repeated root:  x = {r}"
        else:
            rp = -self._b / (2 * self._a)
            ip = math.sqrt(-d) / (2 * self._a)
            root1 = ComplexOrderedPair(rp,  ip)
            root2 = ComplexOrderedPair(rp, -ip)
            return f"Complex roots:  {root1}  and  {root2}"

#end class Quadratic
