#-------------------------------------------------------------------------
#
# Batch Apps Blender Addon
#
# Copyright (c) Microsoft Corporation.  All rights reserved.
#
# MIT License
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the ""Software""), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
#
#--------------------------------------------------------------------------

import bpy

import logging
import random
import os
import string
import threading
import time

from batchapps_blender.ui import ui_submission
from batchapps_blender.props import props_submission
from batchapps_blender.utils import BatchAppsOps

from batchapps.exceptions import (
    SessionExpiredException,
    AuthenticationException,
    InvalidConfigException)


class BatchAppsSubmission(object):
    """
    Manages the creation and submission of a new job.
    """

    pages = ['SUBMIT', 'PROCESSING', 'SUBMITTED']

    def __init__(self, job_mgr, file_mgr, pool_mgr):

        self.batchapps_job = job_mgr
        self.batchapps_files = file_mgr
        self.batchapps_pool = pool_mgr

        self.ops = self._register_ops()
        self.props = self._register_props()
        self.ui = self._register_ui()

    def display(self, ui, layout):
        """
        Invokes the corresponding ui function depending on the session's
        current page.

        :Args:
            - ui (blender :class:`.Interface`): The instance of the Interface
              panel class.
            - layout (blender :class:`bpy.types.UILayout`): The layout object,
              derived from the Interface panel. Used for creating ui
              components.

        :Returns:
            - Runs the display function for the applicable page.
        """
        return self.ui[bpy.context.scene.batchapps_session.page](ui, layout)

    def _register_props(self):
        """
        Registers and retrieves the submission property objects.page
        The dispaly properties are defined in a subclass which is assigned
        to the scene.batchapps_submission context.

        :Returns:
            - :class:`.SubmissionProps`
        """
        props = props_submission.register_props()
        return props
        
    def _register_ops(self):
        """
        Registers each job submission operator with a batchapps_submission
        prefix.

        :Returns:
            - A list of the names (str) of the registered job submission
              operators.
        """
        ops = []
        ops.append(BatchAppsOps.register("submission.page",
                                         "Create new job",
                                         self._submission))
        ops.append(BatchAppsOps.register("submission.start",
                                         "Submit job",
                                         self._start))
        ops.append(BatchAppsOps.register("submission.processing",
                                         "Submitting new job",
                                         modal=self._processing_modal,
                                         invoke=self._processing_invoke,
                                         _timer=None))
        return ops

    def _register_ui(self):
        """
        Matches the submit, processing and completed pages with their
        corresponding ui functions.

        :Returns:
            - A dictionary mapping the page name to its corresponding
              ui function.
        """
        def get_ui(name):
            name = name.lower()
            return getattr(ui_submission, name)

        page_func = map(get_ui, self.pages)
        return dict(zip(self.pages, page_func))

    def _processing_modal(self, op, context, event):
        """
        The modal method for the submission.processing operator to handle
        running the submission of a job in a separate thread to prevent
        the blocking of the Blender UI.

        :Args:
            - op (:class:`bpy.types.Operator`): An instance of the current
              operator class.
            - context (:class:`bpy.types.Context`): The current blender
              context.
            - event (:class:`bpy.types.Event`): The blender invocation event.

        :Returns:
            - If the thread has completed, the Blender-specific value
              {'FINISHED'} to indicate the operator has completed its action.
            - Otherwise the Blender-specific value {'RUNNING_MODAL'} to
              indicate the operator wil continue to process after the
              completion of this function.
        """
        if event.type == 'TIMER':
            context.scene.batchapps_session.log.debug("SubmitThread complete.")
            if not self.props.thread.is_alive():
                context.window_manager.event_timer_remove(op._timer)
            return {'FINISHED'}

        return {'RUNNING_MODAL'}

    def _processing_invoke(self, op, context, event):
        """
        The invoke method for the submission.processing operator.
        Starts the job submission thread.

        :Args:
            - op (:class:`bpy.types.Operator`): An instance of the current
              operator class.
            - context (:class:`bpy.types.Context`): The current blender
              context.
            - event (:class:`bpy.types.Event`): The blender invocation event.

        :Returns:
            - Blender-specific value {'RUNNING_MODAL'} to indicate the operator
              will continue to process after the completion of this function.
        """
        self.props.thread.start()
        context.scene.batchapps_session.log.debug("SubmitThread initiated.")

        context.window_manager.modal_handler_add(op)
        op._timer = context.window_manager.event_timer_add(1, context.window)
        return {'RUNNING_MODAL'}

    def _start(self, op, context, *args):
        """
        The execute method for the submission.start operator.
        Sets the functions to be performed by the job submission thread
        and updates the session page to "PROCESSING" while the thread
        executes.

        :Args:
            - op (:class:`bpy.types.Operator`): An instance of the current
              operator class.
            - context (:class:`bpy.types.Context`): The current blender
              context.

        :Returns:
            - Blender-specific value {'FINISHED'} to indicate the operator has
              completed its action.
        """
        submit_thread = lambda: BatchAppsOps.session(self.submit_job)
        self.props.thread = threading.Thread(name="SubmitThread",
                                             target=submit_thread)

        bpy.ops.batchapps_submission.processing('INVOKE_DEFAULT')

        if context.scene.batchapps_session.page == "SUBMIT":
            context.scene.batchapps_session.page = "PROCESSING"
        
        return {'FINISHED'}

    def _submission(self, op, context, *args):
        """
        The execute method for the submission.page operator.
        Sets the page to SUBMIT.

        :Args:
            - op (:class:`bpy.types.Operator`): An instance of the current
              operator class.
            - context (:class:`bpy.types.Context`): The current blender
              context.

        :Returns:
            - Blender-specific value {'FINISHED'} to indicate the operator has
              completed its action.
        """
        bpy.context.scene.batchapps_session.page = "SUBMIT"
        self.valid_scene(context)

        return {'FINISHED'}

    def valid_scene(self, context):
        """