# Copyright 2016: Mirantis Inc.
# All Rights Reserved.
#
#    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
import os
import time

from rally.common import logging
from rally import exceptions
from rally.task import utils

from glanceclient import exc as glance_exc
from oslo_config import cfg
import requests
import six

LOG = logging.getLogger(__name__)

GLANCE_BENCHMARK_OPTS = [
    cfg.FloatOpt("glance_image_create_prepoll_delay",
                 default=2.0,
                 help="Time to sleep after creating a resource before "
                      "polling for it status"),
    cfg.FloatOpt("glance_image_create_timeout",
                 default=120.0,
                 help="Time to wait for glance image to be created."),
    cfg.FloatOpt("glance_image_create_poll_interval",
                 default=1.0,
                 help="Interval between checks when waiting for image "
                      "creation."),
    cfg.FloatOpt("glance_image_delete_timeout",
                 default=120.0,
                 help="Time to wait for glance image to be deleted."),
    cfg.FloatOpt("glance_image_delete_poll_interval",
                 default=1.0,
                 help="Interval between checks when waiting for image "
                      "deletion.")
]

CONF = cfg.CONF
benchmark_group = cfg.OptGroup(name="benchmark", title="benchmark options")
CONF.register_opts(GLANCE_BENCHMARK_OPTS, group=benchmark_group)


@six.add_metaclass(abc.ABCMeta)
class GlanceWrapper(object):
    def __init__(self, client, owner):
        self.owner = owner
        self.client = client

    @abc.abstractmethod
    def get_image(self, image):
        """Refresh an image from an image object."""

    @abc.abstractmethod
    def create_image(self, container_format, image_location, disk_format):
        """Creates new image."""

    @abc.abstractmethod
    def delete_image(self, image):
        """Deletes image."""

    @abc.abstractmethod
    def list_images(self, **filters):
        """List images."""


class GlanceV1Wrapper(GlanceWrapper):
    def get_image(self, image):
        return image.manager.get(image.id)

    def create_image(self, container_format, image_location,
                     disk_format, **kwargs):
        kw = {
            "container_format": container_format,
            "disk_format": disk_format,
        }
        kw.update(kwargs)
        if "name" not in kw:
            kw["name"] = self.owner.generate_random_name()
        image_location = os.path.expanduser(image_location)

        try:
            if os.path.isfile(image_location):
                kw["data"] = open(image_location)
            else:
                kw["copy_from"] = image_location

            image = self.client.images.create(**kw)

            time.sleep(CONF.benchmark.glance_image_create_prepoll_delay)

            image = utils.wait_for_status(
                image, ["active"],
                update_resource=utils.get_from_manager(),
                timeout=CONF.benchmark.glance_image_create_timeout,
                check_interval=CONF.benchmark.
                glance_image_create_poll_interval)
        finally:
            if "data" in kw:
                kw["data"].close()

        return image

    def delete_image(self, image):
        image.delete()
        utils.wait_for_status(
            image, ["deleted"],
            check_deletion=True,
            update_resource=utils.get_from_manager(),
            timeout=CONF.benchmark.glance_image_delete_timeout,
            check_interval=CONF.benchmark.glance_image_delete_poll_interval)

    def list_images(self, **filters):
        return self.client.images.list(**filters)


class GlanceV2Wrapper(GlanceWrapper):
    def get_image(self, image):
        return self.client.images.get(image.id)

    def _update_image(self, image):
        try:
            return self.get_image(image)
        except glance_exc.HTTPNotFound:
            raise exceptions.GetResourceNotFound(resource=image)

    def create_image(self, container_format, image_location,
                     disk_format, **kwargs):
        kw = {
            "container_format": container_format,
            "disk_format": disk_format,
        }
        kw.update(kwargs)
        if "name" not in kw:
            kw["name"] = self.owner.generate_random_name()

        image_location = os.path.expanduser(image_location)

        image = self.client.images.create(**kw)

        time.sleep(CONF.benchmark.glance_image_create_prepoll_delay)

        start = time.time()
        image = utils.wait_for_status(
            image, ["queued"],
            update_resource=self._update_image,
            timeout=CONF.benchmark.glance_image_create_timeout,
            check_interval=CONF.benchmark.
            glance_image_create_poll_interval)
        timeout = time.time() - start

        image_data = None
        try:
            if os.path.isfile(image_location):
                image_data = open(image_location)
            else:
                response = requests.get(image_location)
                image_data = response.raw
            self.client.images.upload(image.id, image_data)
        finally:
            if image_data is not None:
                image_data.close()

        return utils.wait_for_status(
            image, ["active"],
            update_resource=self._update_image,
            timeout=timeout,
            check_interval=CONF.benchmark.
            glance_image_create_poll_interval)

    def delete_image(self, image):
        self.client.images.delete(image.id)
        utils.wait_for_status(
            image, ["deleted"],
            check_deletion=True,
            update_resource=self._update_image,
            timeout=CONF.benchmark.glance_image_delete_timeout,
            check_interval=CONF.benchmark.glance_image_delete_poll_interval)

    def list_images(self, **filters):
        return self.client.images.list(filters=filters)


def wrap(client, owner):
    """Returns glanceclient wrapper based on glance client version."""
    version = client.choose_version()
    if version == "1":
        return GlanceV1Wrapper(client(), owner)
    elif version == "2":
        return GlanceV2Wrapper(client(), owner)
    else:
        msg = "Version %s of the glance API could not be identified." % version
        LOG.warning(msg)
        raise exceptions.InvalidArgumentsException(msg)
