'''
Created on 12-Jul-2021

@author: rishi_priyansh

# pip install radioactivedecay
# https://radioactivedecay.github.io/overview.html#introduction

'''
import random
import inspect
import traceback
import re
from sympy import (symbols)
import pandas as pd
import pathlib

import networkx as nx
from collections import deque
import numpy as np

from ._radioactivity_func_copied import Radioactivity_func_copied as RF_copied

from xv.km.util import latex_fraction, latex_atom
from xv.util import locatePath, FileSystemManager

from ._base_helper import BaseHelper

class RadioActivityHelper(BaseHelper):
    _NUCLIDE_ATOMIC_MASS_FILE_PATH = 'resources/nuclide_atomic_mass.csv'
    _ALPHA_ATOMIC_NUMBER = 2
    _ALPHA_MASS_NUMBER = 4


    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.atomic_mass_df = self._init_atomic_mass_df()

        try:
            from mendeleev import element as element_lib
            self.element_lib = element_lib
            pass
        except Exception as e:
            sError = f"""Error {self.__class__.__name__}.{inspect.stack()[0][3]}:
            Failed to import 'from mendeleev import element'.
            """
            self._log(sError)
            self._log(traceback.format_exc())
            self.element_lib = None
            pass
        pass

    def get_decay_digraph(self, element_symbol, mass_number):
        import radioactivedecay as rd
        import matplotlib.pyplot as plt
        from xv.km.ui import SvgHelper

        nuc = rd.Nuclide(f'{element_symbol}-{mass_number}')

        digraph, max_generation, max_xpos = RF_copied._build_decay_digraph(nuc, nx.DiGraph())
        return (digraph, max_generation, max_xpos)
        pass

    def plot_radioactive_decay_series(self, element_symbol, mass_number):
        import radioactivedecay as rd
        import matplotlib.pyplot as plt
        from xv.km.ui import SvgHelper

        nuc = rd.Nuclide(f'{element_symbol}-{mass_number}')
        fig, ax = nuc.plot()


        svgHelper = SvgHelper()
        svg_str = svgHelper.plt_to_svg()

        if not svg_str:
            sError = f"""Error {self.__class__.__name__}.{inspect.stack()[0][3]}:
            svg_str could not be initialized
            element_symbol = {element_symbol}
            mass_number = {mass_number}
            """
            self._log(sError)
            self._log(traceback.format_exc())
            return None

        plt.clf()
        plt.close()
        return svg_str
        pass

    def plot_radioactive_decay_graph(self, element_symbol, mass_number):
        import radioactivedecay as rd
        import matplotlib.pyplot as plt
        from xv.km.ui import SvgHelper

        nuc = rd.Nuclide(f'{element_symbol}-{mass_number}')
        half_life = nuc.half_life("s")

        inv = rd.InventoryHP({f'{element_symbol}-{mass_number}': 10.0})

        inv1 = inv.decay(half_life * 4, 's')
        activities = inv1.activities('Bq')
        activities_str = self.activities_to_str(activities)

        fig, ax = inv.plot(half_life * 4, 's',
                 xscale='log', yscale='log', xmin=1, ymin=1E-17, yunits='mg')
        ax.legend(loc=2, prop={'size': 6})

        ax.spines['top'].set_visible(False)
        ax.spines['right'].set_visible(False)
        #ax.spines['bottom'].set_visible(False)
        #ax.spines['left'].set_visible(False)

        fig.tight_layout()

        svgHelper = SvgHelper()
        svg_str = svgHelper.plt_to_svg()

        if not svg_str:
            sError = f"""Error {self.__class__.__name__}.{inspect.stack()[0][3]}:
            svg_str could not be initialized
            element_symbol = {element_symbol}
            mass_number = {mass_number}
            """
            self._log(sError)
            self._log(traceback.format_exc())
            return None

        plt.clf()
        plt.close()
        return (svg_str, activities_str)
        pass

    def activities_to_str(self, activities):
        activities = [(*k.split("-"), v) for k,v in activities.items()]

        activities = sorted(activities, key=lambda x: x[1], reverse=True)
        activities_str = [f"{k1}-{k2}: {v}" for k1, k2, v in activities]
        activities_str = "\n".join(activities_str)
        return activities_str



    def _init_atomic_mass_df(self):
        file_name = RadioActivityHelper._NUCLIDE_ATOMIC_MASS_FILE_PATH
        file_path = self._get_file_path(file_name,
                                        rootFolder = None,
                                        parent_depth = 3)

        atomic_mass_df =  pd.read_csv(file_path)
        return atomic_mass_df
        pass

    def get_elements_by_decay_type(self, decay_type):
        df = self.atomic_mass_df
        df1 = df[df[decay_type]][['atomic_number', 'mass_number']]
        elements = [tuple(row) for row in df1.values]

        elements = [(int(a), int(m)) for a,m in elements]
        return elements
        pass

    def get_element_symbol(self, atomic_number, default_name):
        if atomic_number is None:
            atomic_number = random.randint(1, 118)

        if self.element_lib is None:
            return default_name

        return self.element_lib(atomic_number).symbol
        pass

    def get_element_name(self, atomic_number, default_name):
        if atomic_number is None:
            atomic_number = random.randint(1, 118)

        if self.element_lib is None:
            return default_name

        return self.element_lib(atomic_number).name
        pass

    def get_atomic_mass_in_amu(self, atomic_number, mass_number):
        df = self.atomic_mass_df
        try:
            mass_in_amu = df[ (df.atomic_number == atomic_number) &
                    (df.mass_number == mass_number)].iloc[0].amu
            return mass_in_amu

        except Exception as e:
            sError = f"""Error {self.__class__.__name__}.{inspect.stack()[0][3]}: {e}
            atomic_number = {atomic_number}
            mass_number = {mass_number}
            """
            self._log(sError)
            return None
        pass

    def get_decay_details(self, parent_atom, child_atom, decay_type):
        parent_element_symbol, parent_mass_number = parent_atom.split("-")
        parent_mass_number = int(parent_mass_number)

        elem = self.element_lib(parent_element_symbol)
        parent_atomic_number = elem.atomic_number

        if decay_type == "α":
            decay_data = self.get_alpha_decay(parent_atomic_number, parent_mass_number)
            decay_details = self.get_alpha_decay_problem_details(decay_data)
            return decay_details

        if decay_type == "β⁻"or decay_type == "β-":
            decay_data = self.get_beta_decay(parent_atomic_number, parent_mass_number)
            decay_details = self.get_beta_decay_problem_details(decay_data)
            return decay_details

        if decay_type == "β+":
            decay_data = self.get_positron_decay(parent_atomic_number, parent_mass_number)
            decay_details = self.get_positron_decay_problem_details(decay_data)
            return decay_details

        if decay_type == "γ":
            decay_data = self.get_gamma_decay(parent_atomic_number, parent_mass_number)
            decay_details = self.get_gamma_decay_problem_details(decay_data)
            return decay_details


        if decay_type == "EC":
            decay_data = self.get_electron_capture_decay(parent_atomic_number, parent_mass_number)
            decay_details = self.get_electron_capture_decay_problem_details(decay_data)
            return decay_details

        if decay_type == "β⁺ & EC":
            decay_details = self.get_positron_gamma_decay_problem_details(parent_atomic_number, parent_mass_number)
            return decay_details


        if decay_type == "SF":
            decay_details_text = f"""
            decay_type = {decay_type} not implemented.
            See <a target="_blank" href="https://en.wikipedia.org/wiki/Spontaneous_fission">See link for details.</a>
            """.strip()

            decay_details = {
                'soln_s': decay_details_text,
                'answer_s': decay_details_text
                }
            return decay_details


        decay_details_null = {
            'soln_s': f"decay_type = {decay_type} not implemented",
            'answer_s': f"decay_type = {decay_type} not implemented"
            }
        return decay_details_null
        pass

    def get_alpha_decay(self,
                        parent_atomic_number = None,
                        parent_mass_number = None,
                        ):
        right_arrow = symbols(u'\U00002192')

        if parent_atomic_number is None:
            atomic_numbers = self.get_elements_by_decay_type('alpha_decay')
            if len(atomic_numbers) > 0:
                parent_atomic_number, parent_mass_number = random.choice(
                                                            atomic_numbers)
                self._log(f"parent_atomic_number = {parent_atomic_number}")
            else:
                parent_atomic_number = random.randint(6, 125)


        if parent_mass_number is None:
            parent_mass_number = int(parent_atomic_number * (2 + random.random() * .4))

        parent_atomic_symbol = self.get_element_symbol(parent_atomic_number, "X")

        baby_atomic_number = parent_atomic_number - RadioActivityHelper._ALPHA_ATOMIC_NUMBER
        baby_mass_number = parent_mass_number - RadioActivityHelper._ALPHA_MASS_NUMBER

        baby_atomic_symbol = self.get_element_symbol(baby_atomic_number, "Y")

        alpha_symbol = symbols(r'\alpha')
        helium_symbol = symbols(r'He')

        parent_mass = self.get_atomic_mass_in_amu(parent_atomic_number,
                                                  parent_mass_number)

        baby_mass = self.get_atomic_mass_in_amu(baby_atomic_number,
                                                baby_mass_number)

        alpha_mass = self.get_atomic_mass_in_amu(RadioActivityHelper._ALPHA_ATOMIC_NUMBER,
                                                 RadioActivityHelper._ALPHA_MASS_NUMBER)



        parent_latex = latex_atom(parent_atomic_symbol,
                                  parent_atomic_number,
                                  parent_mass_number)

        baby_latex = latex_atom(baby_atomic_symbol,
                                  baby_atomic_number,
                                  baby_mass_number)

        baby_ion_latex = latex_atom(baby_atomic_symbol,
                                  baby_atomic_number,
                                  baby_mass_number,
                                  ionic_charge = f"{RadioActivityHelper._ALPHA_ATOMIC_NUMBER}-")

        alpha_latex = latex_atom(helium_symbol, RadioActivityHelper._ALPHA_ATOMIC_NUMBER, RadioActivityHelper._ALPHA_MASS_NUMBER, ionic_charge = f"{RadioActivityHelper._ALPHA_ATOMIC_NUMBER}+")

        helium_latex = latex_atom(helium_symbol, RadioActivityHelper._ALPHA_ATOMIC_NUMBER, RadioActivityHelper._ALPHA_MASS_NUMBER)

        electron_latex = latex_atom('e', ionic_charge = "-")

        parent_name = self.get_element_name(parent_atomic_number,
                                            parent_atomic_symbol)

        baby_name = self.get_element_name(baby_atomic_number,
                                          baby_atomic_symbol)

        result = {}

        result['parent'] = {'atomic_symbol': parent_atomic_symbol,
                            'name': parent_name,
                            'atomic_number': parent_atomic_number,
                            'mass_number': parent_mass_number,
                            'latex': parent_latex,
                            }

        result['baby'] = {'atomic_symbol': baby_atomic_symbol,
                            'name': baby_name,
                            'atomic_number': baby_atomic_number,
                            'mass_number': baby_mass_number,
                            'latex': baby_latex,
                            'ion_latex': baby_ion_latex
                            }

        result['alpha'] = {'atomic_symbol': alpha_symbol,
                            'atomic_number': RadioActivityHelper._ALPHA_ATOMIC_NUMBER,
                            'mass_number': RadioActivityHelper._ALPHA_MASS_NUMBER,
                            'ionic_charge': f'{RadioActivityHelper._ALPHA_ATOMIC_NUMBER}+',
                            'latex': alpha_latex
                            }

        result['helium'] = {'atomic_symbol': 'He',
                            'atomic_number': RadioActivityHelper._ALPHA_ATOMIC_NUMBER,
                            'mass_number': RadioActivityHelper._ALPHA_MASS_NUMBER,
                            'latex': helium_latex
                            }

        result['electron'] = {'ionic_charge': '-',
                              'latex': electron_latex
                             }



        result['eq'] = f"""
        {parent_latex} {right_arrow} \\space {baby_latex} + \\space {helium_latex}
        """.strip()

        if not (baby_mass is None or
                alpha_mass is None or
                parent_mass is None
                ):
            result['change_in_mass'] = {
                    'amu':  (baby_mass + alpha_mass) - parent_mass,
                    'latex': f"({baby_mass} + {alpha_mass}) - {parent_mass}",
                    'latex_formula': f"(mass_{{{baby_atomic_symbol}}} + mass_{{He}}) - mass_{{{parent_atomic_symbol}}}"
                }


        return result
        pass

    def get_alpha_decay_problem_details(self, data):
        right_arrow = symbols(u'\U00002192')

        problem_s = f"""
        Write equation of alpha decay of an atom
        ${data['parent']['latex']}$
        """


        soln_s = f"""
        <h3>$
        {data['parent']['latex']}
        {right_arrow}
        \\space {data['baby']['ion_latex']}
        + \\space {data['alpha']['latex']}
        $</h3>
        {data['parent']['atomic_symbol']} = {data['parent']['name']}
        {data['baby']['atomic_symbol']} = {data['baby']['name']}

        ${data['alpha']['latex']}$ is called ${data['alpha']['atomic_symbol']}$-particle. It is a Helium ion.

        The negative ion ${data['baby']['latex']}$ releases two electrons and becomes neutral:
        <h3>$
        {data['baby']['ion_latex']}
        {right_arrow}
        \\space {data['baby']['latex']}
        + \\space 2 {data['electron']['latex']} $</h3>

        From electron cloud, ${data['alpha']['atomic_symbol']}$-particle gathers two electrons and becomes neutral:
        <h3>$
        {data['alpha']['latex']}
        + \\space 2 {data['electron']['latex']}
        {right_arrow}
        \\space {data['helium']['latex']}  $</h3>

        As the charges are more important for chemical reactions, nuclear physicists prefer to write:
        <h3>$
        {data['parent']['latex']}
        {right_arrow}
        \\space {data['baby']['latex']}
        + \\space {data['helium']['latex']}
        $</h3>
        """
        change_in_mass = data.get('change_in_mass', None)
        if not change_in_mass is None:
            soln_s = soln_s.strip() + f"""
            <h3>Change in mass</h3>

            ${change_in_mass['latex_formula']}$

            $= ({change_in_mass['latex']}) amu$

            $= {change_in_mass['amu']} amu $

            <h3>Energy released</h3>
            The lost mass is converted into energy:

            $E = mc^2$

            $E = {abs(change_in_mass['amu'])} amu * c^2$ \t\t Taking absolute value of lost mass

            """

        answer_s = f"""
        <h3>$
        {data['parent']['latex']}
        {right_arrow}
        \\space {data['baby']['latex']}
        + \\space {data['helium']['latex']}
        $</h3>
        """

        problem_details = {
            'problem_s': problem_s,
            'soln_s': soln_s,
            'answer_s': answer_s
            }

        return problem_details
        pass

    def get_beta_decay(self,
                        parent_atomic_number = None,
                        parent_mass_number = None,
                        ):
        right_arrow = symbols(u'\U00002192')

        if parent_atomic_number is None:
            atomic_numbers = self.get_elements_by_decay_type('beta_decay')
            if len(atomic_numbers) > 0:
                parent_atomic_number, parent_mass_number = random.choice(
                                                            atomic_numbers)
                self._log(f"parent_atomic_number = {parent_atomic_number}")
            else:
                parent_atomic_number = random.randint(60, 125)


        if parent_mass_number is None:
            parent_mass_number = int(parent_atomic_number * (2 + random.random() * .4))

        parent_atomic_symbol = self.get_element_symbol(parent_atomic_number, "X")

        baby_atomic_number = parent_atomic_number + 1
        baby_mass_number = parent_mass_number

        baby_atomic_symbol = self.get_element_symbol(baby_atomic_number, "Y")

        beta_symbol = symbols(r'\beta')

        parent_latex = latex_atom(parent_atomic_symbol,
                                  parent_atomic_number,
                                  parent_mass_number)

        baby_latex = latex_atom(baby_atomic_symbol,
                                  baby_atomic_number,
                                  baby_mass_number)

        beta_latex = latex_atom(beta_symbol, ionic_charge = "-")

        electron_latex = latex_atom('e', ionic_charge = "-")

        antineutrino_latex = symbols(r'\overline{v}_{e}')

        parent_name = self.get_element_name(parent_atomic_number,
                                            parent_atomic_symbol)

        baby_name = self.get_element_name(baby_atomic_number,
                                          baby_atomic_symbol)

        result = {}

        result['parent'] = {'atomic_symbol': parent_atomic_symbol,
                            'name': parent_name,
                            'atomic_number': parent_atomic_number,
                            'mass_number': parent_mass_number,
                            'latex': parent_latex
                            }

        result['baby'] = {'atomic_symbol': baby_atomic_symbol,
                            'name': baby_name,
                            'atomic_number': baby_atomic_number,
                            'mass_number': baby_mass_number,
                            'latex': baby_latex
                            }

        result['beta'] = {'atomic_symbol': beta_symbol,
                            'ionic_charge': '-',
                            'latex': beta_latex
                            }

        result['antineutrino'] = {'latex': antineutrino_latex
                            }

        result['electron'] = {'ionic_charge': '-',
                              'latex': electron_latex
                             }



        result['eq'] = f"""
        {parent_latex} {right_arrow} \\space {baby_latex}
        + \\space {beta_latex} + \\space {antineutrino_latex}
        """.strip()

        return result
        pass

    def get_beta_decay_problem_details(self, data):
        right_arrow = symbols(u'\U00002192')

        problem_s = f"""
        Write equation of beta decay of an atom
        ${data['parent']['latex']}$
        """

        soln_s = f"""
        <h3>$
        {data['parent']['latex']}
        {right_arrow}
        \\space {data['baby']['latex']}
        + \\space {data['beta']['latex']}
        + \\space {data['antineutrino']['latex']}
        $</h3>
        {data['parent']['atomic_symbol']} = {data['parent']['name']}
        {data['baby']['atomic_symbol']} = {data['baby']['name']}

        ${data['beta']['latex']}$ is a beta-particle. It is actually an electron.

        ${data['antineutrino']['latex']}$ is an anti-neutrino.

        {self.get_antineutrino_desc()}

        """
        answer_s = f"""
        <h3>$
        {data['parent']['latex']}
        {right_arrow}
        \\space {data['baby']['latex']}
        + \\space {data['beta']['latex']}
        + \\space {data['antineutrino']['latex']}
        $</h3>
        """

        problem_details = {
            'problem_s': problem_s,
            'soln_s': soln_s,
            'answer_s': answer_s
            }

        return problem_details
        pass


    def get_electron_capture_decay(self,
                        parent_atomic_number = None,
                        parent_mass_number = None,
                        ):
        right_arrow = symbols(u'\U00002192')

        if parent_atomic_number is None:
            parent_atomic_number = random.randint(60, 125)

        if parent_mass_number is None:
            parent_mass_number = int(parent_atomic_number * (2 + random.random() * .4))

        parent_atomic_symbol = self.get_element_symbol(parent_atomic_number, "X")

        baby_atomic_number = parent_atomic_number - 1
        baby_mass_number = parent_mass_number

        baby_atomic_symbol = self.get_element_symbol(baby_atomic_number, "Y")

        beta_symbol = symbols(r'\beta')

        parent_latex = latex_atom(parent_atomic_symbol,
                                  parent_atomic_number,
                                  parent_mass_number)

        baby_latex = latex_atom(baby_atomic_symbol,
                                  baby_atomic_number,
                                  baby_mass_number)

        beta_latex = latex_atom(beta_symbol, ionic_charge = "-")

        electron_latex = latex_atom('e', ionic_charge = "-")

        neutrino_latex = symbols(r'{v}_{e}')

        parent_name = self.get_element_name(parent_atomic_number,
                                            parent_atomic_symbol)

        baby_name = self.get_element_name(baby_atomic_number,
                                          baby_atomic_symbol)

        result = {}

        result['parent'] = {'atomic_symbol': parent_atomic_symbol,
                            'name': parent_name,
                            'atomic_number': parent_atomic_number,
                            'mass_number': parent_mass_number,
                            'latex': parent_latex
                            }

        result['baby'] = {'atomic_symbol': baby_atomic_symbol,
                            'name': baby_name,
                            'atomic_number': baby_atomic_number,
                            'mass_number': baby_mass_number,
                            'latex': baby_latex
                            }

        result['beta'] = {'atomic_symbol': beta_symbol,
                            'ionic_charge': '-',
                            'latex': beta_latex
                            }

        result['neutrino'] = {'latex': neutrino_latex
                            }

        result['electron'] = {'ionic_charge': '-',
                              'latex': electron_latex
                             }

        result['eq'] = f"""
        {parent_latex} + \\space {beta_latex} {right_arrow} \\space {baby_latex}
         + \\space {neutrino_latex}
        """.strip()

        return result
        pass


    def get_electron_capture_decay_problem_details(self, data):
        right_arrow = symbols(u'\U00002192')

        problem_s = f"""
        Write equation of beta decay of an atom
        ${data['parent']['latex']}$
        """

        soln_s = f"""
        <h3>$
        {data['parent']['latex']}
        + \\space {data['beta']['latex']}
        {right_arrow}
        \\space {data['baby']['latex']}
        + \\space {data['neutrino']['latex']}
        $</h3>
        {data['parent']['atomic_symbol']} = {data['parent']['name']}
        {data['baby']['atomic_symbol']} = {data['baby']['name']}

        ${data['beta']['latex']}$ is a beta-particle. It is actually an electron.

        ${data['neutrino']['latex']}$ is an neutrino.

        {self.get_neutrino_desc()}

        """
        answer_s = f"""
        <h3>$
        {data['parent']['latex']}
        + \\space {data['beta']['latex']}
        {right_arrow}
        \\space {data['baby']['latex']}
        + \\space {data['neutrino']['latex']}
        $</h3>
        """

        problem_details = {
            'problem_s': problem_s,
            'soln_s': soln_s,
            'answer_s': answer_s
            }

        return problem_details
        pass

    def get_positron_decay(self,
                        parent_atomic_number = None,
                        parent_mass_number = None,
                        ):
        right_arrow = symbols(u'\U00002192')

        if parent_atomic_number is None:
            atomic_numbers = self.get_elements_by_decay_type('positron_decay')
            if len(atomic_numbers) > 0:
                parent_atomic_number, parent_mass_number = random.choice(
                                                            atomic_numbers)
                self._log(f"parent_atomic_number = {parent_atomic_number}")
            else:
                parent_atomic_number = random.randint(60, 125)


        if parent_mass_number is None:
            parent_mass_number = int(parent_atomic_number * (2 + random.random() * .4))

        parent_atomic_symbol = self.get_element_symbol(parent_atomic_number, "X")

        baby_atomic_number = parent_atomic_number - 1
        baby_mass_number = parent_mass_number

        baby_atomic_symbol = self.get_element_symbol(baby_atomic_number, "Y")

        beta_symbol = symbols(r'\beta')

        parent_latex = latex_atom(parent_atomic_symbol,
                                  parent_atomic_number,
                                  parent_mass_number)

        baby_latex = latex_atom(baby_atomic_symbol,
                                  baby_atomic_number,
                                  baby_mass_number)

        positron_latex = latex_atom(beta_symbol, ionic_charge = "+")

        electron_latex = latex_atom('e', ionic_charge = "-")

        neutrino_latex = symbols(r'{v}_{e}')

        parent_name = self.get_element_name(parent_atomic_number,
                                            parent_atomic_symbol)

        baby_name = self.get_element_name(baby_atomic_number,
                                          baby_atomic_symbol)

        result = {}

        result['parent'] = {'atomic_symbol': parent_atomic_symbol,
                            'name': parent_name,
                            'atomic_number': parent_atomic_number,
                            'mass_number': parent_mass_number,
                            'latex': parent_latex
                            }

        result['baby'] = {'atomic_symbol': baby_atomic_symbol,
                            'name': baby_name,
                            'atomic_number': baby_atomic_number,
                            'mass_number': baby_mass_number,
                            'latex': baby_latex
                            }

        result['positron'] = {'atomic_symbol': beta_symbol,
                            'ionic_charge': '+',
                            'latex': positron_latex
                            }

        result['neutrino'] = {'latex': neutrino_latex
                            }

        result['electron'] = {'ionic_charge': '-',
                              'latex': electron_latex
                             }



        result['eq'] = f"""
        {parent_latex} {right_arrow} \\space {baby_latex}
        + \\space {positron_latex} + \\space {neutrino_latex}
        """.strip()

        return result
        pass

    def get_positron_decay_problem_details(self, data):
        right_arrow = symbols(u'\U00002192')

        problem_s = f"""
        Write equation of positron decay of an atom
        ${data['parent']['latex']}$
        """

        soln_s = f"""
        <h3>$
        {data['parent']['latex']}
        {right_arrow}
        \\space {data['baby']['latex']}
        + \\space {data['positron']['latex']}
        + \\space {data['neutrino']['latex']}
        $</h3>
        {data['parent']['atomic_symbol']} = {data['parent']['name']}
        {data['baby']['atomic_symbol']} = {data['baby']['name']}

        ${data['positron']['latex']}$ is a beta-positve-particle. It is actually a positron.

        ${data['neutrino']['latex']}$ is an neutrino.

        {self.get_neutrino_desc()}
        """
        answer_s = f"""
        <h3>$
        {data['parent']['latex']}
        {right_arrow}
        \\space {data['baby']['latex']}
        + \\space {data['positron']['latex']}
        + \\space {data['neutrino']['latex']}
        $</h3>
        """

        problem_details = {
            'problem_s': problem_s,
            'soln_s': soln_s,
            'answer_s': answer_s
            }

        return problem_details
        pass


    def get_positron_gamma_decay_problem_details(self,
                                                parent_atomic_number,
                                                parent_mass_number):
        try:
            right_arrow = symbols(u'\U00002192')

            decay_data1 = self.get_positron_decay(parent_atomic_number, parent_mass_number)

            stage1_atomic_number = decay_data1['baby']['atomic_number']
            stage1_mass_number = decay_data1['baby']['mass_number']

            decay_data2 = self.get_gamma_decay(stage1_atomic_number, stage1_mass_number)

            problem_s = f"""
            Write equation of positron decay of an atom
            ${decay_data1['parent']['latex']}$
            """

            answer_s = f"""
            <h3>$
            {decay_data1['parent']['latex']}
            {right_arrow}
            \\space {decay_data1['baby']['latex']}^{{*}}
            + \\space {decay_data1['positron']['latex']}
            + \\space {decay_data1['neutrino']['latex']}
            $</h3><h3>$
            {decay_data2['parent']['latex']}
            {right_arrow}
            \\space {decay_data2['baby']['latex']}
            + \\space {decay_data2['gamma']['latex']}_{1}
            + \\space {decay_data2['gamma']['latex']}_{2}
            + \\space ...
            $</h3>
            * symbol on ${decay_data2['parent']['latex']}$ shows that it is in excited state.
            """


            soln_s = f"""
            {answer_s.strip()}

            {decay_data1['parent']['atomic_symbol']} = {decay_data1['parent']['name']}
            {decay_data2['baby']['atomic_symbol']} = {decay_data2['baby']['name']}

            ${decay_data1['positron']['latex']}$ is a beta-positve-particle. It is actually a positron.

            ${decay_data1['neutrino']['latex']}$ is an neutrino.

            ${decay_data2['gamma']['latex']}$ is a gamma-particle. More than one gamma particles are released.
            """
            problem_details = {
                'problem_s': problem_s,
                'soln_s': soln_s,
                'answer_s': answer_s
                }
            return problem_details
        except Exception as e:
            sError = f"""Error {self.__class__.__name__}.{inspect.stack()[0][3]}: {e}
            parent_atomic_number = {parent_atomic_number}
            parent_mass_number = {parent_mass_number}
            """
            self._log(sError)
            self._log(traceback.format_exc())
            problem_details = {
                'problem_s': f"{e}",
                'soln_s': None,
                'answer_s': None
                }
            return problem_details
            pass
        pass



    def get_gamma_decay(self,
                        parent_atomic_number = None,
                        parent_mass_number = None,
                        ):
        right_arrow = symbols(u'\U00002192')


        if parent_atomic_number is None:
            parent_atomic_number = random.randint(60, 125)

        if parent_mass_number is None:
            parent_mass_number = int(parent_atomic_number * (2 + random.random() * .4))

        parent_atomic_symbol = self.get_element_symbol(parent_atomic_number, "X")

        baby_atomic_number = parent_atomic_number
        baby_mass_number = parent_mass_number

        baby_atomic_symbol = parent_atomic_symbol

        gamma_symbol = symbols(r'\gamma')

        parent_latex = latex_atom(parent_atomic_symbol,
                                  parent_atomic_number,
                                  parent_mass_number,
                                  ionic_charge = "*")

        baby_latex = latex_atom(baby_atomic_symbol,
                                  baby_atomic_number,
                                  baby_mass_number)

        gamma_latex = latex_atom(gamma_symbol)

        parent_name = self.get_element_name(parent_atomic_number,
                                            parent_atomic_symbol)

        baby_name = self.get_element_name(baby_atomic_number,
                                          baby_atomic_symbol)

        result = {}

        result['parent'] = {'atomic_symbol': parent_atomic_symbol,
                            'name': parent_name,
                            'atomic_number': parent_atomic_number,
                            'mass_number': parent_mass_number,
                            'latex': parent_latex
                            }

        result['baby'] = {'atomic_symbol': baby_atomic_symbol,
                            'name': baby_name,
                            'atomic_number': baby_atomic_number,
                            'mass_number': baby_mass_number,
                            'latex': baby_latex
                            }

        result['gamma'] = {'atomic_symbol': gamma_symbol,
                            'latex': gamma_latex
                            }

        result['eq'] = f"""
        {parent_latex} {right_arrow} \\space {baby_latex}
        + \\space {gamma_latex}_1 + \\space {gamma_latex}_2 + ...
        """.strip()

        return result
        pass

    def get_gamma_decay_problem_details(self, data):
        right_arrow = symbols(u'\U00002192')

        problem_s = f"""
        Write equation of gamma decay of an atom
        ${data['parent']['latex']}$
        """

        soln_s = f"""
        <h3>$
        {data['parent']['latex']}
        {right_arrow}
        \\space {data['baby']['latex']}
        + \\space {data['gamma']['latex']}_{1}
        + \\space {data['gamma']['latex']}_{2}
        + \\space ...
        $</h3>
        * symbol on ${data['parent']['latex']}$ shows that it is in excited state.

        {data['parent']['atomic_symbol']} = {data['parent']['name']}
        {data['baby']['atomic_symbol']} = {data['baby']['name']}

        ${data['gamma']['latex']}$ is a gamma-particle. More than one gamma particles are released.

        <a target="_blank" href="https://www.radioactivity.eu.com/site/pages/Radioactivity_Gamma.htm">Cobalt decay</a>
        <a target="_blank" href="https://imagine.gsfc.nasa.gov/science/toolbox/spectrum_chart.html">Spectrum range</a>
        """
        answer_s = f"""
        <h3>$
        {data['parent']['latex']}
        {right_arrow}
        \\space {data['baby']['latex']}
        + \\space {data['gamma']['latex']}_{1}
        + \\space {data['gamma']['latex']}_{2}
        + \\space ...
        $</h3>
        """

        problem_details = {
            'problem_s': problem_s,
            'soln_s': soln_s,
            'answer_s': answer_s
            }

        return problem_details
        pass

    def get_gram_to_mole(self,
                        atomic_number = None,
                        ):
        #from math import ceil
        if atomic_number is None:
            atomic_number = random.randint(1, 118)


        atomic_symbol = self.get_element_symbol(atomic_number, self.element_lib(atomic_number).symbol)
        mass_number = int(self.atomic_weight['symbol'][atomic_number])
        element_latex = latex_atom(atomic_symbol,
                                   atomic_number,
                                   mass_number,
                                   ionic_charge = "*")

        avogadro_symbol = 'N_A'
        avogadro_value = 6.022e23

        gram_symbol = 'g'
        no_of_gram = random.randint(1, 118)

        mol_symbol = 'mol'
        no_of_mol = no_of_gram/mass_number

        result = {}
        result['element'] = {'atomic_symbol': atomic_symbol,
                             'atomic_number': atomic_number,
                             'mass_number': mass_number,
                             'latex': element_latex
                             }
        result['avogadro'] = {'symbol': avogadro_symbol,
                              'value': avogadro_value,
                              'latex': avogadro_symbol
                              }
        result['gram'] = {'symbol': gram_symbol,
                          'number': no_of_gram,
                          }

        result['mol'] = {'symbol': mol_symbol,
                          'number': no_of_mol,
                          }

        result['eq'] = f"""
        dfrac{{{no_of_gram}{gram_symbol}}}{{{mass_number}{gram_symbol}}} = {no_of_mol} {mol_symbol}
        """.strip()

        return result
        pass

    def get_gram_to_mole_problem_details(self, data):
        right_arrow = symbols(u'\U00002192')

        problem_s = f"""
        Write equation of gamma decay of an atom
        ${data['parent']['latex']}$
        """

        soln_s = f"""
        <h3>$
        {data['parent']['latex']}
        {right_arrow}
        \\space {data['baby']['latex']}
        + \\space {data['gamma']['latex']}_{1}
        + \\space {data['gamma']['latex']}_{2}
        + \\space ...
        $</h3>
        * symbol on ${data['parent']['latex']}$ shows that it is in excited state.

        ${data['gamma']['latex']}$ is a gamma-particle. More than one gamma particles are released.
        """
        answer_s = f"""
        <h3>$
        {data['parent']['latex']}
        {right_arrow}
        \\space {data['baby']['latex']}
        + \\space {data['gamma']['latex']}_{1}
        + \\space {data['gamma']['latex']}_{2}
        + \\space ...
        $</h3>
        """

        problem_details = {
            'problem_s': problem_s,
            'soln_s': soln_s,
            'answer_s': answer_s
            }

        return problem_details
        pass


    def get_antineutrino_desc(self):
        desc = f"""
        Antineutrinos are the antiparticles of neutrinos. The antineutrino is an elementary subatomic particle with infinitesimal mass (less than 0.3eV..?) and with no electric charge. Neutrinos and antineutrinos belong to the family of leptons, which means they do not interact via strong nuclear force. Neutrinos are gravitational and weakly interacting subatomic particles with ½ unit of spin. Also antineutrinos (as neutrinos) are very penetrating subatomic particles, capable of passing through Earth without any interaction. Currently (2015), it is not resolved, whether the neutrino and its antiparticle are not identical particles.
        (Ref: <a target="_blank" href="https://www.nuclear-power.com/nuclear-power/reactor-physics/atomic-nuclear-physics/fundamental-particles/antineutrino/">radiation-dosimetry.org</a>)
        """
        return desc.strip()

    def get_neutrino_desc(self):
        desc = f"""
        Read about neutrino at: <a target="_blank" href="https://neutrino-history.in2p3.fr/neutrino-properties/">History of the Neutrino</a>
        """
        return desc.strip()



    def _get_file_path(self,
                       file_name,
                       rootFolder = None,
                       parent_depth = 3
                       ):
        if file_name is None:
            return None

        if rootFolder is None:
            rootFolder = pathlib.Path(__file__).parent.absolute()

        parent_depth = 3
        file_path = locatePath(file_name,
                               rootFolder = rootFolder,
                               parent_depth = parent_depth,
                               part_match = False,
                               verbose = self.verbose
                               )
        return file_path

    def parse_atomic_input(self, **kwargs):
        element_symbol = kwargs.get('element_symbol', None)
        atomic_number = kwargs.get('atomic_number', None)
        mass_number = kwargs.get('mass_number', None)

        if element_symbol is None and atomic_number is None:
            atomic_input = {
                'error': 'element_symbol or atomic_number is None.',
                'success': False
                }
            return atomic_input

        if mass_number is None:
            atomic_input = {
                'error': 'mass_number is None.',
                'success': False
                }
            return atomic_input

        try:
            if not element_symbol is None:
                elem = self.element_lib(element_symbol)
                atomic_number = elem.atomic_number
                atomic_input = {
                    'element_symbol': element_symbol,
                    'atomic_number': atomic_number,
                    'mass_number': mass_number,
                    'success': True
                    }
                return atomic_input

            if not atomic_number is None:
                elem = self.element_lib(atomic_number)
                element_symbol = elem.symbol
                atomic_input = {
                    'element_symbol': element_symbol,
                    'atomic_number': atomic_number,
                    'mass_number': mass_number,
                    'success': True
                    }
                return atomic_input

        except Exception as e:
            sError = f"""Error {self.__class__.__name__}.{inspect.stack()[0][3]}: {e}
            """
            self._log(sError)
            self._log(traceback.format_exc())
            atomic_input = {
                'error': f'{e}',
                'success': False
                }
            return atomic_input
            pass

        atomic_input = {
            'error': f'Unknown error',
            'success': False
            }
        return atomic_input
        pass

    def get_isotopes(self):
        if self.element_lib is None:
            return None

        from mendeleev.fetch import fetch_table
        isotopes = fetch_table('isotopes', index_col='id')
        return isotopes
        pass

    def get_radioactive_atomic_numbers(self):
        df = self.get_isotopes()
        atomic_numbers = set(df[df['is_radioactive']]['atomic_number'].to_list())
        return atomic_numbers

    def get_radioactivity_data_from_ptable_util(self):
        from xv.chemistry.core import PtableUtil

        ptable_util = PtableUtil(verbose = self.verbose)

        radioactivity_data = ptable_util.get_radioactivity_data()
        return radioactivity_data
        pass

    def get_radioactive_isotopes(self):
        import radioactivedecay as rd


        nuclides = set(rd.DEFAULTDATA.nuclides)

        df = self.get_radioactivity_data_from_ptable_util()
        radioactive_df = df[['element_symb', 'mass_number', 'atomic_number']]
        isotopes = radioactive_df.values.tolist()

        radioactive_isotopes = []
        for isotope in isotopes:
            if not f"{isotope[0]}-{isotope[1]}" in nuclides:
                continue

            radioactive_isotopes.append([isotope[2], isotope[1]])
            pass
        return radioactive_isotopes


    def get_problem_derive_radioactive_decay_equation(self, **kwargs):
        """
        """
        import sympy as sp

        from sympy import (symbols, Symbol, Function,
                           UnevaluatedExpr,
                           Eq, dsolve, solve,
                           latex, Integral, log,
                           )
        t = symbols('t')
        N = Function('N')
        N = N(t)
        dN_dt = N.diff(t)
        k = Symbol("\lambda")

        t0, t1 = symbols('t_0 t_1')
        t_low = t0
        t_high = t1

        N0, N1 = symbols('N_0 N_1')
        N_low = N0
        N_high = N1

        eq0 = Eq(-dN_dt, k * N)

        prop_rel_N_t = eq0.get_prop_rel(N, t)

        eq_symb = Eq( Symbol(f""" {latex(1 / N)} d{latex(N)} """) ,
                 - Symbol(f""" {latex(k)} d{latex(t)} """) )
        #(eq0 / N).doit()

        eq = Eq(1/N, -k)

        eq_integral = Eq(Integral(eq.lhs, (N, N_low, N)),
                        eq.rhs * Integral(1, (t, t_low, t)),
                        )

        eq_integral_doit = eq_integral.doit()

        eq_exp_form = Eq(N / N0 , sp.exp(-k * (t - t0)) )
        eq_exp_form_2 = Eq(N , N0 * sp.exp(-k * (t - t0)) )


        eq_dsoln_subs_0 = eq_exp_form_2.subs({t0: 0})


        self.eqs = [
            eq0,
            eq,
            eq_integral,
            eq_integral_doit,
            eq_dsoln_subs_0,
            ]

        labels = {
            N: f"number of radioactive nuclei at point ${latex(t)}$",
            t: "time",
            k: "decay constant",
            -dN_dt: f"rate of decay of nuclei with respect to time"
        }
        labels_s = eq.show_labels(labels)


        prob_s = f"""
        <div style="line-height:1.8em;">
        Find relation between ${latex(N)}$ and ${latex(t)}$
        where:
        {labels_s}
        </div>
        """

        soln_stmts = []

        soln_stmt = f"""
        The {labels[-dN_dt]} is proportional to {labels[N]}.
        """
        soln_stmts.append(soln_stmt.strip())


        soln_stmt = f"""
        $\\displaystyle {prop_rel_N_t}$

        $\\displaystyle \\implies {latex(eq0)}$

        $\\displaystyle \\implies {latex(eq_symb)}$

        $\\displaystyle \\implies {latex(eq_integral)}$

        $\\displaystyle \\implies {latex(eq_integral_doit)}$

        $\\displaystyle \\implies {latex(eq_exp_form)}
        \\text {{ converting in exponential form }}
        $

        $\\displaystyle \\implies {latex(eq_exp_form_2)}$

        $\\displaystyle \\implies {latex(eq_dsoln_subs_0)}
        \\text {{ substituting }} {latex(t0)} = 0
        $

        """
        soln_stmts.append(soln_stmt.strip())

        soln_s = "\n\n".join(soln_stmts)

        answer_s = f"""
        $\\displaystyle {latex(eq_dsoln_subs_0)}$
        """

        result = {
            'prob_s': prob_s.strip(),
            'soln_s': soln_s.strip(),
            'answer_s': answer_s.strip(),
            }

        return result
        pass





    pass
#class RadioActivityHelper:

