# Copyright 2014 VMware, Inc.  All rights reserved. -- VMware Confidential
#
'''Component question notifier.
'''

import uuid
import time
import os
import tempfile

from .execution_settings import getCoreExecutionSettings
from .defaultStatusFunctor import SimpleComponentQuestionHandler
from .componentStatus import QuestionInfo
from .baseCISException import Timedout

def exeWithinTimeout(timeout, fun, *args):
   stime= time.time()
   rtn = None
   while True:
      rtn = fun(*args)
      if rtn:
         # posted question successfully
         break
      elif timeout > -1 and time.time() - stime > timeout:
         raise Timedout("unable to handle question for %s seconds" % timeout)
      else:
         time.sleep(1)

   return rtn

class QuestionNotifier(object):
   def __init__(self, setting=None, questionHandler=None,
                statusFile=None, timeout=-1):
      '''Reporter constructor
      @param setting: the execution setting. If unset, global
      execution setting will be used
      @type setting: CoreExecutionSettings

      @param questionHandler: custom functor to update internal status with
      question or reply. If not specified, a default functor updates a
      local file (specified via statusFile) a serialized
      ComponentsExecutionStatusInfo instance.
      @type questionHandler: Object with callable interface to interact with
      internal component status.
      * setQuestion: to post a question, return false is already question
      pending (since we support one outstanding question a time)
      * clearQuestion: to clear question/answer
      * getReply: return answer if available, None otherwise
      See SimpleComponentQuestionHandler for an example.

      @param statusFile: Currently used to specify the path to status file
      when updateFun is unset. It is ignored if updateFun is set.
      @type statusFile: string

      @param timeout: Specifies the timeout to block both for posting question
      and retrieving reply. When times out, the postQuestion function instead
      of return the reply, it would throw a TimedoutException fault. A timeout
      of -1 would disable timeout. Defaults disabled.
      @type timeout: integer

      '''
      if not setting:
         self._cName = getCoreExecutionSettings().componentName
         self._silent = getCoreExecutionSettings().silentInstall
      else:
         self._cName = setting.componentName
         self._silent = setting.silentInstall
      assert(self._cName)

      if not questionHandler:
         assert(statusFile)
         self._updateFun = SimpleComponentQuestionHandler(statusFile,
                                                          self._cName)
      else:
         self._updateFun = questionHandler

      self._timeout = timeout

   def postQuestion(self, question):
      '''Post a question and waits for reply.

      @param question: The question to ask.
      @type question: QuestionInfo.

      @returns the index to the multiple choice.
      '''
      if self._silent:
         return self._handleSilent(question)

      if question.id:
         raise ValueError('Invalid question id')
      question.id = uuid.uuid1().hex

      if not question.reply_file:
         question.reply_file = os.path.join(
            tempfile.gettempdir(), question.id + ".json")
      elif not os.path.isabs(question.reply_file):
         raise ValueError('Question reply file path should be absolute')

      exeWithinTimeout(self._timeout, self._updateFun.setQuestion, question)
      reply = exeWithinTimeout(self._timeout, self._updateFun.getReply)
      self._updateFun.clearQuestion()

      # return the reply
      return reply.answer

   def _handleSilent(self, question):
      '''Handle silent answers based on question type.

      @param question: The question ask to the user.
      @type question: QuestionInfo
      '''
      if question.type == QuestionInfo.Type.YES_NO:
         return 1
      elif question.type == QuestionInfo.Type.OK_CANCEL:
         return 1
      elif question.type == QuestionInfo.Type.ABORT_RETRY_IGNORE:
         return 2
      else:
         raise ValueError("Invalid question type %s" % question.type)
