"""Function-like objects creating orthorhombic lattices.

The following lattice creators are defined:
    SimleOrthorhombic
    BaseCenteredOrthorhombic
    BodyCenteredOrthorhombic
    FaceCenteredOrthorhombic
"""

from ase.lattice.bravais import Bravais
import numpy as np
from ase.data import reference_states as _refstate


class SimpleOrthorhombicFactory(Bravais):
    "A factory for creating simple orthorhombic lattices."

    # The name of the crystal structure in ChemicalElements
    xtal_name = "orthorhombic"

    # The natural basis vectors of the crystal structure
    int_basis = np.array([[1, 0, 0],
                          [0, 1, 0],
                          [0, 0, 1]])
    basis_factor = 1.0

    # Converts the natural basis back to the crystallographic basis
    inverse_basis = np.array([[1, 0, 0],
                              [0, 1, 0],
                              [0, 0, 1]])
    inverse_basis_factor = 1.0

    def get_lattice_constant(self):
        "Get the lattice constant of an element with orhtorhombic crystal structure."
        if _refstate[self.atomicnumber]['symmetry'] != self.xtal_name:
            raise ValueError(("Cannot guess the %s lattice constant of"
                                + " an element with crystal structure %s.")
                               % (self.xtal_name,
                                  _refstate[self.atomicnumber]['symmetry']))
        return _refstate[self.atomicnumber].copy()

    def make_crystal_basis(self):
        "Make the basis matrix for the crystal unit cell and the system unit cell."
        lattice = self.latticeconstant
        if isinstance(lattice, type({})):
            a = lattice['a']
            try:
                b = lattice['b']
            except KeyError:
                b = a * lattice['b/a']
            try:
                c = lattice['c']
            except KeyError:
                c = a * lattice['c/a']
        else:
            if len(lattice) == 3:
                (a, b, c) = lattice
            else:
                raise ValueError("Improper lattice constants for orthorhombic crystal.")

        lattice = np.array([[a, 0, 0], [0, b, 0], [0, 0, c]])
        self.latticeconstant = lattice
        self.miller_basis = lattice
        self.crystal_basis = (self.basis_factor *
                              np.dot(self.int_basis, lattice))
        self.basis = np.dot(self.directions, self.crystal_basis)
        self.check_basis_volume()

    def check_basis_volume(self):
        "Check the volume of the unit cell."
        vol1 = abs(np.linalg.det(self.basis))
        vol2 = self.calc_num_atoms() * np.linalg.det(self.latticeconstant)
        if self.bravais_basis is not None:
            vol2 /= len(self.bravais_basis)
        if abs(vol1-vol2) > 1e-5:
            print("WARNING: Got volume %f, expected %f" % (vol1, vol2))


SimpleOrthorhombic = SimpleOrthorhombicFactory()


class BaseCenteredOrthorhombicFactory(SimpleOrthorhombicFactory):
    "A factory for creating base-centered orthorhombic lattices."

    # The natural basis vectors of the crystal structure
    int_basis = np.array([[1, -1, 0],
                          [1, 1, 0],
                          [0, 0, 2]])
    basis_factor = 0.5

    # Converts the natural basis back to the crystallographic basis
    inverse_basis = np.array([[1, 1, 0],
                              [-1, 1, 0],
                              [0, 0, 1]])
    inverse_basis_factor = 1.0

    def check_basis_volume(self):
        "Check the volume of the unit cell."
        vol1 = abs(np.linalg.det(self.basis))
        vol2 = self.calc_num_atoms() * np.linalg.det(self.latticeconstant) / 2.0
        if abs(vol1-vol2) > 1e-5:
            print("WARNING: Got volume %f, expected %f" % (vol1, vol2))


BaseCenteredOrthorhombic = BaseCenteredOrthorhombicFactory()


class BodyCenteredOrthorhombicFactory(SimpleOrthorhombicFactory):
    "A factory for creating body-centered orthorhombic lattices."

    int_basis = np.array([[-1, 1, 1],
                          [1, -1, 1],
                          [1, 1, -1]])
    basis_factor = 0.5
    inverse_basis = np.array([[0, 1, 1],
                              [1, 0, 1],
                              [1, 1, 0]])
    inverse_basis_factor = 1.0

    def check_basis_volume(self):
        "Check the volume of the unit cell."
        vol1 = abs(np.linalg.det(self.basis))
        vol2 = self.calc_num_atoms() * np.linalg.det(self.latticeconstant) / 2.0
        if abs(vol1-vol2) > 1e-5:
            print("WARNING: Got volume %f, expected %f" % (vol1, vol2))


BodyCenteredOrthorhombic = BodyCenteredOrthorhombicFactory()


class FaceCenteredOrthorhombicFactory(SimpleOrthorhombicFactory):
    "A factory for creating face-centered orthorhombic lattices."

    int_basis = np.array([[0, 1, 1],
                          [1, 0, 1],
                          [1, 1, 0]])
    basis_factor = 0.5
    inverse_basis = np.array([[-1, 1, 1],
                              [1, -1, 1],
                              [1, 1, -1]])
    inverse_basis_factor = 1.0

    def check_basis_volume(self):
        "Check the volume of the unit cell."
        vol1 = abs(np.linalg.det(self.basis))
        vol2 = self.calc_num_atoms() * np.linalg.det(self.latticeconstant) / 4.0
        if abs(vol1-vol2) > 1e-5:
            print("WARNING: Got volume %f, expected %f" % (vol1, vol2))


FaceCenteredOrthorhombic = FaceCenteredOrthorhombicFactory()
