
from __future__ import absolute_import

import logging

from vdsm import constants
from vdsm.common import commands
from vdsm.storage import curlImgWrap
from vdsm.storage import exception as se

log = logging.getLogger("storage.ImageSharing")
WAIT_TIMEOUT = 30
BUFFER_SIZE = 65536


def httpGetSize(methodArgs):
    headers = curlImgWrap.head(methodArgs.get('url'),
                               methodArgs.get("headers", {}))

    size = None

    if 'Content-Length' in headers:
        size = int(headers['Content-Length'])

    if 'X-Image-Meta-Size' in headers:
        size = max(size, int(headers['X-Image-Meta-Size']))

    if size is None:
        raise RuntimeError("Unable to determine image size")

    return size


def getLengthFromArgs(methodArgs):
    return methodArgs['length']


def httpDownloadImage(dstImgPath, methodArgs):
    curlImgWrap.download(methodArgs.get('url'), dstImgPath,
                         methodArgs.get("headers", {}))


def httpUploadImage(srcImgPath, methodArgs):
    curlImgWrap.upload(methodArgs.get('url'), srcImgPath,
                       methodArgs.get("headers", {}))


def copyToImage(dstImgPath, methodArgs):
    totalSize = getLengthFromArgs(methodArgs)
    fileObj = methodArgs['fileObj']

    cmd = [
        constants.EXT_DD,
        "of=%s" % dstImgPath,
        "bs=%s" % constants.MEGAB,
        "conv=fsync",
    ]

    p = commands.execCmd(cmd, sync=False)
    with commands.terminating(p):
        _copyData(fileObj, p.stdin, totalSize)
        p.stdin.close()
        if not p.wait(WAIT_TIMEOUT):
            log.error("timeout waiting for dd process")
            raise se.StorageException()

        if p.returncode != 0:
            log.error("dd error - code %s, stderr %s",
                      p.returncode, p.stderr.read(1000))
            raise se.MiscFileWriteException()


def copyFromImage(dstImgPath, methodArgs):
    fileObj = methodArgs['fileObj']
    bytes_left = total_size = methodArgs['length']

    cmd = [
        constants.EXT_DD,
        "if=%s" % dstImgPath,
        "bs=%s" % constants.MEGAB,
        "count=%s" % (total_size // constants.MEGAB + 1),
        "iflag=direct",
    ]

    p = commands.execCmd(cmd, sync=False)
    p.blocking = True
    with commands.terminating(p):
        _copyData(p.stdout, fileObj, bytes_left)


def _copyData(inFile, outFile, totalSize):
    bytesToRead = totalSize
    while totalSize > 0:
        toRead = min(BUFFER_SIZE, totalSize)

        try:
            data = inFile.read(toRead)
        except IOError as e:
            error = "error reading file: %s" % e
            log.error(error)
            raise se.MiscFileReadException(error)

        if not data:
            error = "partial data %s from %s" % \
                    (bytesToRead - totalSize, bytesToRead)
            log.error(error)
            raise se.MiscFileReadException(error)

        outFile.write(data)
        outFile.flush()

        totalSize = totalSize - len(data)


_METHOD_IMPLEMENTATIONS = {
    'http': (httpGetSize, httpDownloadImage, httpUploadImage),
}


def _getSharingMethods(methodArgs):
    try:
        method = methodArgs['method']
    except KeyError:
        raise RuntimeError("Sharing method not specified")

    try:
        return _METHOD_IMPLEMENTATIONS[method]
    except KeyError:
        raise RuntimeError("Sharing method %s not found" % method)


def getSize(methodArgs):
    getSizeImpl, _, _ = _getSharingMethods(methodArgs)
    return getSizeImpl(methodArgs)


def download(dstImgPath, methodArgs):
    _, downloadImageImpl, _ = _getSharingMethods(methodArgs)
    downloadImageImpl(dstImgPath, methodArgs)


def upload(srcImgPath, methodArgs):
    _, _, uploadImageImpl = _getSharingMethods(methodArgs)
    uploadImageImpl(srcImgPath, methodArgs)
