# -*- coding: utf-8 -*-
from __future__ import (absolute_import, division, print_function,
                        unicode_literals, with_statement)

from chemcoord.cartesian_coordinates._cartesian_class_core import CartesianCore
from chemcoord.cartesian_coordinates.point_group import PointGroupOperations


class CartesianSymmetry(CartesianCore):
    def _get_point_group_analyzer(self, tolerance=0.3):
        from pymatgen.symmetry.analyzer import PointGroupAnalyzer
        return PointGroupAnalyzer(self.get_pymatgen_molecule(),
                                  tolerance=tolerance)

    def _convert_eq(self, eq):
        """WORKS INPLACE on eq
        """
        rename = dict(enumerate(self.index))
        eq['eq_sets'] = {rename[k]: {rename[x] for x in v}
                         for k, v in eq['eq_sets'].items()}
        eq['sym_ops'] = {rename[k]: {rename[x]: v[x] for x in v}
                         for k, v in eq['sym_ops'].items()}
        try:
            sym_mol = self.from_pymatgen_molecule(eq['sym_mol'])
            sym_mol.index = self.index
            eq['sym_mol'] = sym_mol._to_numeric()
        except KeyError:
            pass

    def get_pointgroup(self, tolerance=0.3):
        """Returns a PointGroup object for the molecule.

        Args:
            tolerance (float): Tolerance to generate the full set of symmetry
                operations.

        Returns:
            :class:`~PointGroupOperations`

        """
        PA = self._get_point_group_analyzer(tolerance=tolerance)
        return PointGroupOperations(PA.sch_symbol, PA.symmops)

    def get_equivalent_atoms(self, tolerance=0.3):
        """Returns sets of equivalent atoms with symmetry operations

        Args:
            tolerance (float): Tolerance to generate the full set of symmetry
                operations.

        Returns:
            dict: The returned dictionary has two possible keys:

            ``eq_sets``:
            A dictionary of indices mapping to sets of indices,
            each key maps to indices of all equivalent atoms.
            The keys are guaranteed to be not equivalent.

            ``sym_ops``:
            Twofold nested dictionary.
            ``operations[i][j]`` gives the symmetry operation
            that maps atom ``i`` unto ``j``.
        """
        PA = self._get_point_group_analyzer(tolerance=tolerance)
        eq = PA.get_equivalent_atoms()
        self._convert_eq(eq)
        return eq

    def symmetrize(self, max_n=10, tolerance=0.3, epsilon=1e-3):
        """Returns a symmetrized molecule

        The equivalent atoms obtained via
        :meth:`~Cartesian.get_equivalent_atoms`
        are rotated, mirrored... unto one position.
        Then the average position is calculated.
        The average position is rotated, mirrored... back with the inverse
        of the previous symmetry operations, which gives the
        symmetrized molecule.
        This operation is repeated iteratively ``max_n`` times at maximum
        until the difference between subsequently symmetrized structures is
        smaller than ``epsilon``.

        Args:
            max_n (int): Maximum number of iterations.
            tolerance (float): Tolerance for detecting symmetry.
                Gets passed as Argument into
                :class:`~pymatgen.analyzer.symmetry.PointGroupAnalyzer`.
            epsilon (float): If the elementwise absolute difference of two
                subsequently symmetrized structures is smaller epsilon,
                the iteration stops before ``max_n`` is reached.

        Returns:
            dict: The returned dictionary has three possible keys:

            ``sym_mol``:
            A symmetrized molecule :class:`~Cartesian`

            ``eq_sets``:
            A dictionary of indices mapping to sets of indices,
            each key maps to indices of all equivalent atoms.
            The keys are guaranteed to be not symmetry-equivalent.

            ``sym_ops``:
            Twofold nested dictionary.
            ``operations[i][j]`` gives the symmetry operation
            that maps atom ``i`` unto ``j``.
        """
        from pymatgen.symmetry.analyzer import iterative_symmetrize

        mg_mol = self.get_pymatgen_molecule()
        eq = iterative_symmetrize(mg_mol, max_n=max_n, tolerance=tolerance,
                                  epsilon=epsilon)
        self._convert_eq(eq)
        return eq

    def get_asymmetric_unit(self, eq=None):
        eq = self.get_equivalent_atoms() if (eq is None) else eq
        new_frame = self.loc[eq['eq_sets'].keys(), :]._frame
        from chemcoord.cartesian_coordinates.asymmetric_unit_cartesian_class \
            import AsymmetricUnitCartesian
        return AsymmetricUnitCartesian(new_frame, _metadata={'eq': eq})
