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

from openstack.cloud import exc
from openstack import exceptions
from openstack.image import _base_proxy
from openstack.image.v1 import image as _image


class Proxy(_base_proxy.BaseImageProxy):

    def _create_image(self, **kwargs):
        """Create image resource from attributes
        """
        return self._create(_image.Image, **kwargs)

    def upload_image(self, **attrs):
        """Upload a new image from attributes

        .. warning:
          This method is deprecated - and also doesn't work very well.
          Please stop using it immediately and switch to
          `create_image`.

        :param dict attrs: Keyword arguments which will be used to create
            a :class:`~openstack.image.v1.image.Image`,
            comprised of the properties on the Image class.

        :returns: The results of image creation
        :rtype: :class:`~openstack.image.v1.image.Image`
        """
        warnings.warn("upload_image is deprecated. Use create_image instead.")
        return self._create(_image.Image, **attrs)

    def _upload_image(
        self, name, filename, data, meta, wait, timeout,
        use_import=False,
        stores=None,
        all_stores=None,
        all_stores_must_succeed=None,
        **image_kwargs,
    ):
        if use_import:
            raise exceptions.InvalidRequest(
                "Glance v1 does not support image import")
        if stores or all_stores or all_stores_must_succeed:
            raise exceptions.InvalidRequest(
                "Glance v1 does not support stores")
        # NOTE(mordred) wait and timeout parameters are unused, but
        # are present for ease at calling site.
        if filename and not data:
            image_data = open(filename, 'rb')
        else:
            image_data = data
        image_kwargs['properties'].update(meta)
        image_kwargs['name'] = name

        # TODO(mordred) Convert this to use image Resource
        image = self._connection._get_and_munchify(
            'image',
            self.post('/images', json=image_kwargs))
        checksum = image_kwargs['properties'].get(self._IMAGE_MD5_KEY, '')

        try:
            # Let us all take a brief moment to be grateful that this
            # is not actually how OpenStack APIs work anymore
            headers = {
                'x-glance-registry-purge-props': 'false',
            }
            if checksum:
                headers['x-image-meta-checksum'] = checksum

            image = self._connection._get_and_munchify(
                'image',
                self.put(
                    '/images/{id}'.format(id=image.id),
                    headers=headers, data=image_data))

        except exc.OpenStackCloudHTTPError:
            self.log.debug(
                "Deleting failed upload of image %s", name)
            try:
                self.delete('/images/{id}'.format(id=image.id))
            except exc.OpenStackCloudHTTPError:
                # We're just trying to clean up - if it doesn't work - shrug
                self.log.warning(
                    "Failed deleting image after we failed uploading it.",
                    exc_info=True)
            raise
        return self._connection._normalize_image(image)

    def _update_image_properties(self, image, meta, properties):
        properties.update(meta)
        img_props = {}
        for k, v in iter(properties.items()):
            if image.properties.get(k, None) != v:
                img_props['x-image-meta-{key}'.format(key=k)] = v
        if not img_props:
            return False
        self.put(
            '/images/{id}'.format(id=image.id), headers=img_props)
        self._connection.list_images.invalidate(self._connection)
        return True

    def _existing_image(self, **kwargs):
        return _image.Image.existing(connection=self._connection, **kwargs)

    def delete_image(self, image, ignore_missing=True):
        """Delete an image

        :param image: The value can be either the ID of an image or a
            :class:`~openstack.image.v1.image.Image` instance.
        :param bool ignore_missing: When set to ``False``
            :class:`~openstack.exceptions.ResourceNotFound` will be
            raised when the image does not exist.
            When set to ``True``, no exception will be set when
            attempting to delete a nonexistent image.

        :returns: ``None``
        """
        self._delete(_image.Image, image, ignore_missing=ignore_missing)

    def find_image(self, name_or_id, ignore_missing=True):
        """Find a single image

        :param name_or_id: The name or ID of a image.
        :param bool ignore_missing: When set to ``False``
            :class:`~openstack.exceptions.ResourceNotFound` will be
            raised when the resource does not exist.
            When set to ``True``, None will be returned when
            attempting to find a nonexistent resource.
        :returns: One :class:`~openstack.image.v1.image.Image` or None
        """
        return self._find(_image.Image, name_or_id,
                          ignore_missing=ignore_missing)

    def get_image(self, image):
        """Get a single image

        :param image: The value can be the ID of an image or a
            :class:`~openstack.image.v1.image.Image` instance.

        :returns: One :class:`~openstack.image.v1.image.Image`
        :raises: :class:`~openstack.exceptions.ResourceNotFound`
            when no resource can be found.
        """
        return self._get(_image.Image, image)

    def images(self, **query):
        """Return a generator of images

        :param kwargs query: Optional query parameters to be sent to limit
            the resources being returned.

        :returns: A generator of image objects
        :rtype: :class:`~openstack.image.v1.image.Image`
        """
        return self._list(_image.Image, base_path='/images/detail', **query)

    def update_image(self, image, **attrs):
        """Update a image

        :param image: Either the ID of a image or a
            :class:`~openstack.image.v1.image.Image` instance.
        :attrs kwargs: The attributes to update on the image represented
            by ``value``.

        :returns: The updated image
        :rtype: :class:`~openstack.image.v1.image.Image`
        """
        return self._update(_image.Image, image, **attrs)

    def download_image(self, image, stream=False, output=None,
                       chunk_size=1024):
        """Download an image

        This will download an image to memory when ``stream=False``, or allow
        streaming downloads using an iterator when ``stream=True``.
        For examples of working with streamed responses, see
        :ref:`download_image-stream-true`.

        :param image: The value can be either the ID of an image or a
            :class:`~openstack.image.v2.image.Image` instance.

        :param bool stream: When ``True``, return a :class:`requests.Response`
            instance allowing you to iterate over the
            response data stream instead of storing its entire
            contents in memory. See
            :meth:`requests.Response.iter_content` for more
            details. *NOTE*: If you do not consume
            the entirety of the response you must explicitly
            call :meth:`requests.Response.close` or otherwise
            risk inefficiencies with the ``requests``
            library's handling of connections.


            When ``False``, return the entire
            contents of the response.
        :param output: Either a file object or a path to store data into.
        :param int chunk_size: size in bytes to read from the wire and buffer
            at one time. Defaults to 1024

        :returns: When output is not given - the bytes comprising the given
            Image when stream is False, otherwise a :class:`requests.Response`
            instance. When output is given - a
            :class:`~openstack.image.v2.image.Image` instance.
        """

        image = self._get_resource(_image.Image, image)

        return image.download(
            self, stream=stream, output=output, chunk_size=chunk_size)
