
from __future__ import absolute_import
from __future__ import division
from errno import EINTR
import json
import httplib
import logging
import threading
import re
import socket
import sys

from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer

from vdsm import API
from vdsm.common import concurrent
from vdsm.common.define import doneCode
from vdsm.executor import TaskQueue


class RequestException(Exception):
    def __init__(self, httpStatusCode, errorMessage):
        self.httpStatusCode = httpStatusCode
        self.errorMessage = errorMessage


class Server(object):
    def __init__(self, cif, log):
        self.cif = cif
        self.log = log

        self._enabled = False
        self.server = ThreadedServer(ImageRequestHandler)

    def start(self):
        def threaded_start():
            self.log.info("Server running")
            self.server.timeout = 1
            self._enabled = True

            while self._enabled:
                try:
                    self.server.handle_request()
                except Exception as e:
                    if e[0] != EINTR:
                        self.log.error("http handler exception",
                                       exc_info=True)
            self.log.info("Server stopped")

        self._thread = concurrent.thread(threaded_start, name='http',
                                         log=self.log)
        self._thread.start()

    def add_socket(self, connected_socket, socket_address):
        self.server.add(connected_socket, socket_address)

    def stop(self):
        self.log.info("Stopping http server")
        self._enabled = False
        self.server.server_close()
        self._thread.join()
        return {'status': doneCode}


class ImageRequestHandler(BaseHTTPRequestHandler):

    timeout = 60
    log = logging.getLogger("rpc.http.ImageRequestHandler")

    HEADER_POOL = 'Storage-Pool-Id'
    HEADER_DOMAIN = 'Storage-Domain-Id'
    HEADER_IMAGE = 'Image-Id'
    HEADER_VOLUME = 'Volume-Id'
    HEADER_TASK_ID = 'Task-Id'
    HEADER_RANGE = 'Range'
    HEADER_CONTENT_LENGTH = 'content-length'
    HEADER_CONTENT_TYPE = 'content-type'
    HEADER_CONTENT_RANGE = 'content-range'

    protocol_version = "HTTP/1.1"

    def do_GET(self):
        try:
            length = self._getLength()
            img = self._createImage()
            startEvent = threading.Event()
            methodArgs = {'fileObj': self.wfile,
                          'length': length}

            uploadFinishedEvent, operationEndCallback = \
                self._createEventWithCallback()

            volUUID = self.headers.getheader(self.HEADER_VOLUME)

            response = img.uploadToStream(methodArgs,
                                          operationEndCallback,
                                          startEvent, volUUID)

            if response['status']['code'] == 0:
                self.send_response(httplib.PARTIAL_CONTENT)
                self.send_header(self.HEADER_CONTENT_TYPE,
                                 'application/octet-stream')
                self.send_header(self.HEADER_CONTENT_LENGTH, length)
                self.send_header(self.HEADER_CONTENT_RANGE,
                                 "bytes 0-%d" % (length - 1))
                self.send_header(self.HEADER_TASK_ID, response['uuid'])
                self.end_headers()
                startEvent.set()
                self._waitForEvent(uploadFinishedEvent)
            else:
                self._send_error_response(response)

        except RequestException as e:
            self.send_error(e.httpStatusCode, e.errorMessage)
        except Exception:
            self.send_error(httplib.INTERNAL_SERVER_ERROR,
                            "error during execution",
                            exc_info=True)

    def do_PUT(self):
        try:
            contentLength = self._getIntHeader(
                self.HEADER_CONTENT_LENGTH,
                httplib.LENGTH_REQUIRED)

            img = self._createImage()

            methodArgs = {'fileObj': self.rfile,
                          'length': contentLength}

            uploadFinishedEvent, operationEndCallback = \
                self._createEventWithCallback()

            volUUID = self.headers.getheader(self.HEADER_VOLUME)

            response = img.downloadFromStream(methodArgs,
                                              operationEndCallback,
                                              volUUID)

            if response['status']['code'] == 0:
                while not uploadFinishedEvent.is_set():
                    uploadFinishedEvent.wait()
                self.send_response(httplib.OK)
                self.send_header(self.HEADER_TASK_ID, response['uuid'])
                self.end_headers()
            else:
                self._send_error_response(response)

        except RequestException as e:
            self.send_error(e.httpStatusCode, e.errorMessage)
        except Exception:
            self.send_error(httplib.INTERNAL_SERVER_ERROR,
                            "error during execution",
                            exc_info=True)

    def _createImage(self):
        spUUID = self.headers.getheader(self.HEADER_POOL)
        sdUUID = self.headers.getheader(self.HEADER_DOMAIN)
        imgUUID = self.headers.getheader(self.HEADER_IMAGE)
        if not all((spUUID, sdUUID, imgUUID)):
            raise RequestException(
                httplib.BAD_REQUEST,
                "missing or empty required header(s):"
                " spUUID=%s sdUUID=%s imgUUID=%s"
                % (spUUID, sdUUID, imgUUID))

        return API.Image(imgUUID, spUUID, sdUUID)

    @staticmethod
    def _createEventWithCallback():
        operationFinishedEvent = threading.Event()

        def setCallback():
            operationFinishedEvent.set()

        return operationFinishedEvent, setCallback

    @staticmethod
    def _waitForEvent(event):
        while not event.is_set():
            event.wait()

    def _getIntHeader(self, headerName, missingError):
        value = self._getRequiredHeader(headerName, missingError)

        return self._getInt(value)

    def _getRequiredHeader(self, headerName, missingError):
        value = self.headers.getheader(
            headerName)
        if not value:
            raise RequestException(
                missingError,
                "missing header %s" % headerName)
        return value

    def _getInt(self, value):
        try:
            return int(value)
        except ValueError:
            raise RequestException(
                httplib.BAD_REQUEST,
                "not int value %r" % value)

    def _getLength(self):
        value = self._getRequiredHeader(self.HEADER_RANGE,
                                        httplib.BAD_REQUEST)

        m = re.match(r'^bytes=0-(\d+)$', value)
        if m is None:
            raise RequestException(
                httplib.BAD_REQUEST,
                "Unsupported range: %r , expected: bytes=0-last_byte" %
                value)

        last_byte = m.group(1)
        return self._getInt(last_byte) + 1

    def send_error(self, error, message, exc_info=False):
        try:
            self.log.error(message, exc_info=exc_info)
            self.send_response(error)
            self.end_headers()
        except Exception:
            self.log.error("failed to return response",
                           exc_info=True)

    def _send_error_response(self, response):
        self.send_response(httplib.INTERNAL_SERVER_ERROR)
        json_response = json.dumps(response)
        self.send_header(self.HEADER_CONTENT_TYPE,
                         'application/json')
        self.send_header(self.HEADER_CONTENT_LENGTH,
                         len(json_response))
        self.end_headers()
        self.wfile.write(json_response)


