import os
import logging
from multiprocessing import Process, Queue

#TODO Remove the except once we move to Python 3 only
try:
    from queue import Empty #pylint:disable=F0401
except: #pylint:disable=W0702
    from Queue import Empty #pylint:disable=F0401

from status_reporting_sdk.json_utils import JsonSerializer
from patch_errors import InternalError, ComponentError

from vmware.patching.utils.logging_utils import _LOGGING_FMT
from vmware.patching.executor import hook_executor

LOG_LEVEL = "%(levelname)s"

def _setupLoggingFor(patchModulePath, hook):
    '''Prefix the patching hook logging with the component signature.

    @param patchModulePath: Python module where component patching hooks are defined at
    @type patchModulePath: str

    @param hook: The patching hook which has to be executed. Allowed values are
      defined at extensions.Hook structure.
    @type hook: str
    '''
    if LOG_LEVEL not in _LOGGING_FMT:
        raise ValueError("Unnable to configure the component hook logging")

    # %(asctime)s.%(msecs)dZ MOD_NAME:HOOK_NAME %(levelname)s %(name)s %(message)s
    log_level = "%s:%s %s" % (os.path.basename(patchModulePath), hook, LOG_LEVEL)
    log_format = _LOGGING_FMT.replace(LOG_LEVEL, log_level)

    for hdlr in logging.root.handlers:
        fmt = logging.Formatter(log_format)
        hdlr.setFormatter(fmt)

def startPatchProcess(resultQueue, reportingQueue, patchModulePath, hook, args):
    '''The newer process entry point. The method is responsible to execute the
    patching hook and return push its result in to the queue.

    @param resultQueue: A queue where the hook result should be pushed to.
      The hook result is consisted of tuple of (HookResult and Exception)
      in json serialized string.
    @type resultQueue: multiprocessing.Queue

    @param reportingQueue: A queue to be used for reporting purposes
    @type reportingQueue: multiprocessor.Queue

    @param patchModulePath: Python module where component patching hooks are
      defined at
    @type patchModulePath: str

    @param hook: The patching hook which has to be executed. Allowed values are
      defined at extensions.Hook structure.
    @type hook: str

    @param args: The patching hooks input. The object will be passed to the
      the component patching hook.
    @type args: Serializable
    '''
    _setupLoggingFor(patchModulePath, hook)
    result = None,
    ex = None
    try:
        result = hook_executor.executeHook(patchModulePath, hook, args, reportingQueue)
    except ComponentError as e:
        ex = e

    json_result = JsonSerializer().serialize([result, ex])
    resultQueue.put(json_result)

def executeHook(patchModulePath, hook, args, reportQueue):
    '''Executes a component patch hook in a separate python process. The routine
    waits until the process complete.

    @param patchModulePath: Python module where component patching hooks are defined at
    @type patchModulePath: str

    @param hook: The patching hook which has to be executed. Allowed values are
      defined at extensions.Hook structure.
    @type hook: str

    @param args: The patching hooks input. The object will be passed to the
      the component patching hook.
    @type args: Serializable

    @param reportQueue: A queue to be used for reporting purposes by the patching
      hook
    @type reportQueue: multiprocessor.Queue

    @return: The result of the patching hook
    @rtype: Serializable

    @raise: patch_errors.ComponentError or InternalError: If the component fails to be executed in
      the provided contract.
    '''
    resultQueue = Queue()
    p = Process(target=startPatchProcess, args=(resultQueue, reportQueue, patchModulePath, hook, args))
    p.start()
    # Blocks until the process does not complete
    # TODO iradev: Kill the process if does not complete in some time
    p.join()

    try:
        json_result = resultQueue.get_nowait()
        result, ex = JsonSerializer().deserialize(json_result)

        if ex:
            raise ex

        return result
    except Empty:
        # Thrown if the CPO hook_execution logic fail
        raise InternalError("Cannot execute the component hook")
