#
#    Licensed under the Apache License, Version 2.0 (the "License"); you may
#    not use this file except in compliance with the License. You may obtain
#    a copy of the License at
#
#         http://www.apache.org/licenses/LICENSE-2.0
#
#    Unless required by applicable law or agreed to in writing, software
#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
#    License for the specific language governing permissions and limitations
#    under the License.
"""
APIs for dealing with input and output definitions for Software Configurations.
"""
import collections
import copy
from heat.common.i18n import _
from heat.common import exception
from heat.engine import constraints
from heat.engine import parameters
from heat.engine import properties
(
    IO_NAME, DESCRIPTION, TYPE,
    DEFAULT, REPLACE_ON_CHANGE, VALUE,
    ERROR_OUTPUT,
) = (
    'name', 'description', 'type',
    'default', 'replace_on_change', 'value',
    'error_output',
)
TYPES = (
    STRING_TYPE, NUMBER_TYPE, LIST_TYPE, JSON_TYPE, BOOLEAN_TYPE,
) = (
    'String', 'Number', 'CommaDelimitedList', 'Json', 'Boolean',
)
input_config_schema = {
    IO_NAME: properties.Schema(
        properties.Schema.STRING,
        _('Name of the input.'),
        required=True
    ),
    DESCRIPTION: properties.Schema(
        properties.Schema.STRING,
        _('Description of the input.')
    ),
    TYPE: properties.Schema(
        properties.Schema.STRING,
        _('Type of the value of the input.'),
        default=STRING_TYPE,
        constraints=[constraints.AllowedValues(TYPES)]
    ),
    DEFAULT: properties.Schema(
        properties.Schema.ANY,
        _('Default value for the input if none is specified.'),
    ),
    REPLACE_ON_CHANGE: properties.Schema(
        properties.Schema.BOOLEAN,
        _('Replace the deployment instead of updating it when the input '
          'value changes.'),
        default=False,
    ),
}
output_config_schema = {
    IO_NAME: properties.Schema(
        properties.Schema.STRING,
        _('Name of the output.'),
        required=True
    ),
    DESCRIPTION: properties.Schema(
        properties.Schema.STRING,
        _('Description of the output.')
    ),
    TYPE: properties.Schema(
        properties.Schema.STRING,
        _('Type of the value of the output.'),
        default=STRING_TYPE,
        constraints=[constraints.AllowedValues(TYPES)]
    ),
    ERROR_OUTPUT: properties.Schema(
        properties.Schema.BOOLEAN,
        _('Denotes that the deployment is in an error state if this '
          'output has a value.'),
        default=False
    )
}
[docs]class IOConfig(object):
    """Base class for the configuration data for a single input or output."""
    def __init__(self, **config):
        self._props = properties.Properties(self.schema, config)
        try:
            self._props.validate()
        except exception.StackValidationFailed as exc:
            raise ValueError(str(exc))
[docs]    def name(self):
        """Return the name of the input or output."""
        return self._props[IO_NAME] 
[docs]    def as_dict(self):
        """Return a dict representation suitable for persisting."""
        return {k: v for k, v in self._props.items() if v is not None} 
    def __repr__(self):
        return '%s(%s)' % (type(self).__name__,
                           ', '.join('%s=%s' % (k, repr(v))
                                     for k, v in self.as_dict().items())) 
_no_value = object()
[docs]class OutputConfig(IOConfig):
    """Class representing the configuration data for a single output."""
    schema = output_config_schema
[docs]    def error_output(self):
        """Return True if the presence of the output indicates an error."""
        return self._props[ERROR_OUTPUT]  
[docs]def check_io_schema_list(io_configs):
    """Check that an input or output schema list is of the correct type.
    Raises TypeError if the list itself is not a list, or if any of the
    members are not dicts.
    """
    if (
        not isinstance(io_configs, collections.abc.Sequence) or
        isinstance(io_configs, collections.abc.Mapping) or
        isinstance(io_configs, str)
    ):
        raise TypeError('Software Config I/O Schema must be in a list')
    if not all(
        isinstance(conf, collections.abc.Mapping) for conf in io_configs
    ):
        raise TypeError('Software Config I/O Schema must be a dict')