class ThreadedServer(HTTPServer):

    _STOP = object()

    log = logging.getLogger("vds.http.Server")

    def __init__(self, RequestHandlerClass):
        HTTPServer.__init__(self, None, RequestHandlerClass, False)

        self.requestHandler = RequestHandlerClass

        self.queue = TaskQueue("http-server", sys.maxint)

    def add(self, connected_socket, socket_address):
        self.queue.put((connected_socket, socket_address))

    def handle_request(self):
        sock, addr = self.queue.get()
        if sock is self._STOP:
            return
        self.log.info("Starting request handler for %s:%d", addr[0], addr[1])
        t = concurrent.thread(self._process_requests, args=(sock, addr),
                              log=self.log)
        t.start()

    def server_close(self):
        self.queue.clear()
        self.queue.put((self._STOP, self._STOP))

    def _process_requests(self, sock, addr):
        self.log.info("Request handler for %s:%d started", addr[0], addr[1])
        try:
            self.requestHandler(sock, addr, self)
        except Exception:
            self.log.exception("Unhandled exception in request handler for "
                               "%s:%d", addr[0], addr[1])
        finally:
            self._shutdown_connection(sock)
        self.log.info("Request handler for %s:%d stopped", addr[0], addr[1])

    def _shutdown_connection(self, sock):
        try:
            sock.shutdown(socket.SHUT_WR)
        except socket.error:
        finally:
            sock.close()


class HttpDetector():
    log = logging.getLogger("HttpDetector")
    NAME = "http"
    REQUIRED_SIZE = 5

    def __init__(self, server):
        self.server = server

    def detect(self, data):
        return data.startswith("PUT /") or data.startswith("GET /")

    def handle_socket(self, client_socket, socket_address):
        self.server.add_socket(client_socket, socket_address)
        self.log.debug("http detected from %s", socket_address)
