import os
import uuid
import subprocess
import cmath
import re
# import paramiko
import time
import multiprocessing
import matlab.engine
import pdb

import tensorboardX

import numpy as np

# from utils.circuits_utils import get_mos_netlist_static_feature
from copy import deepcopy

__all__ = ['BaseEnv']


class BaseEnv:
    """The main circuit env class. It encapsulates an environment with
    arbitrary behind-the-scenes dynamics. An environment can be
    partially of fully observed

    The main API methods that users of this class need to know are:

        step
        reset
        close
        seed

    And set the following attributes

        action_space: The space object corresponding to valid actions
        observation_space: The space object corresponding to valid observations
        reward_range: A tuple corresponding to min and max possible rewards

    Note: a default reward range set to [-inf, +inf] already exists. Set it if you want a narrower range

    The methods are accessed publicly as "step", "reset", etc...
    """

    def __init__(self):
        self.sizing_upper = None
        self.sizing_lower = None
        self.fix_step_per_episode = None
        self.step_per_episode = None
        self.max_step_per_episode = None
        self.unsaturation_reward = None
        self.max_unsatu_step_per_episode = None


    def step(self, action, episode, global_stp=1, real_step=True):
        """Run one timestep of the environment's dynamics. When end of
        episode is reached, you are responsible for calling 'reset()'
        to reset this environment's state

        Accepts an action and returns a tuple(observation, reward, done, info),

        Args:
            action(object): an action provided by the agent

        Returns:
            observation (object) : agent's observation of the current environment
            reward (float) : amount of reward returned after previous action
            done (bool) : whether the episode has ended, in which case further step() calls will return undefined results
            info (dict) : contains auxiliary diagnostic information (helpful for debugging and sometimes learning)

        """
        raise NotImplementedError


    def reset(self):
        """Resets the state of the environment and returns an initial observation.

        :return:
            observation (object): the initial observation

        """
        raise NotImplementedError

    def close(self):
        """
        Override close in your subclass to perform any necessary cleanup

        Environments will automatically close() themselves when
        garbage collected or when program exits
        :return:
        """

    def seed(self, seed=None):
        """Sets the seed for this env's random number generators

        Note:
            Some environments use multiple pseudorandom number generators
            We want to capture all such seeds used in order to ensure that
            there aren't accidental correlations between multiple generators

        :return:
            List<bigint>: Returns the list of seeds used in this env's random
            number generators. The first value in the list should be the "main"
            seed, or the value which a reproducer should pass to 'seed'. Often,
            main seed equals the provided 'seed', but this won't be true if
            seed=None, for example
        """
        return


    def round_sizings(self, sizings):
        for i in range(len(sizings)):
            # limit max and min
            sizings[i] = min(sizings[i], self.sizing_upper[i])
            sizings[i] = max(sizings[i], self.sizing_lower[i])

        return sizings

    # get abosolute sizings to write to netlist
    def get_absolute_sizings(self, action, config):
        # do not use starting points, currently do not round
        # pdb.set_trace()

        absolute_sizings = self.sizing_lower + (self.sizing_upper - self.sizing_lower) * (action + 1) / 2
        # absolute_sizings = self.round_sizings(absolute_sizings)
        # print(absolute_sizings)
        sizing_dict = dict()
        for i, varnames in enumerate(list(config["des_vars"].keys())):
            sizing_dict[varnames] = matlab.double([absolute_sizings[i]])
            # eg1 = [absolute_sizings[i]]

        return sizing_dict


    def get_reward(self, all_info: dict) -> float:
        return all_info['fom']


    def get_episode_finish(self, reward: float) -> bool:
        # fix step in one episode
        if self.fix_step_per_episode:
            self.step_ctr += 1
            if self.step_ctr == self.step_per_episode:
                return True
        # flexible step in one episode base one different creteria
        else:
            self.step_ctr += 1
            if self.step_ctr == self.max_step_per_episode:
                return True
            if reward == self.unsaturation_reward:
                self.unsatu_step_ctr += 1
                if self.unsatu_step_ctr == self.max_unsatu_step_per_episode:
                    return True

        return False


    def sim(self, sizings):
        """Given the corresponding sizings, collect the simulation results
        from simulator

        Args:
        :param sizings: np.array

        Return:
        A dictionary of simulation results
        """
        raise NotImplementedError





