#
#   Copyright 2016 RIFT.IO Inc
#
#   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.
#
# Author(s): Varun Prasad
# Creation Date: 09/28/2016
#

import tornado.httpclient
import tornado.web
import tornadostreamform.multipart_streamer as multipart_streamer
import logging
import os

MB = 1024 * 1024
GB = 1024 * MB

MAX_STREAMED_SIZE = 5 * GB

class HttpMessageError(Exception):
    def __init__(self, code, msg):
        self.code = code
        self.msg = msg


class RequestHandler(tornado.web.RequestHandler):
    def options(self, *args, **kargs):
        pass

    def set_default_headers(self):
        self.set_header('Access-Control-Allow-Origin', '*')
        self.set_header('Access-Control-Allow-Headers',
                        'Content-Type, Cache-Control, Accept, X-Requested-With, Authorization')
        self.set_header('Access-Control-Allow-Methods', 'POST, GET, PUT, DELETE')


class StoreStreamerPart(multipart_streamer.MultiPartStreamer):
    """
    Create a Part streamer with a custom temp directory. Using the default
    tmp directory and trying to move the file to $RIFT_ARTIFACTS occasionally
    causes link errors. So create a temp directory within the staging area.
    """
    def __init__(self, store, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.store = store

    def create_part(self, headers):
        return multipart_streamer.TemporaryFileStreamedPart(self, headers, tmp_dir=self.store.tmp_dir)


@tornado.web.stream_request_body
class UploadStagingHandler(RequestHandler):
    def initialize(self, store):
        """Initialize the handler

        Arguments:
            log  - the logger that this handler should use
            loop - the tasklets ioloop

        """
        self.log = logging.getLogger()
        self.store = store

        self.part_streamer = None

    @tornado.gen.coroutine
    def prepare(self):
        """Prepare the handler for a request

        The prepare function is the first part of a request transaction. It
        creates a temporary file that uploaded data can be written to.

        """
        if self.request.method != "POST":
            return

        self.request.connection.set_max_body_size(MAX_STREAMED_SIZE)

        # Retrieve the content type and parameters from the request
        content_type = self.request.headers.get('content-type', None)
        if content_type is None:
            raise tornado.httpclient.HTTPError(400, "No content type set")

        content_type, params = tornado.httputil._parse_header(content_type)

        if "multipart/form-data" != content_type.lower():
            raise tornado.httpclient.HTTPError(415, "Invalid content type")

        # You can get the total request size from the headers.
        try:
            total = int(self.request.headers.get("Content-Length", "0"))
        except KeyError:
            self.log.warning("Content length header not found")
            # For any well formed browser request, Content-Length should have a value.
            total = 0

        # And here you create a streamer that will accept incoming data
        self.part_streamer = StoreStreamerPart(self.store, total)


    @tornado.gen.coroutine
    def data_received(self, chunk):
        """When a chunk of data is received, we forward it to the multipart streamer."""
        self.part_streamer.data_received(chunk)

    def post(self, staging_id):
        """Handle a post request

        The function is called after any data associated with the body of the
        request has been received.

        """
        try:
            # You MUST call this to close the incoming stream.
            self.part_streamer.data_complete()
            desc_parts = self.part_streamer.get_parts_by_name("file")
            if len(desc_parts) != 1:
                raise tornado.httpclient.HTTPError(400, "File option not found")

            binary_data = desc_parts[0]
            staging_area = self.store.get_staging_area(staging_id)
            filename = binary_data.get_filename()
            staging_area.model.name = filename
            staging_area.model.size = binary_data.get_size()

            dest_file = os.path.join(staging_area.model.path, filename)
            binary_data.move(dest_file)

            self.set_status(200)
            self.write(tornado.escape.json_encode({
                "path": "/api/download/{}/{}".format(staging_id, filename)
                    }))

        finally:
            self.part_streamer.release_parts()
            self.finish()

