######################################################################
# BioSimSpace: Making biomolecular simulation a breeze!
#
# Copyright: 2017-2025
#
# Authors: Lester Hedges <lester.hedges@gmail.com>
#
# BioSimSpace is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# BioSimSpace is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with BioSimSpace. If not, see <http://www.gnu.org/licenses/>.
#####################################################################
"""A charge type."""
__author__ = "Lester Hedges"
__email__ = "lester.hedges@gmail.com"
__all__ = ["Charge"]
from sire.legacy import Units as _SireUnits
from ._type import Type as _Type
[docs]
class Charge(_Type):
    """A charge type."""
    # A list of the supported Sire unit names.
    _sire_units = ["e_charge", "coulomb"]
    # Dictionary of allowed units.
    _supported_units = {
        "ELECTRON CHARGE": _SireUnits.e_charge,
        "COULOMB": _SireUnits.coulomb,
    }
    # Map unit abbreviations to the full name.
    _abbreviations = {"E": "ELECTRON CHARGE", "C": "COULOMB"}
    # Print formatting.
    _print_format = {"ELECTRON CHARGE": "|e|", "COULOMB": "C"}
    # Documentation strings.
    _doc_strings = {
        "ELECTRON CHARGE": "A charge in electron charge.",
        "COULOMB": "A charge in Coulomb.",
    }
    # Null type unit for avoiding issue printing configargparse help.
    _default_unit = "ELECTRON CHARGE"
    # The dimension mask.
    _dimensions = tuple(list(_supported_units.values())[0].dimensions())
[docs]
    def __init__(self, *args):
        """
        Constructor.
        ``*args`` can be a value and unit, or a string representation
        of the charge, e.g. "0.2 e".
        Parameters
        ----------
        value : float
            The value.
        unit : str
            The unit.
        string : str
            A string representation of the charge.
        Examples
        --------
        Create an object representing a charge of 27.8 electron charge
        print the charge in Coulomb.
        >>> import BioSimSpace as BSS
        >>> charge = BSS.Types.Charge(27.8, "e")
        >>> print(charge.coulomb())
        The same as above, except passing a string representation of the
        charge to the constructor.
        >>> import BioSimSpace as BSS
        >>> charge = BSS.Types.Charge("3.1 atm")
        >>> print(charge.coulomb())
        The string matching is extremeley flexible, so all of the following
        would be valid arguments: "27.8 e", "27.8 electron charge",
        "2.78e1 e".
        """
        # Call the base class constructor.
        super().__init__(*args) 
[docs]
    def electron_charge(self):
        """
        Return the charge in electron charge.
        Returns
        -------
        charge : :class:`Charge <BioSimSpace.Types.Charge>`
            The charge in electron charge.
        """
        return Charge(
            (self._value * self._supported_units[self._unit]).to(_SireUnits.e_charge),
            "ELECTRON CHARGE",
        ) 
[docs]
    def coulomb(self):
        """
        Return the charge in Coulomb.
        Returns
        -------
        charge : :class:`Charge <BioSimSpace.Types.Charge>`
            The charge in Coulomb.
        """
        return Charge(
            (self._value * self._supported_units[self._unit]).to(_SireUnits.coulomb),
            "COULOMB",
        ) 
    def _to_default_unit(self, mag=None):
        """
        Internal method to return an object of the same type in the default unit.
        Parameters
        ----------
        mag : float
            The value (optional).
        Returns
        -------
        charge : :class:`Charge <BioSimSpace.Types.Charge>`
            The charge in the default unit of electron charge.
        """
        if mag is None:
            return self.electron_charge()
        else:
            return Charge(mag, "ELECTRON CHARGE")
    def _convert_to(self, unit):
        """
        Return the charge in a different unit.
        Parameters
        ----------
        unit : str
            The unit to convert to.
        Returns
        -------
        charge : :class:`Charge <BioSimSpace.Types.Charge>`
            The charge in the specified unit.
        """
        if unit == "ELECTRON CHARGE":
            return self.electron_charge()
        elif unit == "COULOMB":
            return self.coulomb()
        else:
            raise ValueError(
                "Supported units are: '%s'" % list(self._supported_units.keys())
            )
    @classmethod
    def _validate_unit(cls, unit):
        """Validate that the unit are supported."""
        # Strip whitespace and convert to upper case.
        unit = unit.replace(" ", "").upper()
        # Replace all instances of ELECTRON with E.
        unit = unit.replace("ELECTRON", "E")
        # Replace all instances of ELECT with E.
        unit = unit.replace("ELECT", "E")
        # Replace all instances of ELEC with E.
        unit = unit.replace("ELEC", "E")
        # Strip all instances of CHARGE.
        unit = unit.replace("CHARGE", "")
        # Strip all instances of -.
        unit = unit.replace("-", "")
        # Strip all instances of S.
        unit = unit.replace("S", "")
        # Replace all instance of COULOMB with C.
        unit = unit.replace("COULOMB", "C")
        # Replace all instance of COUL with C.
        unit = unit.replace("COUL", "C")
        # Check that the unit is supported.
        if unit in cls._abbreviations:
            return cls._abbreviations[unit]
        else:
            raise ValueError(
                "Supported units are: '%s'" % list(cls._supported_units.keys())
            )
    @staticmethod
    def _to_sire_format(unit):
        """
        Reformat the unit string so it adheres to the Sire unit formatting.
        Parameters
        ----------
        unit : str
            A string representation of the unit.
        Returns
        -------
        sire_unit : str
            The unit string in Sire compatible format.
        """
        unit = unit.replace("coulombs", "coulomb")
        unit = unit.replace("electroncharge", "e_charge")
        unit = unit.replace("echarge", "e_charge")
        # Convert powers. (Just 2nd and third for now.)
        unit = unit.replace("coloumb2", "(coulomb*coulomb)")
        unit = unit.replace("coloumb3", "(coulomb*coulomb*coulomb)")
        unit = unit.replace("coloumb-1", "(1/coulomb)")
        unit = unit.replace("coloumb-2", "(1/(coulomb*coulomb))")
        unit = unit.replace("coloumb3", "(1/(coulomb*coulomb*coulomb))")
        unit = unit.replace("e_charge2", "(e_charge*e_charge)")
        unit = unit.replace("e_charge3", "(e_charge*e_charge*e_charge)")
        unit = unit.replace("e_charge-1", "(1/e_charge)")
        unit = unit.replace("e_charge-2", "(1/(e_charge*e_charge))")
        unit = unit.replace("e_charge-3", "(1/(e_charge*e_charge*e_charge))")
        return unit