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

from openstack.object_store import object_store_service
from openstack.object_store.v1 import _base
from openstack import resource


class Object(_base.BaseResource):
    _custom_metadata_prefix = "X-Object-Meta-"
    _system_metadata = {
        "content_disposition": "content-disposition",
        "content_encoding": "content-encoding",
        "content_type": "content-type",
        "delete_after": "x-delete-after",
        "delete_at": "x-delete-at",
        "is_content_type_detected": "x-detect-content-type",
    }

    base_path = "/%(container)s"
    service = object_store_service.ObjectStoreService()
    id_attribute = "name"

    allow_create = True
    allow_retrieve = True
    allow_update = True
    allow_delete = True
    allow_list = True
    allow_head = True

    # Data to be passed during a POST call to create an object on the server.
    data = None

    # URL parameters
    #: The unique name for the container.
    container = resource.prop("container")
    #: The unique name for the object.
    name = resource.prop("name")

    # Object details
    hash = resource.prop("hash")
    bytes = resource.prop("bytes")

    # Headers for HEAD and GET requests
    #: If set to True, Object Storage queries all replicas to return
    #: the most recent one. If you omit this header, Object Storage
    #: responds faster after it finds one valid replica. Because
    #: setting this header to True is more expensive for the back end,
    #: use it only when it is absolutely needed. *Type: bool*
    is_newest = resource.header("x-newest", type=bool)
    #: TODO(briancurtin) there's a lot of content here...
    range = resource.header("range", type=dict)
    #: See http://www.ietf.org/rfc/rfc2616.txt.
    if_match = resource.header("if-match", type=dict)
    #: In combination with Expect: 100-Continue, specify an
    #: "If-None-Match: \*" header to query whether the server already
    #: has a copy of the object before any data is sent.
    if_none_match = resource.header("if-none-match", type=dict)
    #: See http://www.ietf.org/rfc/rfc2616.txt.
    if_modified_since = resource.header("if-modified-since", type=dict)
    #: See http://www.ietf.org/rfc/rfc2616.txt.
    if_unmodified_since = resource.header("if-unmodified-since", type=dict)

    # Query parameters
    #: Used with temporary URLs to sign the request. For more
    #: information about temporary URLs, see OpenStack Object Storage
    #: API v1 Reference.
    signature = resource.header("signature")
    #: Used with temporary URLs to specify the expiry time of the
    #: signature. For more information about temporary URLs, see
    #: OpenStack Object Storage API v1 Reference.
    expires_at = resource.header("expires")
    #: If you include the multipart-manifest=get query parameter and
    #: the object is a large object, the object contents are not
    #: returned. Instead, the manifest is returned in the
    #: X-Object-Manifest response header for dynamic large objects
    #: or in the response body for static large objects.
    multipart_manifest = resource.header("multipart-manifest")

    # Response headers from HEAD and GET
    #: HEAD operations do not return content. However, in this
    #: operation the value in the Content-Length header is not the
    #: size of the response body. Instead it contains the size of
    #: the object, in bytes.
    content_length = resource.header("content-length")
    #: The MIME type of the object.
    content_type = resource.header("content-type")
    #: The type of ranges that the object accepts.
    accept_ranges = resource.header("accept-ranges")
    #: For objects smaller than 5 GB, this value is the MD5 checksum
    #: of the object content. The value is not quoted.
    #: For manifest objects, this value is the MD5 checksum of the
    #: concatenated string of MD5 checksums and ETags for each of
    #: the segments in the manifest, and not the MD5 checksum of
    #: the content that was downloaded. Also the value is enclosed
    #: in double-quote characters.
    #: You are strongly recommended to compute the MD5 checksum of
    #: the response body as it is received and compare this value
    #: with the one in the ETag header. If they differ, the content
    #: was corrupted, so retry the operation.
    etag = resource.header("etag")
    #: Set to True if this object is a static large object manifest object.
    #: *Type: bool*
    is_static_large_object = resource.header("x-static-large-object",
                                             type=bool)
    #: If set, the value of the Content-Encoding metadata.
    #: If not set, this header is not returned by this operation.
    content_encoding = resource.header("content-encoding")
    #: If set, specifies the override behavior for the browser.
    #: For example, this header might specify that the browser use
    #: a download program to save this file rather than show the file,
    #: which is the default.
    #: If not set, this header is not returned by this operation.
    content_disposition = resource.header("content-disposition")
    #: Specifies the number of seconds after which the object is
    #: removed. Internally, the Object Storage system stores this
    #: value in the X-Delete-At metadata item.
    delete_after = resource.header("x-delete-after", type=int)
    #: If set, the time when the object will be deleted by the system
    #: in the format of a UNIX Epoch timestamp.
    #: If not set, this header is not returned by this operation.
    delete_at = resource.header("x-delete-at")
    #: If set, to this is a dynamic large object manifest object.
    #: The value is the container and object name prefix of the
    #: segment objects in the form container/prefix.
    object_manifest = resource.header("x-object-manifest")
    #: The timestamp of the transaction.
    timestamp = resource.header("x-timestamp")
    #: The date and time that the object was created or the last
    #: time that the metadata was changed.
    last_modified_at = resource.header("last_modified", alias="last-modified")

    # Headers for PUT and POST requests
    #: Set to chunked to enable chunked transfer encoding. If used,
    #: do not set the Content-Length header to a non-zero value.
    transfer_encoding = resource.header("transfer-encoding")
    #: If set to true, Object Storage guesses the content type based
    #: on the file extension and ignores the value sent in the
    #: Content-Type header, if present. *Type: bool*
    is_content_type_detected = resource.header("x-detect-content-type",
                                               type=bool)
    #: If set, this is the name of an object used to create the new
    #: object by copying the X-Copy-From object. The value is in form
    #: {container}/{object}. You must UTF-8-encode and then URL-encode
    #: the names of the container and object before you include them
    #: in the header.
    #: Using PUT with X-Copy-From has the same effect as using the
    #: COPY operation to copy an object.
    copy_from = resource.header("x-copy-from")

    # The Object Store treats the metadata for its resources inconsistently so
    # Object.set_metadata must override the BaseResource.set_metadata to
    # account for it.
    def set_metadata(self, session, metadata):
        # Filter out items with empty values so the create metadata behaviour
        # is the same as account and container
        filtered_metadata = \
            {key: value for key, value in metadata.items() if value}

        # Get a copy of the original metadata so it doesn't get erased on POST
        # and update it with the new metadata values.
        obj = self.head(session)
        metadata2 = copy.deepcopy(obj.metadata)
        metadata2.update(filtered_metadata)

        # Include any original system metadata so it doesn't get erased on POST
        for key in self._system_metadata:
            value = getattr(obj, key)
            if value and key not in metadata2:
                metadata2[key] = value

        super(Object, self).set_metadata(session, metadata2)

    # The Object Store treats the metadata for its resources inconsistently so
    # Object.delete_metadata must override the BaseResource.delete_metadata to
    # account for it.
    def delete_metadata(self, session, keys):
        # Get a copy of the original metadata so it doesn't get erased on POST
        # and update it with the new metadata values.
        obj = self.head(session)
        metadata = copy.deepcopy(obj.metadata)

        # Include any original system metadata so it doesn't get erased on POST
        for key in self._system_metadata:
            value = getattr(obj, key)
            if value:
                metadata[key] = value

        # Remove the metadata
        for key in keys:
            if key == 'delete_after':
                del(metadata['delete_at'])
            else:
                del(metadata[key])

        url = self._get_url(self, self.id)
        session.post(url, endpoint_filter=self.service,
                     headers=self._calculate_headers(metadata))

    def get(self, session, include_headers=False, args=None):
        url = self._get_url(self, self.id)
        headers = {'Accept': 'bytes'}
        resp = session.get(url, endpoint_filter=self.service, headers=headers)
        resp = resp.content
        self._set_metadata()
        return resp

    def create(self, session):
        url = self._get_url(self, self.id)

        headers = self.get_headers()
        headers['Accept'] = ''
        if self.data is not None:
            resp = session.put(url, endpoint_filter=self.service,
                               data=self.data,
                               headers=headers).headers
        else:
            resp = session.post(url, endpoint_filter=self.service, data=None,
                                headers=headers).headers
        self.set_headers(resp)
        return self
