import unittest
from spglib import (get_symmetry_dataset, find_primitive,
                    get_spacegroup_type, standardize_cell,
                    get_pointgroup)
from vasp import read_vasp
import yaml
import os
import numpy as np

data_dir = os.path.dirname(os.path.abspath(__file__))

dirnames = ('cubic',
            'hexagonal',
            'monoclinic',
            'orthorhombic',
            'tetragonal',
            'triclinic',
            'trigonal',
            'distorted',
            'virtual_structure')

spg_to_hall = [
      1,   2,   3,   6,   9,  18,  21,  30,  39,  57,
     60,  63,  72,  81,  90, 108, 109, 112, 115, 116,
    119, 122, 123, 124, 125, 128, 134, 137, 143, 149,
    155, 161, 164, 170, 173, 176, 182, 185, 191, 197,
    203, 209, 212, 215, 218, 221, 227, 228, 230, 233,
    239, 245, 251, 257, 263, 266, 269, 275, 278, 284,
    290, 292, 298, 304, 310, 313, 316, 322, 334, 335,
    337, 338, 341, 343, 349, 350, 351, 352, 353, 354,
    355, 356, 357, 358, 359, 361, 363, 364, 366, 367,
    368, 369, 370, 371, 372, 373, 374, 375, 376, 377,
    378, 379, 380, 381, 382, 383, 384, 385, 386, 387,
    388, 389, 390, 391, 392, 393, 394, 395, 396, 397,
    398, 399, 400, 401, 402, 404, 406, 407, 408, 410,
    412, 413, 414, 416, 418, 419, 420, 422, 424, 425,
    426, 428, 430, 431, 432, 433, 435, 436, 438, 439,
    440, 441, 442, 443, 444, 446, 447, 448, 449, 450,
    452, 454, 455, 456, 457, 458, 460, 462, 463, 464,
    465, 466, 467, 468, 469, 470, 471, 472, 473, 474,
    475, 476, 477, 478, 479, 480, 481, 482, 483, 484,
    485, 486, 487, 488, 489, 490, 491, 492, 493, 494,
    495, 497, 498, 500, 501, 502, 503, 504, 505, 506,
    507, 508, 509, 510, 511, 512, 513, 514, 515, 516,
    517, 518, 520, 521, 523, 524, 525, 527, 529, 530, 531]


class TestSpglib(unittest.TestCase):

    def setUp(self):
        self._filenames = []
        self._ref_filenames = []
        self._spgnum_ref = []
        for d in dirnames:
            dirname = os.path.join(data_dir, "data", d)
            refdirname = os.path.join(data_dir, "ref", d)
            filenames = os.listdir(dirname)
            self._spgnum_ref += [int(fname.split('-')[1])
                                 for fname in filenames]
            self._filenames += [os.path.join(dirname, fname)
                                for fname in filenames]
            self._ref_filenames += [os.path.join(refdirname, fname + "-ref")
                                    for fname in filenames]

    def _create_symref(self):
        pass

    def tearDown(self):
        pass

    def test_get_symmetry_dataset(self):
        for fname, spgnum, reffname in zip(self._filenames,
                                           self._spgnum_ref,
                                           self._ref_filenames):
            cell = read_vasp(fname)

            if 'distorted' in fname:
                symprec = 1e-1
            else:
                symprec = 1e-5
            dataset = get_symmetry_dataset(cell, symprec=symprec)
            self.assertEqual(dataset['number'], spgnum, msg=("%s" % fname))

            for i in range(spg_to_hall[spgnum - 1], spg_to_hall[spgnum]):
                dataset = get_symmetry_dataset(cell, hall_number=i,
                                               symprec=symprec)
                self.assertEqual(type(dataset), dict, msg=("%s/%d" % (fname, i)))
                self.assertEqual(dataset['hall_number'], i, msg=("%s" % fname))
                spg_type = get_spacegroup_type(dataset['hall_number'])
                self.assertEqual(dataset['international'],
                                 spg_type['international_short'],
                                 msg=("%s" % fname))
                self.assertEqual(dataset['hall'], spg_type['hall_symbol'],
                                 msg=("%s" % fname))
                self.assertEqual(dataset['choice'], spg_type['choice'],
                                 msg=("%s" % fname))
                self.assertEqual(dataset['pointgroup'],
                                 spg_type['pointgroup_international'],
                                 msg=("%s" % fname))

            wyckoffs = dataset['wyckoffs']
            with open(reffname) as f:
                wyckoffs_ref = yaml.load(f, Loader=yaml.FullLoader)['wyckoffs']
            for w, w_ref in zip(wyckoffs, wyckoffs_ref):
                self.assertEqual(w, w_ref, msg=("%s" % fname))

            # This is for writing out detailed symmetry info into files.
            # Now it is only for Wyckoff positions.
            # with open(reffname, 'w') as f:
            #     f.write("wyckoffs:\n")
            #     for w in dataset['wyckoffs']:
            #         f.write("- \"%s\"\n" % w)

    def test_standardize_cell_and_pointgroup(self):
        for fname, spgnum in zip(self._filenames, self._spgnum_ref):
            cell = read_vasp(fname)
            if 'distorted' in fname:
                symprec = 1e-1
            else:
                symprec = 1e-5

            std_cell = standardize_cell(cell,
                                        to_primitive=False,
                                        no_idealize=True,
                                        symprec=symprec)
            dataset = get_symmetry_dataset(std_cell, symprec=symprec)
            self.assertEqual(dataset['number'], spgnum,
                             msg=("%s" % fname))

            # The test for point group has to be done after standarization.
            ptg_symbol, _, _ = get_pointgroup(dataset['rotations'])
            self.assertEqual(dataset['pointgroup'],
                             ptg_symbol,
                             msg=("%s" % fname))

    def test_standardize_cell_from_primitive(self):
        for fname, spgnum in zip(self._filenames, self._spgnum_ref):
            cell = read_vasp(fname)
            if 'distorted' in fname:
                symprec = 1e-1
            else:
                symprec = 1e-5

            prim_cell = standardize_cell(cell,
                                         to_primitive=True,
                                         no_idealize=True,
                                         symprec=symprec)
            std_cell = standardize_cell(prim_cell,
                                        to_primitive=False,
                                        no_idealize=True,
                                        symprec=symprec)
            dataset = get_symmetry_dataset(std_cell, symprec=symprec)
            self.assertEqual(dataset['number'], spgnum,
                             msg=("%s" % fname))

    def test_standardize_cell_to_primitive(self):
        for fname, spgnum in zip(self._filenames, self._spgnum_ref):
            cell = read_vasp(fname)
            if 'distorted' in fname:
                symprec = 1e-1
            else:
                symprec = 1e-5

            prim_cell = standardize_cell(cell,
                                         to_primitive=True,
                                         no_idealize=True,
                                         symprec=symprec)
            dataset = get_symmetry_dataset(prim_cell, symprec=symprec)
            self.assertEqual(dataset['number'], spgnum,
                             msg=("%s" % fname))

    def test_refine_cell(self):
        for fname, spgnum in zip(self._filenames, self._spgnum_ref):
            cell = read_vasp(fname)
            if 'distorted' in fname:
                dataset_0 = get_symmetry_dataset(cell, symprec=1e-1)
            else:
                dataset_0 = get_symmetry_dataset(cell, symprec=1e-5)
            ref_cell_0 = (dataset_0['std_lattice'],
                          dataset_0['std_positions'],
                          dataset_0['std_types'])
            dataset_1 = get_symmetry_dataset(ref_cell_0, symprec=1e-5)
            # Check the same space group type is found.
            self.assertEqual(dataset_1['number'], spgnum, msg=("%s" % fname))

            # Check if the same structure is obtained when applying
            # standardization again, i.e., examining non cycling behaviour.
            # Currently only for orthorhombic.
            if ('cubic' in fname or
                'hexagonal' in fname or
                'monoclinic' in fname or
                'orthorhombic' in fname or
                'tetragonal' in fname or
                'triclinic' in fname or
                'trigonal' in fname or
                # 'virtual_structure' in fname or
                'distorted' in fname):
                ref_cell_1 = (dataset_1['std_lattice'],
                              dataset_1['std_positions'],
                              dataset_1['std_types'])
                dataset_2 = get_symmetry_dataset(ref_cell_1, symprec=1e-5)
                np.testing.assert_equal(
                    dataset_1['std_types'], dataset_2['std_types'],
                    err_msg="%s" % fname)
                np.testing.assert_allclose(
                    dataset_1['std_lattice'], dataset_2['std_lattice'],
                    atol=1e-5, err_msg="%s" % fname)
                diff = dataset_1['std_positions'] - dataset_2['std_positions']
                diff -= np.rint(diff)
                np.testing.assert_allclose(
                    diff, 0, atol=1e-5, err_msg="%s" % fname)

    def test_find_primitive(self):
        for fname in self._filenames:
            cell = read_vasp(fname)
            if 'distorted' in fname:
                symprec = 1e-1
            else:
                symprec = 1e-5

            dataset = get_symmetry_dataset(cell, symprec=symprec)
            primitive = find_primitive(cell, symprec=symprec)

            spg_type = get_spacegroup_type(dataset['hall_number'])
            c = spg_type['international_short'][0]
            if c in ['A', 'B', 'C', 'I']:
                multiplicity = 2
            elif c == 'F':
                multiplicity = 4
            elif c == 'R':
                self.assertEqual(spg_type['choice'], 'H')
                if spg_type['choice'] == 'H':
                    multiplicity = 3
                else:  # spg_type['choice'] == 'R'
                    multiplicity = 1
            else:
                multiplicity = 1
            self.assertEqual(len(dataset['std_types']),
                             len(primitive[2]) * multiplicity,
                             msg=("multi: %d, %s" % (multiplicity, fname)))


if __name__ == '__main__':
    suite = unittest.TestLoader().loadTestsFromTestCase(TestSpglib)
    unittest.TextTestRunner(verbosity=2).run(suite)
    # unittest.main()
