# Copyright 2014 IBM Corp.
#
# 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.

import abc

from oslo_config import cfg
from oslo_log import log
import six

from keystone.common import dependency
from keystone.common import manager
from keystone import exception
from keystone.i18n import _, _LE, _LW


CONF = cfg.CONF
LOG = log.getLogger(__name__)


@dependency.provider('endpoint_policy_api')
@dependency.requires('catalog_api', 'policy_api')
class Manager(manager.Manager):
    """Default pivot point for the Endpoint Policy backend.

    See :mod:`keystone.common.manager.Manager` for more details on how this
    dynamically calls the backend.

    """

    driver_namespace = 'keystone.endpoint_policy'

    def __init__(self):
        super(Manager, self).__init__(CONF.endpoint_policy.driver)

    def _assert_valid_association(self, endpoint_id, service_id, region_id):
        """Assert that the association is supported.

        There are three types of association supported:

        - Endpoint (in which case service and region must be None)
        - Service and region (in which endpoint must be None)
        - Service (in which case endpoint and region must be None)

        """
        if (endpoint_id is not None and
                service_id is None and region_id is None):
            return
        if (service_id is not None and region_id is not None and
                endpoint_id is None):
            return
        if (service_id is not None and
                endpoint_id is None and region_id is None):
            return

        raise exception.InvalidPolicyAssociation(endpoint_id=endpoint_id,
                                                 service_id=service_id,
                                                 region_id=region_id)

    def create_policy_association(self, policy_id, endpoint_id=None,
                                  service_id=None, region_id=None):
        self._assert_valid_association(endpoint_id, service_id, region_id)
        self.driver.create_policy_association(policy_id, endpoint_id,
                                              service_id, region_id)

    def check_policy_association(self, policy_id, endpoint_id=None,
                                 service_id=None, region_id=None):
        self._assert_valid_association(endpoint_id, service_id, region_id)
        self.driver.check_policy_association(policy_id, endpoint_id,
                                             service_id, region_id)

    def delete_policy_association(self, policy_id, endpoint_id=None,
                                  service_id=None, region_id=None):
        self._assert_valid_association(endpoint_id, service_id, region_id)
        self.driver.delete_policy_association(policy_id, endpoint_id,
                                              service_id, region_id)

    def list_endpoints_for_policy(self, policy_id):

        def _get_endpoint(endpoint_id, policy_id):
            try:
                return self.catalog_api.get_endpoint(endpoint_id)
            except exception.EndpointNotFound:
                msg = _LW('Endpoint %(endpoint_id)s referenced in '
                          'association for policy %(policy_id)s not found.')
                LOG.warning(msg, {'policy_id': policy_id,
                                  'endpoint_id': endpoint_id})
                raise

        def _get_endpoints_for_service(service_id, endpoints):
            # TODO(henry-nash): Consider optimizing this in the future by
            # adding an explicit list_endpoints_for_service to the catalog API.
            return [ep for ep in endpoints if ep['service_id'] == service_id]

        def _get_endpoints_for_service_and_region(
                service_id, region_id, endpoints, regions):
            # TODO(henry-nash): Consider optimizing this in the future.
            # The lack of a two-way pointer in the region tree structure
            # makes this somewhat inefficient.

            def _recursively_get_endpoints_for_region(