# Copyright 2012 OpenStack Foundation
#
# 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 itertools
import os.path

from oslo_config import cfg
from oslo_log import log
import six

from keystone.catalog import core
from keystone import exception
from keystone.i18n import _LC


LOG = log.getLogger(__name__)

CONF = cfg.CONF


def parse_templates(template_lines):
    o = {}
    for line in template_lines:
        if ' = ' not in line:
            continue

        k, v = line.strip().split(' = ')
        if not k.startswith('catalog.'):
            continue

        parts = k.split('.')

        region = parts[1]
        # NOTE(termie): object-store insists on having a dash
        service = parts[2].replace('_', '-')
        key = parts[3]

        region_ref = o.get(region, {})
        service_ref = region_ref.get(service, {})
        service_ref[key] = v

        region_ref[service] = service_ref
        o[region] = region_ref

    return o


class Catalog(core.Driver):
    """A backend that generates endpoints for the Catalog based on templates.

    It is usually configured via config entries that look like:

      catalog.$REGION.$SERVICE.$key = $value

    and is stored in a similar looking hierarchy. Where a value can contain
    values to be interpolated by standard python string interpolation that look
    like (the % is replaced by a $ due to paste attempting to interpolate on
    its own:

      http://localhost:$(public_port)s/

    When expanding the template it will pass in a dict made up of the conf
    instance plus a few additional key-values, notably tenant_id and user_id.

    It does not care what the keys and values are but it is worth noting that
    keystone_compat will expect certain keys to be there so that it can munge
    them into the output format keystone expects. These keys are:

      name - the name of the service, most likely repeated for all services of
             the same type, across regions.

      adminURL - the url of the admin endpoint

      publicURL - the url of the public endpoint

      internalURL - the url of the internal endpoint

    """

    def __init__(self, templates=None):
        super(Catalog, self).__init__()
        if templates:
            self.templates = templates
        else:
            template_file = CONF.catalog.template_file
            if not os.path.exists(template_file):
                template_file = CONF.find_file(template_file)
            self._load_templates(template_file)

    def _load_templates(self, template_file):
        try:
            with open(template_file) as f:
                self.templates = parse_templates(f)
        except IOError:
            LOG.critical(_LC('Unable to open template file %s'), template_file)
            raise

    # region crud

    def create_region(self, region_ref):
        raise exception.NotImplemented()

    def list_regions(self, hints):
        return [{'id': region_id, 'description': '', 'parent_region_id': ''}
                for region_id in self.templates]

    def get_region(self, region_id):
        if region_id in self.templates:
            return {'id': region_id, 'description': '', 'parent_region_id': ''}
        raise exception.RegionNotFound(region_id=region_id)

    def update_region(self, region_id, region_ref):
        raise exception.NotImplemented()

    def delete_region(self, region_id):
        raise exception.NotImplemented()

    # service crud

    def create_service(self, service_id, service_ref):
        raise exception.NotImplemented()

    def _list_services(self, hints):
        for region_ref in six.itervalues(self.templates):
            for service_type, service_ref in six.iteritems(region_ref):
                yield {
                    'id': service_type,
                    'enabled': True,
                    'name': service_ref.get('name', ''),
                    'description': service_ref.get('description', ''),
                    'type': service_type,
                }

    def list_services(self, hints):
        return list(self._list_services(hints=None))

    def get_service(self, service_id):
        for service in self._list_services(hints=None):
            if service['id'] == service_id:
                return service
        raise exception.ServiceNotFound(service_id=service_id)

    def update_service(self, service_id, service_ref):
        raise exception.NotImplemented()

    def delete_service(self, service_id):
        raise exception.NotImplemented()

    # endpoint crud

    def create_endpoint(self, endpoint_id, endpoint_ref):
        raise exception.NotImplemented()

    def _list_endpoints(self):
        for region_id, region_ref in six.iteritems(self.templates):
            for service_type, service_ref in six.iteritems(region_ref):
                for key in service_ref:
                    if key.endswith('URL'):
                        interface = key[:-3]
                        endpoint_id = ('%s-%s-%s' %
                                       (region_id, service_type, interface))
                        yield {