#
#    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.

from oslo_log import log as logging

from heat.common.i18n import _
from heat.engine import attributes
from heat.engine import constraints
from heat.engine import properties
from heat.engine import resource
from heat.engine import support
from heat.rpc import api as rpc_api

LOG = logging.getLogger(__name__)


class SoftwareConfig(resource.Resource):
    '''
    A resource for describing and storing software configuration.

    The software_configs API which backs this resource creates immutable
    configs, so any change to the template resource definition will result
    in a new config being created, and the old one being deleted.

    Configs can be defined in the same template which uses them, or they can
    be created in one stack, and passed to another stack via a parameter.

    A config resource can be referenced in other resource properties which
    are config-aware. This includes the properties OS::Nova::Server user_data,
    OS::Heat::SoftwareDeployment config and OS::Heat::MultipartMime parts
    config.

    Along with the config script itself, this resource can define schemas for
    inputs and outputs which the config script is expected to consume and
    produce. Inputs and outputs are optional and will map to concepts which
    are specific to the configuration tool being used.
    '''

    support_status = support.SupportStatus(version='2014.1')

    PROPERTIES = (
        GROUP, CONFIG, OPTIONS, INPUTS, OUTPUTS
    ) = (
        'group', 'config', 'options', 'inputs', 'outputs'
    )

    IO_PROPERTIES = (
        NAME, DESCRIPTION, TYPE, DEFAULT, ERROR_OUTPUT
    ) = (
        'name', 'description', 'type', 'default', 'error_output'
    )

    ATTRIBUTES = (
        CONFIG_ATTR,
    ) = (
        'config',
    )

    input_schema = {
        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',
            constraints=[constraints.AllowedValues((
                'String', 'Number', 'CommaDelimitedList', 'Json'))]
        ),
        DEFAULT: properties.Schema(
            properties.Schema.STRING,
            _('Default value for the input if none is specified.'),
        ),
    }

    output_schema = {
        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',
            constraints=[constraints.AllowedValues((
                'String', 'Number', 'CommaDelimitedList', 'Json'))]
        ),
        ERROR_OUTPUT: properties.Schema(
            properties.Schema.BOOLEAN,
            _('Denotes that the deployment is in an error state if this '
              'output has a value.'),
            default=False
        )
    }

    properties_schema = {
        GROUP: properties.Schema(
            properties.Schema.STRING,
            _('Namespace to group this software config by when delivered to '
              'a server. This may imply what configuration tool is going to '
              'perform the configuration.'),
            default='Heat::Ungrouped'
        ),
        CONFIG: properties.Schema(
            properties.Schema.STRING,
            _('Configuration script or manifest which specifies what actual '
              'configuration is performed.'),
        ),
        OPTIONS: properties.Schema(
            properties.Schema.MAP,
            _('Map containing options specific to the configuration '
              'management tool used by this resource.'),
        ),
        INPUTS: properties.Schema(
            properties.Schema.LIST,
            _('Schema representing the inputs that this software config is '
              'expecting.'),
            schema=properties.Schema(properties.Schema.MAP,
                                     schema=input_schema)
        ),
        OUTPUTS: properties.Schema(
            properties.Schema.LIST,
            _('Schema representing the outputs that this software config '
              'will produce.'),
            schema=properties.Schema(properties.Schema.MAP,
                                     schema=output_schema)
        ),
    }

    attributes_schema = {
        CONFIG_ATTR: attributes.Schema(
            _("The config value of the software config.")
        ),
    }

    def handle_create(self):
        props = dict(self.properties)
        props[self.NAME] = self.physical_resource_name()

        sc = self.rpc_client().create_software_config(self.context, **props)
        self.resource_id_set(sc[rpc_api.SOFTWARE_CONFIG_ID])

    def handle_delete(self):

        if self.resource_id is None:
            return

        try:
            self.rpc_client().delete_software_config(
                self.context, self.resource_id)
        except Exception as ex:
            self.rpc_client().ignore_error_named(ex, 'NotFound')

    def _resolve_attribute(self, name):
        '''
        "config" returns the config value of the software config. If the
         software config does not exist, returns an empty string.
        '''
        if name == self.CONFIG_ATTR and self.resource_id:
            try:
                sc = self.rpc_client().show_software_config(
                    self.context, self.resource_id)
                return sc[rpc_api.SOFTWARE_CONFIG_CONFIG]
            except Exception as ex:
                self.rpc_client().ignore_error_named(ex, 'NotFound')


def resource_mapping():
    return {
        'OS::Heat::SoftwareConfig': SoftwareConfig,
    }
