'''
Created on 20-Jan-2022

@author: bthakur
'''

import random

import inspect
import traceback

import matplotlib.pyplot as plt
from numpy import sin, cos, pi, linspace
import numpy as np
import sympy as sp

from sympy import (latex,
                   Symbol, symbols,
                   Add, Mul,
                   )

from xv.km.ui import SvgHelper
from xv.util.data import CacheUtil

from xv.util import UtilClass, toIntOrFloat

# _triangle_basic_properties
# TriangleBasicProperties

class TriangleBasicProperties(UtilClass):
    
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        pass
    

    def _get_other_vars_in_order(self, arr, element):
        idx = arr.index(element)
        if idx == 0:
            return (arr[1], arr[2])
        
        if idx == 1:
            return (arr[2], arr[0])
        
        return (arr[0], arr[1])
        pass
    
    def get_random_sides(self, is_real = True):
        if not is_real:
            sides = [random.randint(1, 9), random.randint(1, 9), random.randint(1, 9)]
            return sides
            pass
        
        a, b, c = (0, 0, 0)
        while not (
            a > 0 and b > 0 and c > 0 and 
            a + b > c and b + c > a and c + a > b
            ):
            a = random.randint(1, 9)
            b = random.choice([a, random.randint(1, 9)])
            c = random.choice([a, b, random.randint(1, 9)])
            pass
        
        right_angled_abc = list(np.array([3, 4, 5]) * random.randint(1, 9))
        
        sides = random.choice([[a, b, c], right_angled_abc])
        
        #shuffle them
        random.shuffle(sides)
        
        return sides
        pass
    
    def get_random_angles(self, is_real = True):
        A = random.randint(1, 15) * 10
        B = random.randint(1, 15) * 10
        C = abs(180 - A - B)

        if not is_real:
            return [A, B, C]
            pass

        while not (A > 0 and B > 0 and C > 0) or A + B + C != 180:
            A = random.choice([random.randint(1, 15) * 10, 90])
            B = random.randint(1, 15) * 10
            C = abs(180 - A - B)
            pass

        angles = [A, B, C]
        
        #shuffle them
        random.shuffle(angles)
        
        return angles
        pass
    
    def is_a_polygon(self):

        prob_stmt = f"""
        Is a triangle a polygon?
        """
        soln_stmt = f"""
        Yes. It is a polygon with three sides.
        """
        answer_stmt = f"""
        Yes.
        """
        
        result = (prob_stmt.strip(),
                  soln_stmt.strip(),
                  answer_stmt.strip())
        
        return result
        pass

    
    def has_real_sides(self):

        a, b, c = self.get_random_sides(is_real = False)
        
        prob_stmt = f"""
        Is a triangle with sides {a}, {b} and {c} real?
        """
        soln_stmt_part = f"""
        For a real triangle, the sum of any two sides should be greater than the third one. 
        
        $\\displaystyle 
        a + b - c \\quad = \\quad {a} + {b} - {c} \\quad = \\quad {a + b - c} \\\\
        b + c - a \\quad = \\quad {b} + {c} - {a} \\quad = \\quad {b + c - a} \\\\
        c + a - b \\quad = \\quad {c} + {a} - {b} \\quad = \\quad {c + a - b} 
        $
        """

        if ( a + b > c and b + c > a and c + a > b):
            soln_stmt = f"""{soln_stmt_part.strip()}
            Therefore, it is a real triangle.
            """
            answer_stmt = f"""
            Yes.
            """
        else:
            soln_stmt = f"""{soln_stmt_part.strip()}
            Therefore, it is not a real triangle.
            """
            answer_stmt = f"""
            No.
            """
        
        result = (prob_stmt.strip(),
                  soln_stmt.strip(),
                  answer_stmt.strip())
        
        return result
        pass

    def has_real_angles(self):
        A, B, C = self.get_random_angles(is_real = False)

        prob_stmt = f"""
        Is a triangle with angles ${A}^{{\\circ}}$, ${B}^{{\\circ}}$ and ${C}^{{\\circ}}$ real?
        """
        soln_stmt_part = f"""
        For a real triangle, the sum of all angles must be ${{180}}^{{\\circ}}$. 
        
        $\\displaystyle 
        A + B + C
        \\quad = \\quad 
        
        {A}^{{\\circ}} + {B}^{{\\circ}} + {C}^{{\\circ}}
        \\quad = \\quad 
        
        {A + B + C}^{{\\circ}}
        $
        """

        if A + B + C == 180:
            soln_stmt = f"""{soln_stmt_part.strip()}
            
            Therefore, it is a real triangle.
            """
            answer_stmt = f"""
            Yes.
            """
        else:
            soln_stmt = f"""{soln_stmt_part.strip()}
            
            Therefore, it is not a real triangle.
            """
            answer_stmt = f"""
            No.
            """
        
        result = (prob_stmt.strip(),
                  soln_stmt.strip(),
                  answer_stmt.strip())
        
        return result
        pass

    
    def classify_by_angle(self):
        
        A, B, C = self.get_random_angles()

        stmt_pairs = []
        
        prob_stmt = f"""
        Classify the triangle based on its angles $\\displaystyle 
        {A}^{{\\circ}}, {B}^{{\\circ}} \\text{{ and }} {C}^{{\\circ}}
        $.
        """
        
        if A == 90 or B == 90 or C == 90:
            soln_stmt = f"""
            As one of the angles is a right angle (${90}^{{\\circ}}$),
            it is a right-angled triangle.
            """.replace("\n", " ")
            
            answer_stmt = f"""
            right-angled
            """
            stmt_pairs.append([soln_stmt, answer_stmt])
        else:
            soln_stmt = f"""
            As none of the angles of the triangle is a right angle (${90}^{{\\circ}}$),
            it is an oblique triangle.
            """.replace("\n", " ")
            
            answer_stmt = f"""
            oblique
            """
            stmt_pairs.append([soln_stmt, answer_stmt])
             

        if A > 90 or B > 90 or C > 90:
            soln_stmt = f"""
            As one of the angles is an obtuse angle (more than ${90}^{{\\circ}}$),
            it is an obtuse triangle.
            """.replace("\n", " ")
            
            answer_stmt = f"""
            obtuse
            """
            stmt_pairs.append([soln_stmt, answer_stmt])
            
        if A < 90 and B < 90 and C < 90:
            soln_stmt = f"""
            As all angles are acute angles (less than ${90}^{{\\circ}}$),
            it is an acute triangle.
            """.replace("\n", " ")
            
            answer_stmt = f"""
            acute
            """
            stmt_pairs.append([soln_stmt, answer_stmt])

        if len(set([A, B, C])) == 1:
            soln_stmt = f"""
            As all angles of the triangle are equal,
            it is an equilateral triangle.
            """.replace("\n", " ")
            
            answer_stmt = f"""
            equilateral
            """
            stmt_pairs.append([soln_stmt, answer_stmt])

        
        if len(set([A, B, C])) == 2:
            soln_stmt = f"""
            As two angles of the triangle are equal,
            it is an isoceles triangle.
            """.replace("\n", " ")
            
            answer_stmt = f"""
            isoceles
            """
            stmt_pairs.append([soln_stmt, answer_stmt])

        if len(set([A, B, C])) == 3:
            soln_stmt = f"""
            As all angles of the triangle are different from each other,
            it is a scalene triangle.
            """.replace("\n", " ")
            
            answer_stmt = f"""
            scalene
            """
            stmt_pairs.append([soln_stmt, answer_stmt])

 
        soln_stmt = "\n".join([x[0].strip() for x in stmt_pairs])

        soln_stmt = f"""
        Angles of the triangle are $\\displaystyle 
        {A}^{{\\circ}}, {B}^{{\\circ}} \\text{{ and }} {C}^{{\\circ}}
        $.
        
        {soln_stmt.strip()}
        """
        answer_stmt = ", ".join([x[1].strip() for x in stmt_pairs])
        
        result = (prob_stmt.strip(),
                  soln_stmt.strip(),
                  answer_stmt.strip())
        
        return result
        pass

    
    def classify_by_sides(self):
        
        a, b, c = self.get_random_sides()

        stmt_pairs = []
        
        prob_stmt = f"""
        Classify the triangle based on its side lengths $\\displaystyle 
        {a}, {b} \\text{{ and }} {c}$.
        """
        
        a1, b1, c1 = list(sorted([a, b, c]))
        
        if a1 ** 2 + b1 ** 2 == c1 ** 2:
            soln_stmt = f"""
            A right-angled triangle follows Pythagorean theorem.
            The sum of squares of the two smaller sides is equal to the square of the third side.
            $\\displaystyle 
            {a1}^2 + {b1}^2
            
            \\quad = \\quad 
            {a1 ** 2} + {b1 ** 2}
            
            \\quad = \\quad 
            {a1 **2 + b1 **2}
            
            \\quad = \\quad 
            {c1}^2
            $
            Therefore, it is a right-angled triangle.
            """
            
            answer_stmt = f"""
            right-angled
            """
            stmt_pairs.append([soln_stmt, answer_stmt])
             

        if len(set([a, b, c])) == 1:
            soln_stmt = f"""
            As all sides of the triangle have equal lengths,
            it is an equilateral triangle.
            """.replace("\n", " ")
            
            answer_stmt = f"""
            equilateral
            """
            stmt_pairs.append([soln_stmt, answer_stmt])

        
        if len(set([a, b, c])) == 2:
            soln_stmt = f"""
            As two sides of the triangle have equal lengths,
            it is an isoceles triangle.
            """.replace("\n", " ")
            
            answer_stmt = f"""
            isoceles
            """
            stmt_pairs.append([soln_stmt, answer_stmt])

        if len(set([a, b, c])) == 3:
            soln_stmt = f"""
            As all sides of the triangle have different lengths from each other,
            it is a scalene triangle.
            """.replace("\n", " ")
            
            answer_stmt = f"""
            scalene
            """
            stmt_pairs.append([soln_stmt, answer_stmt])

 
        soln_stmt = "\n\n".join([x[0].strip() for x in stmt_pairs])

        soln_stmt = f"""
        Side lengths of the triangle are $\\displaystyle 
        {a}, {b} \\text{{ and }} {c}$.
        
        {soln_stmt.strip()}
        """
        answer_stmt = ", ".join([x[1].strip() for x in stmt_pairs])
        
        result = (prob_stmt.strip(),
                  soln_stmt.strip(),
                  answer_stmt.strip())
        
        return result
        pass


    def get_type_of_triangle_by_sides(self, triangle):
        side_lengths = [toIntOrFloat(float(side.length), 2) for side in triangle.sides]
        
        side_lengths_latex = [latex(x) for x in side_lengths]
        side_lengths_latex = ", ".join(side_lengths_latex)
        
        prob_stmt = f"""
        Classify the type of the triangle based on sides.
        """
        
        side_lengths_set = set(side_lengths)
        
        if len(side_lengths_set) == 1:
            triangle_type = "equilateral"
            triangle_desc = f"All sides are equal. Therefore, it is an {triangle_type} triangle."
        elif len(side_lengths_set) == 2:
            triangle_type = "isosceles"
            triangle_desc = f"Two sides are equal. Therefore, it is an {triangle_type} triangle."
        else:
            triangle_type = "Scalene"
            triangle_desc = f""" None of sides are equal to one another. 
            Therefore, it is an {triangle_type} triangle.
            """.replace("\n", " ")
        

        soln_stmt = f"""
        The sides of the triangle are: $\\displaystyle 
        \\text{{perimeter }} = {side_lengths_latex}
        $. 
        {triangle_desc}
        """


        answer_stmt = f"""
        {triangle_type}
        """

        result = (prob_stmt.strip(),
                  soln_stmt.strip(),
                  answer_stmt.strip())
        
        return result
        pass
    

    def get_perimeter_calc(self, triangle):
        side_lengths = [toIntOrFloat(float(side.length), 2) for side in triangle.sides]
        p_val = toIntOrFloat(sum(side_lengths), 2)
        
        side_lengths_latex = [latex(x) for x in side_lengths]
        side_lengths_latex = " + ".join(side_lengths_latex)
        
        prob_stmt = f"""
        Find perimeter of the triangle.
        """


        soln_stmt = f"""
        $\\displaystyle 
        \\text{{perimeter }} = {side_lengths_latex} = {latex(p_val)}
        $
        """


        answer_stmt = f"""
        {p_val}
        """

        result = (prob_stmt.strip(),
                  soln_stmt.strip(),
                  answer_stmt.strip())
        
        return result
        pass
    
    
    def get_area_calc_by_sides(self, triangle):
        side_lengths = [toIntOrFloat(float(side.length), 2) for side in triangle.sides]
        
        side_lengths_latex = [latex(x) for x in side_lengths]
        side_lengths_latex = " + ".join(side_lengths_latex)
        
        s = Symbol('s')
        area = Symbol('area')
        
        a, b, c = symbols('a, b, c')

        p_val = toIntOrFloat(sum(side_lengths), 2)
        s_val = toIntOrFloat(p_val / 2, 2)

        a_val, b_val, c_val = side_lengths
        
        a_val_sqr = s_val * (s_val - a_val) * (s_val - b_val) * (s_val - c_val)
        a_val_sqr = toIntOrFloat(a_val_sqr, 2)
        
        area_val = toIntOrFloat(a_val_sqr ** .5, 2)
        
        abc_val_str =  f"""We have $\\displaystyle 
        {latex(a)} = {latex(a_val)} \\\\
        {latex(b)} = {latex(b_val)} \\\\ 
        {latex(c)} = {latex(c_val)} 
        $."""
 
        prob_stmt = f"""
        Find area of the triangle.
        """

        soln_stmt = f"""
        {abc_val_str}
        
        $\\displaystyle 
        \\text{{perimeter }} = {side_lengths_latex} = {latex(p_val)}
        $
        
        $\\displaystyle 
        {latex(s)} = \\text{{ half perimeter }} 
        = \\frac{{ {latex(p_val)} }} {{ 2 }}
        = {latex(s_val)}
        $
        
        $\\displaystyle
        \\begin{{align}}

        {latex(area)} 
        & = \\sqrt{{ 
            {latex(s)} 
            \\left( {latex(s)} - {latex(a)} \\right)  
            \\left( {latex(s)} - {latex(b)} \\right)  
            \\left( {latex(s)} - {latex(c)} \\right)  
          }}
        \\\\ \\\\
        & = \\sqrt{{ 
            {latex(s_val)} 
            \\left( {latex(s_val)} - {latex(a_val)} \\right)  
            \\left( {latex(s_val)} - {latex(b_val)} \\right)  
            \\left( {latex(s_val)} - {latex(c_val)} \\right)  
          }}
        
        \\\\ \\\\
        & = \\sqrt{{ 
            {latex(s_val)} 
            \\cdot {latex(s_val - a_val)} 
            \\cdot {latex(s_val - b_val)} 
            \\cdot {latex(s_val - c_val)}
          }}
        
        \\\\ \\\\
        & = \\sqrt{{ 
            {latex(a_val_sqr)} 
          }}
        \\qquad \\text{{ use calculator }}
        
        \\\\ \\\\
        & = {latex(area_val)}
        \\qquad \\text{{ use calculator }}
        
        \\end{{align}} 
        $
        
        """

        answer_stmt = f"""
        {area_val}
        """

        result = (prob_stmt.strip(),
                  soln_stmt.strip(),
                  answer_stmt.strip())
        
        return result
        pass


    def get_circumradius_calc_by_sides(self, triangle, show_sub_cal = True):
        side_lengths = [toIntOrFloat(float(side.length), 2) for side in triangle.sides]
        
        side_lengths_latex = [latex(x) for x in side_lengths]
        side_lengths_latex = " + ".join(side_lengths_latex)
        
        s = Symbol('s')
        area = Symbol('area')
        
        a, b, c = symbols('a, b, c')

        p_val = toIntOrFloat(sum(side_lengths), 2)
        s_val = toIntOrFloat(p_val / 2, 2)

        a_val, b_val, c_val = side_lengths
        
        a_val_sqr = s_val * (s_val - a_val) * (s_val - b_val) * (s_val - c_val)
        a_val_sqr = toIntOrFloat(a_val_sqr, 2)
        
        area_val = toIntOrFloat(a_val_sqr ** .5, 2)
        
        abc_val_str =  f"""We have $\\displaystyle 
        {latex(a)} = {latex(a_val)} \\\\
        {latex(b)} = {latex(b_val)} \\\\ 
        {latex(c)} = {latex(c_val)} 
        $."""
 
        prob_stmt = f"""
        Find the radius of the circumcircle (circle passing through all vertices) of the triangle.
        """

        soln_stmt_items = []
        
        if show_sub_cal:
            soln_stmt_item = f"""
            {abc_val_str}
            
            $\\displaystyle 
            \\text{{perimeter }} = {side_lengths_latex} = {latex(p_val)}
            $
            
            $\\displaystyle 
            {latex(s)} = \\text{{ half perimeter }} 
            = \\frac{{ {latex(p_val)} }} {{ 2 }}
            = {latex(s_val)}
            $
            
            $\\displaystyle
            \\begin{{align}}
    
            {latex(area)} 
            & = \\sqrt{{ 
                {latex(s)} 
                \\left( {latex(s)} - {latex(a)} \\right)  
                \\left( {latex(s)} - {latex(b)} \\right)  
                \\left( {latex(s)} - {latex(c)} \\right)  
              }}
            \\\\ \\\\
            & = \\sqrt{{ 
                {latex(s_val)} 
                \\left( {latex(s_val)} - {latex(a_val)} \\right)  
                \\left( {latex(s_val)} - {latex(b_val)} \\right)  
                \\left( {latex(s_val)} - {latex(c_val)} \\right)  
              }}
            
            \\\\ \\\\
            & = \\sqrt{{ 
                {latex(s_val)} 
                \\cdot {latex(s_val - a_val)} 
                \\cdot {latex(s_val - b_val)} 
                \\cdot {latex(s_val - c_val)}
              }}
            
            \\\\ \\\\
            & = \\sqrt{{ 
                {latex(a_val_sqr)} 
              }}
            \\qquad \\text{{ use calculator }}
            
            \\\\ \\\\
            & = {latex(area_val)}
            \\qquad \\text{{ use calculator }}
            
            
            \\end{{align}} 
            $
            
            """
            soln_stmt_items.append(soln_stmt_item.strip())
        else:
            soln_stmt_item = f"""
            {abc_val_str}
            
            $\\displaystyle
            {latex(area)} = {latex(area_val)}
            $
            """
            soln_stmt_items.append(soln_stmt_item.strip())
            

        R = Symbol('R')
        R_val = (a_val * b_val * c_val) / (4 * area_val)
        R_val = toIntOrFloat(R_val)

        soln_stmt_item = f"""
        The radius of the circumcircle
        
        $\\displaystyle
        \\begin{{align}}
        
        {latex(R)} 
        & = \\frac{{ {latex(a * b * c)} }} {{ 4 \\cdot {latex(area )} }} 
        \\\\ \\\\
        
        & = \\frac{{ {latex(a_val)} \\cdot {latex(b_val)} \\cdot {latex(c_val)} }} 
        {{ 4 \\cdot {latex(area_val)} }} 
        \\\\ \\\\
        
        & = {latex(R_val)}
        
        \\end{{align}} 
        $
        
        """
        soln_stmt_items.append(soln_stmt_item.strip())



        answer_stmt = f"""
        {R_val}
        """
        
        soln_stmt = "\n\n".join(soln_stmt_items)

        result = (prob_stmt.strip(),
                  soln_stmt.strip(),
                  answer_stmt.strip())
        
        return result
        pass
    
    def get_inradius_calc_by_sides(self, triangle, show_sub_cal = True):
        side_lengths = [toIntOrFloat(float(side.length), 2) for side in triangle.sides]
        
        side_lengths_latex = [latex(x) for x in side_lengths]
        side_lengths_latex = " + ".join(side_lengths_latex)
        
        s = Symbol('s')
        area = Symbol('area')
        
        a, b, c = symbols('a, b, c')

        p_val = toIntOrFloat(sum(side_lengths), 2)
        s_val = toIntOrFloat(p_val / 2, 2)

        a_val, b_val, c_val = side_lengths
        
        a_val_sqr = s_val * (s_val - a_val) * (s_val - b_val) * (s_val - c_val)
        a_val_sqr = toIntOrFloat(a_val_sqr, 2)
        
        area_val = toIntOrFloat(a_val_sqr ** .5, 2)
        
        abc_val_str =  f"""We have $\\displaystyle 
        {latex(a)} = {latex(a_val)} \\\\
        {latex(b)} = {latex(b_val)} \\\\ 
        {latex(c)} = {latex(c_val)} 
        $."""
 
        prob_stmt = f"""
        Find the radius of the incircle (circle inscribed within the triangle).
        """

        soln_stmt_items = []
        
        if show_sub_cal:
            soln_stmt_item = f"""
            {abc_val_str}
            
            $\\displaystyle 
            \\text{{perimeter }} = {side_lengths_latex} = {latex(p_val)}
            $
            
            $\\displaystyle 
            {latex(s)} = \\text{{ half perimeter }} 
            = \\frac{{ {latex(p_val)} }} {{ 2 }}
            = {latex(s_val)}
            $
            
            $\\displaystyle
            \\begin{{align}}
    
            {latex(area)} 
            & = \\sqrt{{ 
                {latex(s)} 
                \\left( {latex(s)} - {latex(a)} \\right)  
                \\left( {latex(s)} - {latex(b)} \\right)  
                \\left( {latex(s)} - {latex(c)} \\right)  
              }}
            \\\\ \\\\
            & = \\sqrt{{ 
                {latex(s_val)} 
                \\left( {latex(s_val)} - {latex(a_val)} \\right)  
                \\left( {latex(s_val)} - {latex(b_val)} \\right)  
                \\left( {latex(s_val)} - {latex(c_val)} \\right)  
              }}
            
            \\\\ \\\\
            & = \\sqrt{{ 
                {latex(s_val)} 
                \\cdot {latex(s_val - a_val)} 
                \\cdot {latex(s_val - b_val)} 
                \\cdot {latex(s_val - c_val)}
              }}
            
            \\\\ \\\\
            & = \\sqrt{{ 
                {latex(a_val_sqr)} 
              }}
            \\qquad \\text{{ use calculator }}
            
            \\\\ \\\\
            & = {latex(area_val)}
            \\qquad \\text{{ use calculator }}
            
            
            \\end{{align}} 
            $
            
            """
            soln_stmt_items.append(soln_stmt_item.strip())
        else:
            soln_stmt_item = f"""
            {abc_val_str}
            
            $\\displaystyle
            {latex(area)} = {latex(area_val)}
            $
            
            $\\displaystyle
            {latex(s)} = {latex(s_val)}
            $
            """
            soln_stmt_items.append(soln_stmt_item.strip())
            

        r = Symbol('r')
        r_val = toIntOrFloat(area_val / s_val)

        soln_stmt_item = f"""
        The radius of the circumcircle
        
        $\\displaystyle
        \\begin{{align}}
        
        {latex(r)} 
        & = \\frac{{ {latex(area)} }} {{ {latex(s)} }} 
        \\\\ \\\\
        
        & = \\frac{{ {latex(area_val)} }} {{ {latex(s_val)} }} 
        \\\\ \\\\
        
        & = {latex(r_val)}
        
        \\end{{align}} 
        $
        
        """
        soln_stmt_items.append(soln_stmt_item.strip())



        answer_stmt = f"""
        {r_val}
        """
        
        soln_stmt = "\n\n".join(soln_stmt_items)

        result = (prob_stmt.strip(),
                  soln_stmt.strip(),
                  answer_stmt.strip())
        
        return result
        pass
    

    def get_radii_excircles_calc_by_sides(self, triangle, show_sub_cal = True):
        side_lengths = [toIntOrFloat(float(side.length), 2) for side in triangle.sides]
        
        side_lengths_latex = [latex(x) for x in side_lengths]
        side_lengths_latex = " + ".join(side_lengths_latex)
        
        s = Symbol('s')
        area = Symbol('area')
        
        a, b, c = symbols('a, b, c')

        p_val = toIntOrFloat(sum(side_lengths), 2)
        s_val = toIntOrFloat(p_val / 2, 2)

        a_val, b_val, c_val = side_lengths
        
        a_val_sqr = s_val * (s_val - a_val) * (s_val - b_val) * (s_val - c_val)
        a_val_sqr = toIntOrFloat(a_val_sqr, 2)
        
        area_val = toIntOrFloat(a_val_sqr ** .5, 2)
        
        abc_val_str =  f"""We have $\\displaystyle 
        {latex(a)} = {latex(a_val)} \\\\
        {latex(b)} = {latex(b_val)} \\\\ 
        {latex(c)} = {latex(c_val)} 
        $."""
 
        prob_stmt = f"""
        Find the radii of the excircles opposite each vertex.
        """

        soln_stmt_items = []
        
        if show_sub_cal:
            soln_stmt_item = f"""
            {abc_val_str}
            
            $\\displaystyle 
            \\text{{perimeter }} = {side_lengths_latex} = {latex(p_val)}
            $
            
            $\\displaystyle 
            {latex(s)} = \\text{{ half perimeter }} 
            = \\frac{{ {latex(p_val)} }} {{ 2 }}
            = {latex(s_val)}
            $
            
            $\\displaystyle
            \\begin{{align}}
    
            {latex(area)} 
            & = \\sqrt{{ 
                {latex(s)} 
                \\left( {latex(s)} - {latex(a)} \\right)  
                \\left( {latex(s)} - {latex(b)} \\right)  
                \\left( {latex(s)} - {latex(c)} \\right)  
              }}
            \\\\ \\\\
            & = \\sqrt{{ 
                {latex(s_val)} 
                \\left( {latex(s_val)} - {latex(a_val)} \\right)  
                \\left( {latex(s_val)} - {latex(b_val)} \\right)  
                \\left( {latex(s_val)} - {latex(c_val)} \\right)  
              }}
            
            \\\\ \\\\
            & = \\sqrt{{ 
                {latex(s_val)} 
                \\cdot {latex(s_val - a_val)} 
                \\cdot {latex(s_val - b_val)} 
                \\cdot {latex(s_val - c_val)}
              }}
            
            \\\\ \\\\
            & = \\sqrt{{ 
                {latex(a_val_sqr)} 
              }}
            \\qquad \\text{{ use calculator }}
            
            \\\\ \\\\
            & = {latex(area_val)}
            \\qquad \\text{{ use calculator }}
            
            
            \\end{{align}} 
            $
            
            """
            soln_stmt_items.append(soln_stmt_item.strip())
        else:
            soln_stmt_item = f"""
            {abc_val_str}
            
            $\\displaystyle
            {latex(area)} = {latex(area_val)}
            $
            
            $\\displaystyle
            {latex(s)} = {latex(s_val)}
            $
            """
            soln_stmt_items.append(soln_stmt_item.strip())
            
        r = Symbol("r")
        
        soln_stmt_item = f"""
        We know
        
        $\\displaystyle
        \\begin{{align}}
        
        {latex(r)} 
        & = \\frac{{ {latex(area)} }} {{ {latex(s)} - \\text{{side}} }} 
        \\\\ \\\\
        
        \\end{{align}} 
        $
        
        """
        soln_stmt_items.append(soln_stmt_item.strip())


        soln_stmt_item = f"""
        Now, we can calculate respective excircle radius for each side:
        """
        soln_stmt_items.append(soln_stmt_item.strip())

        r_vals = []
        for idx, side_length in enumerate(side_lengths):
            r_symb = Symbol(f"{latex(r)}_{{ {idx + 1} }}")
            
            r_val = toIntOrFloat(area_val / (s_val - side_length))
            r_vals.append(r_val)
            
            soln_stmt_item = f"""
            $\\displaystyle
            \\begin{{align}}
            
            {latex(r_symb)} 
            & = \\frac{{ {latex(area_val)} }} {{ {latex(s_val)} - {latex(side_length)} }} 
            \\\\ \\\\
            
            & = {latex(r_val)}
            
            
            \\end{{align}} 
            $
            
            """
            soln_stmt_items.append(soln_stmt_item.strip())
    
            pass
            

        answer_stmt = f"""
        {r_vals}
        """
        
        soln_stmt = "\n\n".join(soln_stmt_items)

        result = (prob_stmt.strip(),
                  soln_stmt.strip(),
                  answer_stmt.strip())
        
        return result
        pass
    
    
    def get_altitudes_calc_by_sides(self, triangle, show_sub_cal = True):
        side_lengths = [toIntOrFloat(float(side.length), 2) for side in triangle.sides]
        
        side_lengths_latex = [latex(x) for x in side_lengths]
        side_lengths_latex = " + ".join(side_lengths_latex)
        
        s = Symbol('s')
        area = Symbol('area')
        
        a, b, c = symbols('a, b, c')

        p_val = toIntOrFloat(sum(side_lengths), 2)
        s_val = toIntOrFloat(p_val / 2, 2)

        a_val, b_val, c_val = side_lengths
        
        a_val_sqr = s_val * (s_val - a_val) * (s_val - b_val) * (s_val - c_val)
        a_val_sqr = toIntOrFloat(a_val_sqr, 2)
        
        area_val = toIntOrFloat(a_val_sqr ** .5, 2)
        
        abc_val_str =  f"""We have $\\displaystyle 
        {latex(a)} = {latex(a_val)} \\\\
        {latex(b)} = {latex(b_val)} \\\\ 
        {latex(c)} = {latex(c_val)} 
        $."""
 
        prob_stmt = f"""
        Find the altitudes of the the triangle from each vertex.
        """

        soln_stmt_items = []
        
        if show_sub_cal:
            soln_stmt_item = f"""
            {abc_val_str}
            
            $\\displaystyle 
            \\text{{perimeter }} = {side_lengths_latex} = {latex(p_val)}
            $
            
            $\\displaystyle 
            {latex(s)} = \\text{{ half perimeter }} 
            = \\frac{{ {latex(p_val)} }} {{ 2 }}
            = {latex(s_val)}
            $
            
            $\\displaystyle
            \\begin{{align}}
    
            {latex(area)} 
            & = \\sqrt{{ 
                {latex(s)} 
                \\left( {latex(s)} - {latex(a)} \\right)  
                \\left( {latex(s)} - {latex(b)} \\right)  
                \\left( {latex(s)} - {latex(c)} \\right)  
              }}
            \\\\ \\\\
            & = \\sqrt{{ 
                {latex(s_val)} 
                \\left( {latex(s_val)} - {latex(a_val)} \\right)  
                \\left( {latex(s_val)} - {latex(b_val)} \\right)  
                \\left( {latex(s_val)} - {latex(c_val)} \\right)  
              }}
            
            \\\\ \\\\
            & = \\sqrt{{ 
                {latex(s_val)} 
                \\cdot {latex(s_val - a_val)} 
                \\cdot {latex(s_val - b_val)} 
                \\cdot {latex(s_val - c_val)}
              }}
            
            \\\\ \\\\
            & = \\sqrt{{ 
                {latex(a_val_sqr)} 
              }}
            \\qquad \\text{{ use calculator }}
            
            \\\\ \\\\
            & = {latex(area_val)}
            \\qquad \\text{{ use calculator }}
            
            
            \\end{{align}} 
            $
            
            """
            soln_stmt_items.append(soln_stmt_item.strip())
        else:
            soln_stmt_item = f"""
            {abc_val_str}
            
            $\\displaystyle
            {latex(area)} = {latex(area_val)}
            $
            
            $\\displaystyle
            {latex(s)} = {latex(s_val)}
            $
            """
            soln_stmt_items.append(soln_stmt_item.strip())
            
        
        
        soln_stmt_item = f"""
        We know
        
        $\\displaystyle
        \\begin{{align}}
        
        {latex(area)} 
        & = {latex(sp.Rational(1, 2))} \\cdot \\text{{base}} \\cdot \\text{{altitude}}
        \\\\ \\\\
        
        \\implies
        \\text{{altitude}}
        & = 2 \\cdot \\frac{{ {latex(area)} }} {{ \\text{{base}} }} 
        \\\\ \\\\
        
        & = 2 \\cdot \\frac{{ {latex(area_val)} }} {{ \\text{{base}} }} 
        \\\\ \\\\
        
        & = \\frac{{ {latex(2 * area_val)} }} {{ \\text{{base}} }} 
        
        \\end{{align}} 
        $
        
        """
        soln_stmt_items.append(soln_stmt_item.strip())


        soln_stmt_item = f"""
        Now, treating each side as base, we can calculate respective altitudes.
        """
        soln_stmt_items.append(soln_stmt_item.strip())

        altitude_vals = []
        for idx, side_length in enumerate(side_lengths):
            altitude_symb = Symbol(f"altitude_{{ {idx + 1} }}")
            
            altitude_val = toIntOrFloat(2 * area_val / side_length)
            altitude_vals.append(altitude_val)
            
            soln_stmt_item = f"""
            $\\displaystyle
            \\begin{{align}}
            
            {latex(altitude_symb)} 
            & = \\frac{{ {latex(2 * area_val)} }} {{ {latex(side_length)} }} 
            \\\\ \\\\
            
            & = {latex(altitude_val)}
            
            
            \\end{{align}} 
            $
            
            """
            soln_stmt_items.append(soln_stmt_item.strip())
    
            pass
            

        answer_stmt = f"""
        {altitude_vals}
        """
        
        soln_stmt = "\n\n".join(soln_stmt_items)

        result = (prob_stmt.strip(),
                  soln_stmt.strip(),
                  answer_stmt.strip())
        
        return result
        pass
    
    def get_angle_calc_by_sides(self, triangle):
        side_lengths = [toIntOrFloat(float(side.length), 2) for side in triangle.sides]
        
        side_lengths_latex = [latex(x) for x in side_lengths]
        side_lengths_latex = " + ".join(side_lengths_latex)
        
        s = Symbol('s')
        area = Symbol('area')
        
        angle_symbs = symbols('A, B, C')
        A, B, C = angle_symbs
        
        angle_symb = random.choice(angle_symbs)
        oth_angles = self._get_other_vars_in_order(angle_symbs, angle_symb)
        angle_idx = angle_symbs.index(angle_symb)
        
        side_symbs = symbols('a, b, c')
        a, b, c = side_symbs
        side_symb = side_symbs[angle_idx]
        side_length = side_lengths[angle_idx]
        
        oth_sides = self._get_other_vars_in_order(side_symbs, side_symb)
        oth_side_lengths = self._get_other_vars_in_order(side_lengths, side_length)
        

        a_val, b_val, c_val = side_lengths
        
        
        cos_angle_symb = Symbol(f"""
          \\frac{{ 
                  {{ {latex(oth_sides[0])} }}^2 
                + {{ {latex(oth_sides[1])} }}^2 
                - {{ {latex(side_symb)} }}^2 
              }}
          {{ 
              2 {latex(oth_sides[0])} {latex(oth_sides[1])} 
          }}        
        """)
        
        cos_angle_val_ueval = Symbol(f"""
          \\frac{{ 
                  {{ {latex(oth_side_lengths[0])} }}^2 
                + {{ {latex(oth_side_lengths[1])} }}^2 
                - {{ {latex(side_length)} }}^2 
              }}
          {{ 
              2 \\cdot {latex(oth_side_lengths[0])} \\cdot {latex(oth_side_lengths[1])} 
          }}        
        """)
        
        numr = oth_side_lengths[0] ** 2 + oth_side_lengths[1] ** 2 - side_length ** 2 
        denr = 2 * oth_side_lengths[0] * oth_side_lengths[1] * side_length
         
        cos_angle_val_rational = Symbol(f"""
        \\frac{{ {toIntOrFloat(numr, 2)} }}{{ {toIntOrFloat(denr, 2)} }}
        """)
        
        cos_angle_val = toIntOrFloat(numr / denr, 2)
        angle_val_rad = toIntOrFloat(float(sp.acos(cos_angle_val)), 2)
        
        angle_val = toIntOrFloat(np.degrees(angle_val_rad), 2)

        degree_180_latex = f"180^{{\\circ}}"


        abc_val_str =  f"""We have $\\displaystyle 
        {latex(a)} = {latex(a_val)} \\\\
        {latex(b)} = {latex(b_val)} \\\\ 
        {latex(c)} = {latex(c_val)} 
        $."""
 
 
        prob_stmt = f"""
        Find angle ${latex(angle_symb)}$ of the triangle.
        """
        
        soln_stmt = f"""
        {abc_val_str}
        
        $\\displaystyle 
        \\vec{{ {latex(side_symb)} }} 
        = \\vec{{ {latex(oth_sides[0])} }} 
        + \\vec{{ {latex(oth_sides[1])} }} 
        $
        
        $\\displaystyle \\implies
        {{ {latex(side_symb)} }}^2
        = {{ {latex(oth_sides[0])} }}^2 
        + {{ {latex(oth_sides[1])} }}^2 
        - 2 {latex(oth_sides[0])} {latex(oth_sides[1])} 
          \\cdot {latex(sp.cos(angle_symb))}
        $
        
        $\\displaystyle \\implies
        {latex(sp.cos(angle_symb))}
        = {latex(cos_angle_symb)}
        $
        
        $\\displaystyle
        \\begin{{align}}

        \\implies {latex(angle_symb)}
        & = \\text{{cos}}^{{-1}} \\left({latex(cos_angle_symb)}\\right)
        \\\\ \\\\
        
        
        & = \\text{{cos}}^{{-1}} \\left({latex(cos_angle_val_ueval)}\\right)
        \\\\ \\\\
        
        
        & = \\text{{cos}}^{{-1}} \\left({latex(cos_angle_val_rational)}\\right)
        \\qquad \\text{{ use calculator }}
        \\\\ \\\\
        
        
        & = \\text{{cos}}^{{-1}} \\left({latex(cos_angle_val)}\\right)
        \\\\ \\\\
        
        
        & = {latex(angle_val_rad)}
        \\qquad \\text{{ use calculator }}
        \\\\ \\\\
        
        & = {latex(angle_val_rad)}
        \\cdot \\frac{{ {degree_180_latex} }}{{ {latex(np.pi)} }}
        \\qquad \\text{{ as }} {latex(sp.pi)} = {latex(np.pi)} = {degree_180_latex}
        \\\\ \\\\
        
        & = {latex(angle_val)} ^{{ \\circ }}
        \\qquad \\text{{ use calculator }}
        
        \\end{{align}} 
        $
        
        """

        answer_stmt = f"""
        ${latex(angle_val)} ^{{ \\circ }}$
        """

        result = (prob_stmt.strip(),
                  soln_stmt.strip(),
                  answer_stmt.strip())
        
        return result
        pass
    
    
    def get_median_calc_by_sides(self, triangle):
        side_lengths = [toIntOrFloat(float(side.length), 2) for side in triangle.sides]
        
        side_lengths_latex = [latex(x) for x in side_lengths]
        side_lengths_latex = " + ".join(side_lengths_latex)
        
        
        s = Symbol('s')
        median = Symbol('m')
        
        angle_symbs = symbols('A, B, C')
        A, B, C = angle_symbs
        
        angle_symb = random.choice(angle_symbs)
        oth_angles = self._get_other_vars_in_order(angle_symbs, angle_symb)
        angle_idx = angle_symbs.index(angle_symb)
        
        side_symbs = symbols('a, b, c')
        a, b, c = side_symbs
        side_symb = side_symbs[angle_idx]
        side_length = side_lengths[angle_idx]
        
        oth_sides = self._get_other_vars_in_order(side_symbs, side_symb)
        oth_side_lengths = self._get_other_vars_in_order(side_lengths, side_length)
        

        a_val, b_val, c_val = side_lengths

        median_val_sqr = 2 * oth_side_lengths[0] ** 2 + 2 * oth_side_lengths[1] ** 2 - side_length ** 2
        median_val_sqr = toIntOrFloat(float(median_val_sqr), 2)
        
        median_val = toIntOrFloat(median_val_sqr ** .5, 2)
        

        abc_val_str =  f"""We have $\\displaystyle 
        {latex(a)} = {latex(a_val)} \\\\
        {latex(b)} = {latex(b_val)} \\\\ 
        {latex(c)} = {latex(c_val)} 
        $."""
 
 
        prob_stmt = f"""
        Find length of median on side ${latex(side_symb)}$ of the triangle.
        """
        
        soln_stmt = f"""
        {abc_val_str}
        
        Median on side ${latex(side_symb)}$ from ${latex(angle_symb)}$
        
        $\\displaystyle
        \\begin{{align}}
        
        

        {latex(median)}_{{ {latex(side_symb)} }}
        & = \\sqrt {{
        2 {latex(oth_sides[0])}^2 + 2 {latex(oth_sides[1])}^2 - {latex(side_symb)}^2
        }}
        \\\\ \\\\
        
        & = \\sqrt {{
        2 \\cdot {latex(oth_side_lengths[0])}^2 
        + 2 \\cdot {latex(oth_side_lengths[1])}^2 
        - {latex(side_length)}^2
        }}
        \\\\ \\\\
        
        
        & = \\sqrt {{ {latex(median_val_sqr)} }}
        \\qquad \\text{{ use calculator }}
        \\\\ \\\\
        
        
        & = {latex(median_val)}
        \\qquad \\text{{ use calculator }}
        
        
        \\end{{align}} 
        $
        
        """

        answer_stmt = f"""
        ${latex(median_val)}$
        """

        result = (prob_stmt.strip(),
                  soln_stmt.strip(),
                  answer_stmt.strip())
        
        return result
        pass
    
    
    def get_angle_bisector_calc_by_sides(self, triangle):
        '''
        length_internal_angle_bisector
        '''
        
        
        side_lengths = [toIntOrFloat(float(side.length), 2) for side in triangle.sides]
        
        side_lengths_latex = [latex(x) for x in side_lengths]
        side_lengths_latex = " + ".join(side_lengths_latex)
        
        
        bisector = Symbol('w')
        
        angle_symbs = symbols('A, B, C')
        A, B, C = angle_symbs
        
        angle_symb = random.choice(angle_symbs)
        oth_angles = self._get_other_vars_in_order(angle_symbs, angle_symb)
        angle_idx = angle_symbs.index(angle_symb)
        
        side_symbs = symbols('a, b, c')
        a, b, c = side_symbs
        side_symb = side_symbs[angle_idx]
        side_length = side_lengths[angle_idx]
        
        oth_sides = self._get_other_vars_in_order(side_symbs, side_symb)
        oth_side_lengths = self._get_other_vars_in_order(side_lengths, side_length)
        

        a_val, b_val, c_val = side_lengths

        median_val_sqr = 2 * oth_side_lengths[0] ** 2 + 2 * oth_side_lengths[1] ** 2 - side_length ** 2
        median_val_sqr = toIntOrFloat(float(median_val_sqr), 2)
        
        median_val = toIntOrFloat(median_val_sqr ** .5, 2)
        
        
        abc_val_str =  f"""We have $\\displaystyle 
        {latex(a)} = {latex(a_val)} \\\\
        {latex(b)} = {latex(b_val)} \\\\ 
        {latex(c)} = {latex(c_val)} 
        $."""
 
 
        prob_stmt = f"""
        Find length of the internal angle bisector of angle $
        {latex(angle_symb)}$ on the opposite side $
        {latex(side_symb)}$ of the triangle.
        """
        
        term1 = oth_sides[0] * oth_sides[1]
        sub_term2 = side_symb**2 / (oth_sides[0] + oth_sides[1]) ** 2
        term2 = 1 - sub_term2
        
        term1_ueval = Symbol(f"{latex(oth_side_lengths[0])} \\cdot {latex(oth_side_lengths[1])}") 
        sub_term2_ueval = Symbol(f"""
        \\frac{{ {latex(side_length)}^2 }} 
              {{ \\left( {latex(oth_side_lengths[0])} + {latex(oth_side_lengths[1])} \\right)^2 }}
        """)
        term2_ueval = Symbol(f"""1 - {latex(sub_term2_ueval)}""")
        

        term1_val = toIntOrFloat(oth_side_lengths[0] * oth_side_lengths[1], 2)
        
        sub_term2_val = side_length**2 / (oth_side_lengths[0] + oth_side_lengths[1]) ** 2
        sub_term2_val = toIntOrFloat(sub_term2_val, 2)

        term2_val = 1 - sub_term2_val
        
        term_val_sqr = toIntOrFloat(float(term1_val * term2_val), 2)
        term_val = toIntOrFloat(term_val_sqr ** .5, 2)
        
        soln_stmt = f"""
        {abc_val_str}
        
        Length of the internal angle bisector of angle $
        {latex(angle_symb)}$ on the opposite side $
        {latex(side_symb)}$ 
        
        
        $\\displaystyle
        \\begin{{align}}

        {latex(bisector)}_{{ {latex(side_symb)} }}
        & = \\sqrt{{   
            {latex(term1)} \\left( 1- {latex(sub_term2)} \\right)
        }} 
        \\\\ \\\\
        
        & = \\sqrt{{   
            {latex(term1_ueval)} \\left( {latex(term2_ueval)} \\right)
        }} 
        \\\\ \\\\
        
        & = \\sqrt{{   
            {latex(term1_val)} \\left( 1- {latex(sub_term2_val)} \\right)
        }} 
        \\qquad \\text{{ use calculator }}
        \\\\ \\\\
        
        & = \\sqrt{{{latex(term_val_sqr)}}} 
        \\\\ \\\\
        
        & = {latex(term_val)}
        \\\\ \\\\
        
        
        
        
        \\end{{align}} 
        $
        
        """

        answer_stmt = f"""
        """

        result = (prob_stmt.strip(),
                  soln_stmt.strip(),
                  answer_stmt.strip())
        
        return result
        pass
    
    
    
    pass
#class TriangleBasicProperties:
