#!/usr/bin/env python

#===============================================================================

import numpy as np

from olympus         import Logger
from olympus.objects import ObjectParameter
from olympus.objects import ParameterContinuous

#===============================================================================

class ParameterSpace:
    ''' generic parameter space

    This class is intended to store all information about a parameter
    space subject to optimization, provide the interface to define a
    parameter space and perform basic operations on the parameter
    space (constraints, boundaries, etc.)
    '''

    def __init__(self):
        self.parameters = []

    def __getitem__(self, index):
        return self.parameters[index]

    def __iter__(self):
        for param in self.parameters:
            yield param

    def __len__(self):
        return len(self.parameters)

    def __str__(self):
        return '\n'.join([str(param) for param in self])

    @property
    def param_names(self):
        return [param.name for param in self]

    @property
    def param_bounds(self):
        return [(param.low, param.high) for param in self]

    @property
    def param_lowers(self):
        return np.array([param.low for param in self])

    @property
    def param_uppers(self):
        return np.array([param.high for param in self])

    def _add_param(self, param):
        # check if we already have that param
        if param.name in self.param_names:
            message = '''Parameter "{}" is already defined'''.format(param.name)
            Logger.log(message, 'ERROR')
        else:
            self.parameters.append(param)

    def add(self, param):
        """

        Args:
            param:

        Returns:

        """
        if isinstance(param, ObjectParameter):
            self._add_param(param)
        elif isinstance(param, list):
            for _param in param:
                self._add_param(_param)
        else:
            Logger.log('Please provide a valid parameter', 'ERROR')

    def get_param(self, name):
        """

        Args:
            name:

        Returns:

        """
        for param in self.parameters:
            if param['name'] == name:
                return param
        message = 'Could not find Parameter with name {0} in {1}'.format(name, str(self))
        Logger.log(message, 'WARNING')
        return None

    def item(self):
        """

        Returns:

        """
        return [param.to_dict() for param in self]

    def validate(self, param_vector):
        """

        Args:
            param_vector:

        Returns:

        """
        bounds = self.param_bounds
        valid  = True
        for i, bound in enumerate(bounds):
            entry = param_vector[i]
            valid = valid and bound[0] <= entry
            valid = valid and entry <= bound[1]

        if valid is False:
            message = f'Not all parameters of {param_vector} are within bounds!'
            Logger.log(message, 'WARNING')

        return valid
