from __future__ import annotations

import inspect
import json
import logging
import os

logger = logging.getLogger("xia2.Schema.Interfaces.Refiner")


class Refiner:
    """An interface to present refinement functionality in a similar way to the
    scaler interface."""

    LATTICE_POSSIBLE = "LATTICE_POSSIBLE"
    LATTICE_IMPOSSIBLE = "LATTICE_IMPOSSIBLE"
    LATTICE_CORRECT = "LATTICE_CORRECT"

    def __init__(self):
        super().__init__()
        # set up a framework for storing all of the input information...
        # this should really only consist of integraters...

        # key this by the epoch, if available, else will need to
        # do something different.
        self._refinr_indexers = {}
        self._refinr_sweeps = []

        # admin junk
        self._working_directory = os.getcwd()

        self._refinr_payload = {}
        self._refinr_refined_experiment_list = None

        # implementation dependent parameters - these should be keyed by
        # say 'mosflm':{'yscale':0.9999} etc.
        self._refinr_program_parameters = {}

        # Record refiner status
        self._refinr_done = False
        self._refinr_prepare_done = False
        self._refinr_finish_done = False
        self._refinr_result = None

    # serialization functions

    def to_dict(self):
        obj = {}
        obj["__id__"] = "Refiner"
        obj["__module__"] = self.__class__.__module__
        obj["__name__"] = self.__class__.__name__

        attributes = inspect.getmembers(self, lambda m: not (inspect.isroutine(m)))
        for a in attributes:
            if a[0] == "_refinr_indexers":
                d = {}
                for k, v in a[1].items():
                    d[k] = v.to_dict()
                obj[a[0]] = d
            elif a[0] == "_refinr_refined_experiment_list":
                if a[1] is not None:
                    obj[a[0]] = a[1].to_dict()
            elif a[0] == "_refinr_sweeps":
                # XXX I guess we probably want this?
                continue
            elif a[0].startswith("_refinr_"):
                obj[a[0]] = a[1]
        return obj

    @classmethod
    def from_dict(cls, obj):
        assert obj["__id__"] == "Refiner"
        return_obj = cls()
        for k, v in obj.items():
            if k == "_refinr_indexers":
                v_new = {}
                for k_, v_ in v.items():
                    from libtbx.utils import import_python_object

                    integrater_cls = import_python_object(
                        import_path=".".join((v_["__module__"], v_["__name__"])),
                        error_prefix="",
                        target_must_be="",
                        where_str="",
                    ).object
                    v_new[float(k_)] = integrater_cls.from_dict(v_)
                v = v_new
            elif k == "_refinr_payload":
                v_new = {}
                for k_, v_ in v.items():
                    try:
                        v_new[float(k_)] = v_
                    except ValueError:
                        v_new[k_] = v_
                v = v_new
            if isinstance(v, dict):
                if v.get("__id__") == "ExperimentList":
                    from dxtbx.model.experiment_list import ExperimentListFactory

                    v = ExperimentListFactory.from_dict(v, check_format=False)
            setattr(return_obj, k, v)
        return return_obj

    def as_json(self, filename=None, compact=False):
        obj = self.to_dict()
        if compact:
            text = json.dumps(
                obj, skipkeys=False, separators=(",", ":"), ensure_ascii=True
            )
        else:
            text = json.dumps(obj, skipkeys=False, indent=2, ensure_ascii=True)

        # If a filename is set then dump to file otherwise return string
        if filename is not None:
            with open(filename, "w") as outfile:
                outfile.write(text)
        else:
            return text

    @classmethod
    def from_json(cls, filename=None, string=None):
        assert [filename, string].count(None) == 1
        if filename is not None:
            with open(filename, "rb") as f:
                string = f.read()
        obj = json.loads(string)
        return cls.from_dict(obj)

    def _refine_prepare(self):
        raise NotImplementedError("overload me")

    def _refine(self):
        raise NotImplementedError("overload me")

    def _refine_finish(self):
        pass

    def add_refiner_sweep(self, sweep):
        self._refinr_sweeps.append(sweep)

    def get_indexer_sweeps(self):
        return self._refinr_sweeps

    def set_working_directory(self, working_directory):
        self._working_directory = working_directory

    def get_working_directory(self):
        return self._working_directory

    def set_refiner_prepare_done(self, done=True):
        frm = inspect.stack()[1]
        mod = inspect.getmodule(frm[0])
        logger.debug(
            "Called refiner prepare done from %s %d (%s)"
            % (mod.__name__, frm[0].f_lineno, done)
        )

        self._refinr_prepare_done = done

    def set_refiner_done(self, done=True):
        frm = inspect.stack()[1]
        mod = inspect.getmodule(frm[0])
        logger.debug(
            "Called refiner done from %s %d (%s)"
            % (mod.__name__, frm[0].f_lineno, done)
        )

        self._refinr_done = done

    def set_refiner_finish_done(self, done=True):
        frm = inspect.stack()[1]
        mod = inspect.getmodule(frm[0])
        logger.debug(
            "Called refiner finish done from %s %d (%s)"
            % (mod.__name__, frm[0].f_lineno, done)