import os
import pickle, json
import time

from hpbandster.workers.hpolibbenchmark import HPOlib2Worker
from hpbandster.core.base_iteration import Datum
from hpbandster.core.result import Result


class BaseWorker(HPOlib2Worker):

    def __init__(self, max_budget, **kwargs):
        super().__init__(**kwargs)

        self.time_ref = time.time()
        self.max_budget=max_budget
        self.run_data = {}


    def tpe_configspace(self):
        """
            specifies the configuration space for TPE
        """
        raise NotImplementedError("Overwrite for actual experiment")


    def subdir(self):
        """
            specifies the subdirectory to store the data
        """

    def evaluate_and_log (self, config, budget):
        """
            Helper functions to log results and store them in the same format as Hyperband and BOHB runs
        """

        start = time.time()
        res = self.compute(config, budget=budget)
        end = time.time()


        id = (len(self.run_data), 0,0)

        # construct a Datum object to mimic the internals of a HpBandSter iteration
        res_dict = {budget: {'loss': res['loss'], 'info': res['info']}}
        ts_dict  = {budget: {'submitted': start, 'started': start, 'finished': end}}
        self.run_data[id] = Datum(config, {}, results=res_dict, budget=budget, time_stamps = ts_dict, status='FINISHED')

        return(res["loss"])


    def get_result(self):

        # mock minial HB_config to have meaningful output
        mock_HB_config = {'min_budget': self.max_budget, 'max_budget': self.max_budget, 'time_ref': self.time_ref}

        # get Result by pretending to be a HB-run with one iteration
        res = Result([self.run_data, ], mock_HB_config)

        return(res)

    def run_tpe(self, num_iterations):
        """
            Wrapper around TPE to return a HpBandSter Result object to integrate better with the other methods
        """
        try:
            from hyperopt import fmin, tpe, hp, STATUS_OK, Trials
        except ImportError:
            raise ImportError('To run TPE, please install the hyperopt package!')
        except:
            raise

        def tpe_objective(config):
            loss = self.evaluate_and_log(config, budget=self.max_budget)
            return({    'config': config,
                        'loss': loss,
                        'status': STATUS_OK})




        space = self.tpe_configspace()
        trials = Trials()
        best = fmin(tpe_objective,
                space=space,
                algo=tpe.suggest,
                max_evals=num_iterations,
                trials=trials)
        return(self.get_result())



    def run_smac(self, num_iterations, deterministic=True, working_directory='/tmp'):
        """
            Wrapper around SMAC to return a HpBandSter Result object to integrate better with the other methods
        """

        try:
            from smac.facade.smac_facade import SMAC
            from smac.scenario.scenario import Scenario
        except ImportError:
            raise ImportError('To run SMAC, please install the latest python implementation of SMAC (pip install smac)!')
        except:
            raise

        os.makedirs(working_directory, exist_ok=True)
        tmp_fn = os.path.join(working_directory, 'smac_%s.json'%self.run_id)

        # SMAC puts every call into a subprocess, so the data has to be stored on disk to
        # be persistent. Here, we simply pickle the run data to disk after every call and
        # load it before the actual call.
        with open(tmp_fn, 'wb') as fh:
            pickle.dump(self.run_data, fh)


        def smac_objective(config, **kwargs):

            with open(tmp_fn, 'rb') as fh:
                self.run_data = pickle.load(fh)

            loss = self.evaluate_and_log(config, budget=self.max_budget)

            with open(tmp_fn, 'wb') as fh:
                pickle.dump(self.run_data, fh)

            return loss, []

        scenario = Scenario({   "run_obj": "quality",
                                "runcount-limit": num_iterations,
                                "cs": self.configspace,
                                "deterministic": deterministic,
                                "initial_incumbent": "RANDOM",
                                "output_dir": working_directory})


        smac = SMAC(scenario=scenario, tae_runner=smac_objective)
        smac.optimize()


        with open(tmp_fn, 'rb') as fh:
            self.run_data = pickle.load(fh)

        os.remove(tmp_fn)

        return(self.get_result())