'''
Created on Mar-07-2024

@author: bthakur

pip install roman

'''

import random

import inspect
import traceback

from sympy import (latex,
                   Matrix,
                   nsimplify, Rational,
                   Symbol, symbols,
                   Add, Mul, Eq, 
                   UnevaluatedExpr as ueval,
                   cos, sin,
                   )
import sympy as sp
import numpy as np

from xv.util import (UtilClass, list_to_str, toIntOrFloat,
                     get_arrow_symbols,
                     get_arrow_symbol,
                     )

from sympy.vector import (CoordSys3D, BaseVector, BaseScalar,
                          VectorAdd, VectorMul,
                          )

from ._geometry_3d_base_helper import Geometry3dBaseHelper
from ._geometry_3d_line_helper import Geometry3dLineHelper

# _geometry_3d_plane_helper
# Geometry3dPlainHelper
        
class Geometry3dPlainHelper(Geometry3dBaseHelper):
    
    def __init__(self, **kwargs):
        super().__init__(**kwargs)

        self.N = CoordSys3D('N')
        self.line_helper = Geometry3dLineHelper(verbose = self.verbose)
        pass
    
    
    
    def get_problem_equation_of_plane(self, v1 = None, v2 = None, v3 = None, N = None, **kwargs):
        '''
        @v1: A vector 
        @v2: A vector
        @v3: A vector
        @N: coord_sys
        @vector_subscripts: a list of symbols. [A, B, C, P].
        '''
        
        ref_idx = kwargs.get('ref_idx', 0)
        sub_problem_idx = kwargs.get('sub_problem_idx', None)
        if sub_problem_idx is None:
            sub_problem_idx = ""
        else:
            sub_problem_idx = f"{sub_problem_idx}."



        N = self._parse_N(v1, v2, v3, N = N)
        
        if v1 is None:
            v1 = self.get_random_vector(N = N)
        
        if v2 is None:
            v2 = self.get_random_vector(N = N)
        
        if v3 is None:
            v3 = self.get_random_vector(N = N)
        
        x, y, z = symbols("x, y, z")
        vP = self.get_vector(x, y, z, N)
        
        
        prob_stmts = []
        soln_stmts = []
        answer_stmts = []
        
        prob_stmt = f"""
        """
        prob_stmts.append(prob_stmt.strip())

        prob_stmt = f"""
        If the three points in a plane are are given by position vectors $\\displaystyle 
        {v1.get_latex()}, {v2.get_latex()} \\text {{ and }} {v3.get_latex()} 
        $. Find equation of the plane.
        """
        prob_stmts.append(prob_stmt.strip())

        vector_subscripts = kwargs.get('vector_subscripts', None)
        if not vector_subscripts is None:
            A, B, C, P = vector_subscripts
        else:
            A, B, C, P = get_arrow_symbols("A B C P".split(" "))
        
        
        PABC = Symbol(f"{latex(P)}{latex(A)}{latex(B)}{latex(C)}".replace(f"\\overrightarrow", ""))
        
        

        soln_stmt = f"""
        $\\displaystyle 
        \\begin{{aligned}}
        
        \\text{{We have }} \\quad
        & {latex(A)} = {v1.get_latex()} \\\\
        & {latex(B)} = {v2.get_latex()} \\\\
        & {latex(C)} = {v3.get_latex()} \\\\
        & {latex(P)} = {vP.get_latex()} 
        \\quad \\text{{ where}} {latex(P)} \\text{{ is an arbitray point. }}

        \\end{{aligned}}
        $
        """
        soln_stmts.append(soln_stmt.strip())

        
        soln_stmt = f"""
        As $\\displaystyle 
        {latex(P)}, {latex(A)}, {latex(B)} \\text {{ and }} {latex(C)} 
        $ are in the plane, the volume of tetrahedron $
        {latex(PABC)}$ formed by these points will be zero.
        TODO: Add an image.
        """
        soln_stmts.append(soln_stmt.strip())


        matrix = self.convert_to_matrix(vP, v1, v2, v3)
        one_sixth = Rational(1, 6)
        
        soln_stmt = f"""
        The volume of tetrahedron ${latex(PABC)}
        $ can be expressed as a matrix made of coefficients of the points. Therefore,
        
        $\\displaystyle 
        \\begin{{aligned}}
        
        \\implies
        & {latex(one_sixth)} \\cdot  \\text{{ determinant }} \\left( {latex(matrix)} \\right) = 0
        \\\\ \\\\
        
        \\implies 
        & {latex(matrix.as_symb_det())} = 0
        \\\\ \\\\
        
        \\implies 
        & {latex(matrix.det())} = 0

        \\end{{aligned}}
        $
        This is the equation of the plane.
        """
        soln_stmts.append(soln_stmt.strip())

        plane_eq = Eq(matrix.det(), 0)
        
        answer_stmt = f"""
        ${latex(plane_eq)}$
        """
        answer_stmts.append(answer_stmt.strip())


        prob_s = "\n\n".join(prob_stmts)
        soln_s = "\n\n".join(soln_stmts)
        answer_s = "\n\n".join(answer_stmts)
        
        result = {
            'v1': v1,
            'v2': v2,
            'v3': v3,
            'prob_s': prob_s.strip(),
            'soln_s': soln_s.strip(),
            'answer_s': answer_s.strip(),
            'plane_eq': plane_eq,
            }
        return result
        pass



    def get_problem_normal_of_plane_through_3_points(self, 
                            v1 = None, v2 = None, v3 = None, N = None, **kwargs):
        '''
        @v1: A vector 
        @v2: A vector
        @v3: A vector
        @N: coord_sys
        '''
        
        ref_idx = kwargs.get('ref_idx', 0)
        sub_problem_idx = kwargs.get('sub_problem_idx', None)
        if sub_problem_idx is None:
            sub_problem_idx = ""
        else:
            sub_problem_idx = f"{sub_problem_idx}."



        N = self._parse_N(v1, v2, N = N)
        
        if v1 is None:
            v1 = self.get_random_vector(N = N)
        
        if v2 is None:
            v2 = self.get_random_vector(N = N)
        
        if v3 is None:
            v3 = self.get_random_vector(N = N)
        
        x, y, z = symbols("x, y, z")
        vP = self.get_vector(x, y, z, N)
        
        prob_stmts = []
        soln_stmts = []
        answer_stmts = []
        
        prob_stmt = f"""
        """
        prob_stmts.append(prob_stmt.strip())

        prob_stmt = f"""
        If the three points in a plane are are given by position vectors $\\displaystyle 
        {v1.get_latex()} \\text {{ and }} {v3.get_latex()} 
        $. Find equation of the plane.
        """
        prob_stmts.append(prob_stmt.strip())

        A, B, C, AB, AC, BC, n = get_arrow_symbols("A B C AB AC BC n".split(" "))

        soln_stmt = f"""
        $\\displaystyle 
        \\begin{{aligned}}
        
        \\text{{Let }} \\quad
        & {latex(A)} = {v1.get_latex()} \\\\
        & {latex(B)} = {v2.get_latex()} \\\\
        & {latex(C)} = {v3.get_latex()} 

        \\end{{aligned}}
        $
        """
        soln_stmts.append(soln_stmt.strip())

        
        soln_stmt = f"""
        As $\\displaystyle 
        {latex(A)}, {latex(B)} \\text {{ and }} {latex(C)} 
        $ are in the plane, $\\displaystyle 
        {latex(AB)}, {latex(AC)} \\text {{ and }} {latex(BC)} 
        $ will be in the plane.
        TODO: Add an image.
        """
        soln_stmts.append(soln_stmt.strip())

        soln_stmt = f"""
        $\\displaystyle 
        \\begin{{aligned}}
        
        {latex(AB)} 
        
        & = {latex(B)} - {latex(A)} \\\\
        
        & = \\left( {v2.get_latex()} \\right) 
          - \\left( {v1.get_latex()} \\right)  \\\\ 
          
        & = {(v2 - v1).get_latex()}

        \\end{{aligned}}
        $
        """
        soln_stmts.append(soln_stmt.strip())

        
        soln_stmt = f"""
        $\\displaystyle 
        \\begin{{aligned}}
        
        {latex(AC)} 
        
        & = {latex(C)} - {latex(A)} \\\\
        
        & = \\left( {v3.get_latex()} \\right) 
          - \\left( {v1.get_latex()} \\right)  \\\\ 
          
        & = {(v3 - v1).get_latex()}

        \\end{{aligned}}
        $
        """
        soln_stmts.append(soln_stmt.strip())

        
        soln_stmt = f"""
        $\\displaystyle 
        \\begin{{aligned}}
        
        {latex(BC)} 
        
        & = {latex(C)} - {latex(B)} \\\\
        
        & = \\left( {v3.get_latex()} \\right) 
          - \\left( {v2.get_latex()} \\right)  \\\\ 
          
        & = {(v3 - v2).get_latex()}

        \\end{{aligned}}
        $
        """
        soln_stmts.append(soln_stmt.strip())

        soln_stmt = f"""
        The cross product of any two of the above vectors will be normal to the plane.
        Let $\\displaystyle {n}$ be the normal vector. 
        <br/>
        TODO: Add an image.
        """
        soln_stmts.append(soln_stmt.replace("\n", "").strip())

        base_vectors  = get_arrow_symbols(['i', 'j', 'k'])

        matrix = self.convert_to_matrix(base_vectors, v2 - v1, v3 - v1)
        n_vector = (v2 - v1).cross(v3 - v1)

        
        soln_stmt = f"""
        $\\displaystyle 
        \\begin{{aligned}}
        
        {latex(n)}
        & = {latex(AB)} \\times {latex(AC)} \\\\ \\\\
        
        & = \\left( {(v2 - v1).get_latex()} \\right) \\times 
            \\left( {(v3 - v1).get_latex()} \\right) \\\\ \\\\
            
        & = {latex(matrix)} \\\\ \\\\
        
        & = {n_vector.get_latex()} 
        
        
        \\end{{aligned}}
        $
        """
        soln_stmts.append(soln_stmt.strip())

        soln_stmt = f"""
        $\\displaystyle 
        \\begin{{aligned}}
        
        \\text {{The magnitude of }}{latex(n)}
        & = {self.get_magnitude_calc_latex(n_vector)} \\\\ \\\\
        
        & = {latex(n_vector.magnitude())}  \\\\ \\\\
        
        \\end{{aligned}}
        $
        """
        soln_stmts.append(soln_stmt.strip())
        
        unit_n = Symbol(f"\\hat{{n}}")
        inv_magnitude = Rational

        unit_n_vector = (1 / n_vector.magnitude()) * n_vector
        
        soln_stmt = f"""
        Therefore, unit normal vector 
        
        $\\displaystyle 
        \\begin{{aligned}}
        
        {latex(unit_n)}
        & = \\frac {{ 1 }} {{ {latex(n_vector.magnitude())} }} {latex(n)} 
        \\\\ \\\\
        
        & = \\frac {{ 1 }} {{ {latex(n_vector.magnitude())} }}
        \\left( {n_vector.get_latex()} \\right) 
        \\\\ \\\\
        
        & = {unit_n_vector.get_latex()}  
        
        \\end{{aligned}}
        $
        """
        soln_stmts.append(soln_stmt.strip())

        
        
        answer_stmt = f"""
        $\\displaystyle {unit_n_vector.get_latex()}$
        """
        answer_stmts.append(answer_stmt.strip())


        prob_s = "\n\n".join(prob_stmts)
        soln_s = "\n\n".join(soln_stmts)
        answer_s = "\n\n".join(answer_stmts)
        
        result = {
            'v1': v1,
            'v2': v2,
            'v3': v3,
            'vP': vP,
            'prob_s': prob_s.strip(),
            'soln_s': soln_s.strip(),
            'answer_s': answer_s.strip(),
            'n_vector': n_vector,
            'unit_n_vector': unit_n_vector,
            
            }
        return result
        pass


    def get_problem_plane_eq_from_a_point_and_a_normal(self, v1, n_vector, **kwargs):
        '''
        @v1: A vector 
        @n_vector: A vector
        '''
        
        ref_idx = kwargs.get('ref_idx', 0)
        sub_problem_idx = kwargs.get('sub_problem_idx', None)
        if sub_problem_idx is None:
            sub_problem_idx = ""
        else:
            sub_problem_idx = f"{sub_problem_idx}."



        
        N = self._parse_N(v1)
        
        x, y, z = symbols("x, y, z")
        vP = self.get_vector(x, y, z, N)
        
        prob_stmts = []
        soln_stmts = []
        answer_stmts = []
        
        prob_stmt = f"""
        """
        prob_stmts.append(prob_stmt.strip())

        prob_stmt = f"""
        Find the equation of a plane passing through $\\displaystyle 
        {v1.get_latex()}$ having a normal at ${n_vector.get_latex()} 
        $. Find equation of the plane.
        """
        prob_stmts.append(prob_stmt.strip())

        A, P, AP, n = get_arrow_symbols("A P AP n".split(" "))

        soln_stmt = f"""
        $\\displaystyle 
        \\begin{{aligned}}
        
        \\text{{We have }} \\quad
        & {latex(n)} = {n_vector.get_latex()} 
        \\qquad \\text {{ normal to the plane }} \\\\
        
        & {latex(A)} = {v1.get_latex()}
        \\qquad \\text {{ given point in the plane }} \\\\
        
        & {latex(P)} = {vP.get_latex()} 
        \\qquad \\text {{ an arbitrary point in the plane }} 
        \\end{{aligned}}
        $
        """
        soln_stmts.append(soln_stmt.strip())

        APn_dot = (vP - v1).dot(n_vector)
        plane_eq = Eq(APn_dot, 0)
        
        soln_stmt = f"""
        As ${latex(AP)}$ is in plane and ${latex(n)}
        $ normal to it, their dot product will be zero.
        
        $\\displaystyle 
        
        {latex(AP)} \\times {latex(n)} = 0
        \\\\ ~ \\\\
        
        \\implies
        \\left [ {latex(P)} - {latex(A)} \\right ] 
        \\times {latex(n)} = 0
        \\\\ ~ \\\\
        
        \\implies
        \\left [
                 \\left ( {vP.get_latex()} \\right ) 
                  - \\left ( {v1.get_latex()} \\right )
        \\right ] 
        
        \\; \\times \\; 
        \\left( {n_vector.get_latex()} \\right) = 0
        \\\\ ~ \\\\
        
        \\implies
        \\left ( {(vP - v1).get_latex()} \\right )
        \\; \\times \\; 
        \\left( {n_vector.get_latex()} \\right) = 0
        \\\\ ~ \\\\
        
        \\implies
        {latex(plane_eq)}
        $
        
        This is equation of the plane.
        """
        soln_stmts.append(soln_stmt.strip())

        
        answer_stmt = f"""
        $\\displaystyle {latex(plane_eq)}$
        """
        answer_stmts.append(answer_stmt.strip())


        prob_s = "\n\n".join(prob_stmts)
        soln_s = "\n\n".join(soln_stmts)
        answer_s = "\n\n".join(answer_stmts)
        
        result = {
            'prob_s': prob_s.strip(),
            'soln_s': soln_s.strip(),
            'answer_s': answer_s.strip(),
            'plane_eq': plane_eq,
            }
        return result
        pass


    def get_problem_plane_eq_from_a_point_and_a_line_on_it(self, v1, line_eq, **kwargs):
        '''
        @v1: A vector 
        @line_eq: A tuple of tuples
        '''
        
        ref_idx = kwargs.get('ref_idx', 0)
        sub_problem_idx = kwargs.get('sub_problem_idx', None)
        if sub_problem_idx is None:
            sub_problem_idx = ""
        else:
            sub_problem_idx = f"{sub_problem_idx}."

        N = self._parse_N(v1)
        
        x, y, z = symbols("x, y, z")
        vP = self.get_vector(x, y, z, N)
        
        prob_stmts = []
        soln_stmts = []
        answer_stmts = []
        
        prob_stmt = f"""
        """
        prob_stmts.append(prob_stmt.strip())

        prob_stmt = f"""
        Find the equation of a plane having a point ${v1.get_latex()}$ having a line

        ${self.get_line_eq_latex(line_eq)}$
        """
        prob_stmts.append(prob_stmt.strip())

        A, D, E, P = get_arrow_symbols("A D E P".split(" "))
        
        L = Symbol('L')

        soln_stmt = f"""
        $\\displaystyle 
        \\begin{{aligned}}
        
        \\text{{We have }} \\quad
        & {latex(A)} = {v1.get_latex()}
        \\qquad \\text {{ point in the plane }} 
        \\quad \\cdots \\text {{ ({sub_problem_idx}{ref_idx + 1})  }}
        \\\\ \\\\
        
        & {latex(L)} \\equiv {self.get_line_eq_latex(line_eq)}
        \\qquad \\text {{ line in the plane }} 
        \\quad \\cdots \\text {{ ({sub_problem_idx}{ref_idx + 2})  }}
        \\\\ \\\\
        
        \\end{{aligned}}
        $
        """
        soln_stmts.append(soln_stmt.strip())



        P_coords = vP.get_coords()

        result_point = self.line_helper.get_problem_find_point_on_the_line(
                                        line_eq, N, r_val = 0, 
                                        vector_subscript = D,
                                        ref_idx = 2,
                                        explanation = False,
                                        **kwargs)
        v4 = result_point.get('v')
        
        soln_stmt = f"""
        We can find a point on a line equating its equation to a real value.
        
        {result_point.get('soln_s').strip()}
        """
        soln_stmts.append(soln_stmt.strip())


        result_point = self.line_helper.get_problem_find_point_on_the_line(
                                        line_eq, N, r_val = 1, 
                                        vector_subscript = E,
                                        ref_idx = 3,
                                        explanation = False,
                                        **kwargs)
        v5 = result_point.get('v')
        
        soln_stmt = f"""
        Using real value 1, we can get another point.
        
        {result_point.get('soln_s')}
        """
        soln_stmts.append(soln_stmt.strip())



        kwargs['vector_subscripts'] = [A, D, E, P]
        result_plane_eq = self.get_problem_equation_of_plane(
                                        v1 = v1, v2 = v4, v3 = v5, **kwargs)
        plane_eq = result_plane_eq['plane_eq']
        
        soln_stmt = f"""
        Now, we can calculate equation of the plain.
        
        {result_plane_eq.get('soln_s')}
        """
        soln_stmts.append(soln_stmt.strip())

        
        answer_stmt = f"""
        $\\displaystyle {latex(plane_eq)}$
        """
        answer_stmts.append(answer_stmt.strip())


        prob_s = "\n\n".join(prob_stmts)
        soln_s = "\n\n".join(soln_stmts)
        answer_s = "\n\n".join(answer_stmts)
        
        result = {
            'prob_s': prob_s.strip(),
            'soln_s': soln_s.strip(),
            'answer_s': answer_s.strip(),
            'plane_eq': plane_eq,
            }
        return result
        pass



    def get_problem_plane_eq_from_a_point_and_a_line_on_it_WORKING_BUT_ALTERNATE_BETTER(
            self, v1, line_eq, **kwargs):
        '''
        @v1: A vector 
        @line_eq: A tuple of tuples
        '''
        
        ref_idx = kwargs.get('ref_idx', 0)
        sub_problem_idx = kwargs.get('sub_problem_idx', None)
        if sub_problem_idx is None:
            sub_problem_idx = ""
        else:
            sub_problem_idx = f"{sub_problem_idx}."



        N = self._parse_N(v1)
        
        x, y, z = symbols("x, y, z")
        vP = self.get_vector(x, y, z, N)
        
        prob_stmts = []
        soln_stmts = []
        answer_stmts = []
        
        prob_stmt = f"""
        """
        prob_stmts.append(prob_stmt.strip())

        prob_stmt = f"""
        Find the equation of a plane passing through $\\displaystyle 
        {v1.get_latex()}$ having a line at ${self.get_line_eq_latex(line_eq)} 
        $.
        """
        prob_stmts.append(prob_stmt.strip())

        r, A, D, AD, P, AP, n = get_arrow_symbols("r A D AD P AP n".split(" "))
        
        L = Symbol('L')

        soln_stmt = f"""
        $\\displaystyle 
        \\begin{{aligned}}
        
        \\text{{We have }} \\quad
        & {latex(L)} \\equiv {self.get_line_eq_latex(line_eq)}
        \\qquad \\text {{ line in the plane }} \\\\ \\\\
        
        & {latex(A)} = {v1.get_latex()}
        \\qquad \\qquad \\text {{ given point in the plane }} \\\\ \\\\
        
        & {latex(P)} = {vP.get_latex()} 
        \\qquad \\qquad \\text {{ an arbitrary point in the plane }} 
        \\end{{aligned}}
        $
        """
        soln_stmts.append(soln_stmt.strip())

        alpha, beta, gamma = symbols(f"\\alpha, \\beta, \\gamma")

        x, y, z = symbols("x, y, z")
        vP = self.get_vector(x, y, z, N)
        
        x0, y0, z0 = symbols("x_0, y_0, z_0")
        vP0 = self.get_vector(x0, y0, z0, N)

        P0P_coords = (vP - vP0).get_coords()

        direction_tuple = tuple([ item[1] for item in line_eq])
        v_direction_vector = self.get_vector(*direction_tuple, N)

        soln_stmt = f"""
        The standard parametric equation of a line is
        
        $\\displaystyle 
        \\frac {{ {latex(P0P_coords[0])} }} {{ {latex(cos(alpha))} }} 
        
        = \\frac {{ {latex(P0P_coords[1])} }} {{ {latex(cos(beta))} }} 
        
        = \\frac {{ {latex(P0P_coords[2])} }} {{ {latex(cos(gamma))} }} 
        $ 
        
        where $\\displaystyle \\left(
        {latex(cos(alpha))}, {latex(cos(beta))}, {latex(cos(gamma))}
        \\right)
        $ is called direction vector of the line.
        """
        soln_stmts.append(soln_stmt.strip())

        
        
        soln_stmt = f"""
        Compare it with the given equation of the line:
        
        $\\displaystyle {self.get_line_eq_latex(line_eq)} $
        
        The direction vector of the line is $\\displaystyle {latex(direction_tuple)} $
        
        In vector form, it is
        
        $\\displaystyle {latex(r)} = {v_direction_vector.get_latex()} 
        \\qquad \\cdots \\text {{ ({sub_problem_idx}{ref_idx + 1})  }}
        $
        """
        soln_stmts.append(soln_stmt.strip())



        soln_stmt = f"""
        Coming back to the plane, line ${latex(L)}$ and direction vector ${latex(r)}
        $, both are in the plane. 
        We already have point ${latex(A)}$ in the plane.
        If we find a point ${latex(D)}$ on the line, ${latex(AD)}$ will be in the plane too.
        So, the cross product of direction vector ${latex(r)}$ and ${latex(AD)}
        $ will be normal to the plane.
        <br/>
        TODO: Add image
        
        """
        soln_stmts.append(soln_stmt.replace("\n", " ").strip())


        P_coords = vP.get_coords()

        result_point = self.line_helper.get_problem_find_point_on_the_line(
                                        line_eq, N, r_val = 1, 
                                        vector_subscript = D,
                                        ref_idx = 1,
                                        explanation = False,
                                        **kwargs)
        v4 = result_point.get('v')
        
        soln_stmt = f"""
        {result_point.get('soln_s')}
        """
        soln_stmts.append(soln_stmt.strip())


        base_vectors  = get_arrow_symbols(['i', 'j', 'k'])

        matrix = self.convert_to_matrix(base_vectors, v_direction_vector, v4 - v1)
        n_vector = v_direction_vector.cross(v4 - v1)
        
        
        
        soln_stmt = f"""
        Now, let us calculate the normal the plane. We will call it ${latex(n)}$.
        
        $\\displaystyle 
        \\begin{{aligned}}
        
        {latex(n)}
        & = {latex(r)} \\times {latex(AD)} \\\\ \\\\
        
        & = \\left( {v_direction_vector.get_latex()} \\right) \\times 
            \\left[
             
                  \\left( {(v4).get_latex()} \\right)
                - \\left( {(v1).get_latex()} \\right)
            
            \\right] \\\\ \\\\
            
        & = \\left( {v_direction_vector.get_latex()} \\right) \\times 
            \\left( {(v4 - v1).get_latex()} \\right) \\\\ \\\\
            
        & = {latex(matrix)} \\\\ \\\\
        
        & = {n_vector.get_latex()} 
        \\qquad \\cdots \\text {{ ({sub_problem_idx}{ref_idx + 3})  }}
        
        \\end{{aligned}}
        $
        """
        soln_stmts.append(soln_stmt.strip())


        result_plane_eq = self.get_problem_plane_eq_from_a_point_and_a_normal(
                                        v1, n_vector, **kwargs)
        
        plane_eq = result_plane_eq['plane_eq']
        
        soln_stmt = f"""
        Now, we can calculate equation of the plain.
        
        {result_plane_eq.get('soln_s')}
        """
        soln_stmts.append(soln_stmt.strip())

        
        answer_stmt = f"""
        $\\displaystyle {latex(plane_eq)}$
        """
        answer_stmts.append(answer_stmt.strip())


        prob_s = "\n\n".join(prob_stmts)
        soln_s = "\n\n".join(soln_stmts)
        answer_s = "\n\n".join(answer_stmts)
        
        result = {
            'prob_s': prob_s.strip(),
            'soln_s': soln_s.strip(),
            'answer_s': answer_s.strip(),
            'plane_eq': plane_eq,
            }
        return result
        pass


    def get_problem_plane_eq_from_two_lines_on_it(self, line_eq1, line_eq2, N = None, **kwargs):
        '''
        @line_eq1: A tuple of tuples
        @line_eq2: A tuple of tuples
        '''
        
        ref_idx = kwargs.get('ref_idx', 0)
        sub_problem_idx = kwargs.get('sub_problem_idx', None)
        if sub_problem_idx is None:
            sub_problem_idx = ""
        else:
            sub_problem_idx = f"{sub_problem_idx}."



        N = self._parse_N(N = N)
        
        x, y, z = symbols("x, y, z")
        vP = self.get_vector(x, y, z, N)
        
        prob_stmts = []
        soln_stmts = []
        answer_stmts = []
        
        prob_stmt = f"""
        """
        prob_stmts.append(prob_stmt.strip())

        prob_stmt = f"""
        Find the equation of a plane having lines 

        ${self.get_line_eq_latex(line_eq1)}$, and  

        ${self.get_line_eq_latex(line_eq2)}$
        """
        prob_stmts.append(prob_stmt.strip())

        D, E, F, P = get_arrow_symbols("D E F P".split(" "))
        
        L1, L2 = symbols('L1 L2')

        soln_stmt = f"""
        $\\displaystyle 
        \\begin{{aligned}}
        
        \\text{{We have }} \\quad
        & {latex(L1)} \\equiv {self.get_line_eq_latex(line_eq1)}
        \\qquad \\text {{ line in the plane }} 
        \\quad \\cdots \\text {{ ({sub_problem_idx}{ref_idx + 1})  }}
        \\\\ \\\\
        
        & {latex(L2)} \\equiv {self.get_line_eq_latex(line_eq2)}
        \\qquad \\text {{ another line in the plane }} 
        \\quad \\cdots \\text {{ ({sub_problem_idx}{ref_idx + 2})  }}
        
        \\end{{aligned}}
        $
        """
        soln_stmts.append(soln_stmt.strip())



        P_coords = vP.get_coords()
        
        result_point = self.line_helper.get_problem_find_point_on_the_line(
                                        line_eq1, N, r_val = 0, 
                                        vector_subscript = D,
                                        ref_idx = 2,
                                        explanation = False,
                                        **kwargs)
        v4 = result_point.get('v')
        
        soln_stmt = f"""
        Equating line equation in $({sub_problem_idx}{ref_idx + 1})
        $ to a real value, we can find a point on this line.  
        
        {result_point.get('soln_s')}
        """
        soln_stmts.append(soln_stmt.strip())


        result_point = self.line_helper.get_problem_find_point_on_the_line(
                                        line_eq1, N, r_val = 1, 
                                        vector_subscript = E,
                                        ref_idx = 3,
                                        explanation = False,
                                        **kwargs)
        v5 = result_point.get('v')
        
        soln_stmt = f"""
        Using real value 1, we can get another point.
        
        {result_point.get('soln_s')}
        """
        soln_stmts.append(soln_stmt.strip())

        


        result_point = self.line_helper.get_problem_find_point_on_the_line(
                                        line_eq2, N, r_val = 2, 
                                        vector_subscript = F,
                                        ref_idx = 4,
                                        explanation = False,
                                        **kwargs)
        v6 = result_point.get('v')
        
        soln_stmt = f"""
        Similarly, using line equation in $({sub_problem_idx}{ref_idx + 2})
        $, we can find a point on this line. 
        Not using same real value as used earlier minimizes 
        the chance of finding the same point found earlier.
        """
        soln_stmts.append(soln_stmt.replace("\n", " ").strip())

        soln_stmt = f"""
        {result_point.get('soln_s')}
        """
        soln_stmts.append(soln_stmt.strip())


        kwargs['vector_subscripts'] = [D, E, F, P]
        result_plane_eq = self.get_problem_equation_of_plane(
                                        v1 = v4, v2 = v5, v3 = v6, **kwargs)
        plane_eq = result_plane_eq['plane_eq']
        
        soln_stmt = f"""
        Now, we can calculate equation of the plain.
        
        {result_plane_eq.get('soln_s')}
        """
        soln_stmts.append(soln_stmt.strip())

        
        answer_stmt = f"""
        $\\displaystyle {latex(plane_eq)}$
        """
        answer_stmts.append(answer_stmt.strip())


        prob_s = "\n\n".join(prob_stmts)
        soln_s = "\n\n".join(soln_stmts)
        answer_s = "\n\n".join(answer_stmts)
        
        result = {
            'prob_s': prob_s.strip(),
            'soln_s': soln_s.strip(),
            'answer_s': answer_s.strip(),
            'plane_eq': plane_eq,
            }
        return result
        pass



    def get_problem_plane_distance_of_a_point(self, plane_eq, v4, N = None, **kwargs):
        '''
        @line_eq1: A tuple of tuples
        @line_eq2: A tuple of tuples
        '''
        
        ref_idx = kwargs.get('ref_idx', 0)
        sub_problem_idx = kwargs.get('sub_problem_idx', None)
        if sub_problem_idx is None:
            sub_problem_idx = ""
        else:
            sub_problem_idx = f"{sub_problem_idx}."


        N = self._parse_N(N = N)
        
        x, y, z, theta, d = symbols("x, y, z, theta, d")
        vP = self.get_vector(x, y, z, N)

        A, B, AB, n = get_arrow_symbols("A B AB n".split(" "))
        
        prob_stmts = []
        soln_stmts = []
        answer_stmts = []
        
        prob_stmt = f"""
        """
        prob_stmts.append(prob_stmt.strip())

        prob_stmt = f"""
        Find distance of the point  $\\displaystyle {v4.get_latex()}$ from the plane

        ${latex(plane_eq)}$
        """
        prob_stmts.append(prob_stmt.strip())


        soln_stmt = f"""
        The given plain is 
        
        $\\displaystyle 
        {latex(plane_eq)}
        \\quad \\cdots \\text {{ ({sub_problem_idx}{ref_idx + 1})  }}
        $
        """
        soln_stmts.append(soln_stmt.strip())

        plane_coeff_dict = plane_eq.lhs.as_coefficients_dict()
        
        n_vector = self.get_vector(plane_coeff_dict.get(x, 0), 
                                   plane_coeff_dict.get(y, 0), 
                                   plane_coeff_dict.get(z, 0), 
                                   N)
        

        soln_stmt = f"""
        Taking its coefficients, we can form the normal vector to it. 
        
        $\\displaystyle 
        {latex(n)} = {n_vector.get_latex()}
        \\quad \\cdots \\text {{ ({sub_problem_idx}{ref_idx + 2})  }}
        $
        """
        soln_stmts.append(soln_stmt.strip())


        plane_eq_subs = plane_eq.subs({y: 0, z: 0})
        x_val = sp.solve(plane_eq_subs, x)[0]
        
        vA = self.get_vector(x_val, 0, 0, N)
        
        soln_stmt = f"""
        Let us find a point on the plain. Setting ${latex(y)} = 0
        $ and ${latex(z)} = 0$, we can find ${latex(x)}$.
        
        ${latex(x)} = {latex(x_val)}$
        
        $\\implies 
        {latex(A)} = {vA.get_latex()}
        \\quad \\cdots \\text {{ ({sub_problem_idx}{ref_idx + 3})  }}
        $
        
        """
        soln_stmts.append(soln_stmt.strip())


        soln_stmt = f"""
        Now, we have 
        
        $ \\displaystyle 
        {latex(A)} = {vA.get_latex()}
        \\quad  \\text {{ a point in the plain  }}
        \\\\ ~ \\\\
        
        {latex(B)} = {v4.get_latex()}
        \\quad  \\text {{ the point out of the plain  }}
        \\\\ ~ \\\\
        
        {latex(n)} = {n_vector.get_latex()}
        \\quad \\text {{ normal vector to the plain  }}
        $
        """
        soln_stmts.append(soln_stmt.strip())


        soln_stmt = f"""
        $ \\displaystyle 
        {latex(n)} \\cdot {latex(AB)}
        = \\left| {latex(n)} \\right| \\cdot  \\left| {latex(AB)} \\right| {latex(sp.cos(theta))}

        \\quad \\text {{ where }} {latex(theta)} \\text {{ is angle between }} 
        
        {latex(n)} \\text {{ and }}  {latex(AB)}
        $
        """
        soln_stmts.append(soln_stmt.strip())

        soln_stmt = f"""
        $\\displaystyle  \\left| {latex(AB)} \\right| {latex(sp.cos(theta))}
        $ is the normal distance between the plain and the point ${latex(B)}
        $. Let us call it ${latex(d)}$.
        
        $ \\displaystyle \\implies
          {latex(d)} \\cdot \\left| {latex(n)} \\right|  
        = {latex(n)} \\cdot {latex(AB)}
        $
        
        $ \\displaystyle \\implies
          {latex(d)} \\cdot \\left| {latex(n)} \\right|  
        = {latex(n)} \\cdot \\left[ {latex(B)} - {latex(A)} \\right]
        $
        """
        soln_stmts.append(soln_stmt.strip())

        
        soln_stmt = f"""
        $ \\displaystyle \\implies
          {latex(d)} \\cdot \\left| {n_vector.get_latex()} \\right|  
        
        = \\left( {n_vector.get_latex()} \\right) \\cdot \\left[
        
        \\left( {v4.get_latex()} \\right) - \\left( {vA.get_latex()} \\right)
        \\right]
        $
        """
        soln_stmts.append(soln_stmt.strip())


        soln_stmt = f"""
        $ \\displaystyle \\implies
          {latex(d)} \\cdot {self.get_magnitude_calc_latex(n_vector)}

        = \\left( {n_vector.get_latex()} \\right) \\cdot  
          \\left( {(v4 - vA).get_latex()} \\right)
        
        $
        """
        soln_stmts.append(soln_stmt.strip())

        n_vector_dot_v4_vA = n_vector.dot(v4 - vA)
        d_val = n_vector_dot_v4_vA / n_vector.magnitude()

        soln_stmt = f"""
        $ \\displaystyle \\implies
          {latex(d)} \\cdot {latex(n_vector.magnitude())}
          
        = {self.get_dot_product_calc_latex(n_vector, v4 - vA)}
        $
        
        $ \\displaystyle \\implies
          {latex(d)} \\cdot {latex(n_vector.magnitude())}
          
        = {latex(n_vector_dot_v4_vA)}
        $
        
        $ \\displaystyle \\implies
          {latex(d)}
          
        = {latex(d_val)}
        $
        
        
        """
        soln_stmts.append(soln_stmt.strip())



        
        answer_stmt = f"""
        $ \\displaystyle
        {latex(d_val)}
        $
        """
        answer_stmts.append(answer_stmt.strip())


        prob_s = "\n\n".join(prob_stmts)
        soln_s = "\n\n".join(soln_stmts)
        answer_s = "\n\n".join(answer_stmts)
        
        result = {
            'prob_s': prob_s.strip(),
            'soln_s': soln_s.strip(),
            'answer_s': answer_s.strip(),
            }
        return result
        pass


    pass
#class Geometry3dPlainHelper:
