import hashlib  # 3.8
from abc import ABCMeta
from abc import abstractmethod
import numpy as np
from monty.json import MSONable
from pymatgen.core.lattice import Lattice
from pymatgen.core.structure import Structure
[docs]def sha1hash(obj):
    m = hashlib.sha1()
    m.update(bytes(repr(obj), 'utf-8'))
    return m.hexdigest() 
[docs]class DBschema(MSONable, metaclass=ABCMeta):
[docs]    @abstractmethod
    def getid(self) -> str:
        pass 
    @abstractmethod
    def __hash__(self) -> str:
        pass
    @property
    def unfilled(self):
        d = self.as_dict()
        uf = []
        for k in d.keys():
            if d[k] is None:
                uf.append(k)
        return uf 
from collections import Counter
[docs]class DBCrystal(DBschema):
    _id: str
[docs]    def __init__(
            self,
            lattice: Lattice = None,
            smiles: str = None,
            hsmiles: str = None,
            chromophore: str = None,
            elements: (str) = None,  # e.g. ('c', 'h', ...), must be sorted
            identifier: str = None,
            source: str = None,
            cif_string: str = None,
            disorder_location: str = None,  # 'bone' or 'sidechain' or 'not sure' or 'no disorder
            disorder_class: str = None,
            configurations: [str] = None,
            major_configuration: [str] = None,
            results: dict = None
    ):
        self.elements = elements
        self.identifier = identifier
        self.source = source
        self.cif_string = cif_string
        self.disorder_location = disorder_location
        self.disorder_class = disorder_class
        self.configurations = configurations
        self.major_configuration = major_configuration
        self.lattice = lattice
        self.smiles = smiles
        self.hsmiles = hsmiles
        self.chromophore = chromophore
        self.results = results
        self.id = self.getid()  # source + identifier  # e.g. 'csd_ASFNC' 
[docs]    def getid(self):
        return "{}_{}".format(self.source, self.identifier) 
    def __hash__(self):
        f = ','.join(['{:.4}'] * 9)
        c = Counter(self.elements)
        els = sorted(c.most_common(), key=lambda x: x[0])
        els = str(els)
        latmat_format = f.format(*np.round(self.lattice.matrix.flatten(), decimals=4))
        return sha1hash('@'.join([els, latmat_format])) 
[docs]class DBConfiguration(DBschema):
[docs]    def __init__(
            self,
            calculation_stage: str = None,  # denotes how many calculations have been done for this config
            unwrap_structure: Structure = None,  # this is the unwrap_clean_pstructure
            molconformers: [str] = None,  # use rmsd to get rid of identical conformers, see BasicConformer.compare()
            z: int = None,
            occupancy: float = None,
            backbone_structure: Structure = None,
            crystal: str = None,
            config_index: int = None,
            is_major: bool = None,
            packing_data: dict = None,
            hop_data_geodict: dict = None,
            hop_data_zindo: dict = None,
            hop_data_dft: dict = None,
            fb_opt_structure: Structure = None,
            ar_opt_structure: Structure = None,
            fb_opt_ebs: dict = None,
            ar_opt_ebs: dict = None,
            energetics: dict = None,  # keys are fb_opt, ar_opt, mol_in_box, cohesive
    ):
        self.calculation_stage = calculation_stage
        self.unwrap_structure = unwrap_structure
        self.molconformers = molconformers
        self.z = z
        self.occupancy = occupancy
        self.backbone_structure = backbone_structure
        self.crystal = crystal
        self.config_index = config_index
        self.is_major = is_major
        self.packing_data = packing_data
        self.hop_data_geodict = hop_data_geodict
        self.hop_data_zindo = hop_data_zindo
        self.hop_data_dft = hop_data_dft
        self.fb_opt_structure = fb_opt_structure
        self.ar_opt_structure = ar_opt_structure
        self.fb_opt_ebs = fb_opt_ebs
        self.ar_opt_ebs = ar_opt_ebs
        self.energetics = energetics
        self.id = self.getid() 
[docs]    def getid(self):
        return '_'.join([self.crystal, self.config_index]) 
    def __hash__(self):
        return self.id 
[docs]class DBChromophore(MSONable):
[docs]    def __init__(
            self,
            hsmiles: str = None,
            smiles: str = None,
            ChromophoreConfs: [str] = None
    ):
        self.hsmiles = hsmiles
        self.smiles = smiles
        self.ChromophoreConfs = ChromophoreConfs
        self.id = self.getid() 
[docs]    def getid(self):
        return self.hsmiles 
    def __hash__(self):
        return self.id 
# dc = DBCrystal()
# dc.lattice = Lattice.from_parameters(1, 2, 3, 40, 50, 60)
# from pprint import pprint
# pprint(dc.as_dict())