"""Classes for handling user-defined energy terms"""

from modeller.util.modlist import LinkList
import _modeller

class energy_term(object):
    """Base class for user-defined energy terms. Override the L{eval} method
       and the L{_physical_type} member in your own subclasses."""

    _physical_type = None
    _cutoff = 0.0

    def _add_term(self, edat, indx):
        """Register this energy term with Modeller"""
        _modeller.mod_energy_term_new(edat, indx, self.eval, self._cutoff,
                                      self._physical_type.get_type())

    def eval(self, mdl, deriv, indats):
        raise NotImplementedError("No Python function for energy term")

    def indices_to_atoms(self, mdl, atom_indices):
        """Converts Modeller-style atom indices into atom objects"""
        return [mdl.atoms[x-1] for x in atom_indices]

class TermList(LinkList):
    """A list of L{energy_term} objects"""
    def __init__(self, edat):
        self.__edat = edat
        self.__list = []
        LinkList.__init__(self)

    def __setstate__(self, d):
        self.__dict__.update(d)
        for (indx, obj) in enumerate(self.__list):
            obj._add_term(self.__edat(), indx)

    def _insfunc(self, indx, obj):
        obj._add_term(self.__edat(), indx)
        self.__list.insert(indx, obj)
    def __len__(self):
        return len(self.__list)
    def _getfunc(self, indx):
        return self.__list[indx]
    def _delfunc(self, indx):
        del self.__list[indx]
        _modeller.mod_energy_term_del(self.__edat(), indx)

class AssessEnergyTerm(energy_term):
    """Base class for an energy term that can also be used for assessment."""

    _edat = None

    def _assess(self, atmsel, schedule_scale=None, **vars):
        if schedule_scale is None:
            schedule_scale = self._get_schedule_scale()
        return atmsel.energy(edat=self._get_energy_data_cached(),
                             schedule_scale=schedule_scale, **vars)

    def __call__(self, atmsel):
        return (self.name, atmsel.assess(self))

    def _get_energy_data(self):
        from modeller import energy_data
        return energy_data(contact_shell=-999, dynamic_sphere=False)

    def _get_energy_data_cached(self):
        if self._edat is None:
            self._edat = self._get_energy_data_all()
        return self._edat

    def _get_energy_data_all(self):
        edat = self._get_energy_data()
        edat.energy_terms.append(self)
        return edat

    def _get_schedule_scale(self):
        from modeller import physical
        schedule_scale = physical.values(default=0.)
        schedule_scale[self._group] = 1.
        return schedule_scale
