import random
import sys
import numpy as np

try:
    import botorch
    import torch
    from botorch.utils.multi_objective.box_decompositions.dominated import DominatedPartitioning
    from pymoo.indicators.hv import Hypervolume
except ImportError as e:
    print(f'Error: {e}')


def debugger_is_active() -> bool:
    """Return if the debugger is currently active"""
    return hasattr(sys, 'gettrace') and sys.gettrace() is not None

def set_seed(seed=1234):
    random.seed(seed)
    np.random.seed(seed)
    if 'torch' in sys.modules:
        torch.manual_seed(seed)
        torch.use_deterministic_algorithms(True)
    if 'botorch' in sys.modules:
        botorch.manual_seed(seed)


def get_nadir_ideal_points(_f, minimize=True):
    if minimize:
        ideal_point = np.min(_f, axis=0)
        nadir_point = np.max(_f, axis=0)
    else:
        ideal_point = np.max(_f, axis=0)
        nadir_point = np.min(_f, axis=0)
    return ideal_point, nadir_point

def distance_to_line(_f, _point_on_line, _line_normal):
    _f = np.atleast_2d(_f)
    pa = _f - _point_on_line
    ba = np.atleast_2d(_line_normal)
    t = (pa*ba).sum(1) / (ba*ba).sum(1)
    d = np.linalg.norm(pa - t.reshape(-1, 1)*ba, axis=1)
    return d

def to_unit_cube(x, lb, ub):
    return (x - lb) / (ub - lb)

def from_unit_cube(x, lb, ub):
    return x * (ub - lb) + lb

def standardize(fx):
    mean = np.mean(fx, axis=0)
    std = np.std(fx, axis=0)
    std[std < 1e-6] = 1.0
    return (fx - mean) / std, mean, std

def standardize_by_mean_std(fx, mean, std):
    return (fx - mean) / std

def unstandardize(fx, mean, std):
    return fx * std + mean

