# Copyright 2014 VMware, Inc.  All rights reserved. -- VMware Confidential
'''
Definition execution status, includes
- public status information the data that will be serialized in the published
  status file
- internal component execution status, that will be used as raw data to
  compose the public status.
'''

from . import json_utils

class ProgressData(json_utils.Serializable):
   class State(object):
      RUNNING = "running"
      ERROR = "error"
      SUCCESS = "success"

   def __init__(self, status=None, percentage=0, progress_message=None):
      '''Initializes the progress data.
      @param status: Execution state.
      @type status: State

      @param percentage: An integer of [1, 100] specifying the progress.
      @type percentage: integer

      @param progress_message: A message describing the current progress or
      an error with error message details
      @param progress_message: LocalizableMessage or ErrorInfo
      '''
      self.status = status
      if not self.status:
         self.status = ProgressData.State.RUNNING

      self.percentage = percentage
      self.progress_message = progress_message

   def __eq__(self, other):
      if not isinstance(other, ProgressData):
         return False
      return self.status == other.status and\
          self.percentage == other.percentage and\
          self.progress_message == other.progress_message

   def __ne__(self, other):
      return not self.__eq__(other)

   def __hash__(self, other):
      return hash((self.status, self.percentage, self.progress_message))

class MessageInfo(json_utils.Serializable):
   ''' Represents a message sent to the user. It can be info or warning or error.
   '''
   class Severity(object):
      ''' Represents message severity.
      '''
      INFO = 0
      WARNING = 1
      ERROR = 2

   def __init__(self, msg, resolution=None, problemId=None):
      '''Initialization.

      @param msg: The localizable message
      @type msg: LocalizableMessage

      @param resolution: localizable message briefing problem resolution
      @type: LocalizableMessage

      @param problemId: Unique problem identificator which is mapped to
         external VMWare KB article explaining the problem.
      @type problemId: string
      '''
      self.msg = msg
      self.resolution = resolution
      self.problemId = problemId

   def __eq__(self, other):
      if not isinstance(other, MessageInfo):
         return False
      return self.msg == other.msg and\
          self.resolution == other.resolution and\
          self.problemId == other.problemId

   def __ne__(self, other):
      return not self.__eq__(other)

   def __hash__(self, other):
      return hash((self.msg, self.resolution, self.problemId))

class ErrorInfo(json_utils.Serializable):
   '''Represents error information detail available in  base install
   exception.
   '''
   def __init__(self, detail, componentKey=None, resolution=None,
                problemId=None):
      '''Initializes the error info.

      @param detail: The error root cause
      @type detail: array of LocalizableMessage

      @param componentKey: the component key where the error originates
      @type componentKey: string

      @param resolution: localizable message briefing problem resolution
      @type: LocalizableMessage

      @param problemId: Unique problem identificator which is mapped to
      external VMWare KB article explaining the problem.
      @type problemId: string
      '''
      self.detail = detail
      self.componentKey = componentKey
      self.problemId = problemId
      self.resolution = resolution

   def appendErrorDetail(self, cause):
      '''append information to error stack

      @param cause: error message
      @type: LocalizableMessage
      '''
      self.detail.append(cause)

   def __eq__(self, other):
      if not isinstance(other, ErrorInfo):
         return False
      return self.detail == other.detail and\
          self.componentKey == other.componentKey and\
          self.resolution == other.resolution and\
          self.problemId == other.problemId

   def __ne__(self, other):
      return not self.__eq__(other)

   def __hash__(self, other):
      return hash((self.componentKey, self.resolution, self.problemId))

class ReplyInfo(json_utils.Serializable):
   '''Reply to a question
   '''
   def __init__(self, reply_to, answer):
      '''Initialization.

      @param reply_to: the question ID this reply applies to
      @type reply_to: string

      @param answer: the index to the multi-choices posted by question.
      @type answer: integer
      '''
      self.reply_to = reply_to
      self.answer = answer

class QuestionInfo(json_utils.Serializable):
   '''Represents the question for the user.
   '''
   class Type(object):
      '''Question type.
      '''
      YES_NO = "YES_NO"
      OK_CANCEL = "OK_CANCEL"
      ABORT_RETRY_IGNORE = "ABORT_RETRY_IGNORE"

   def __init__(self, text, question_type=None, default=0, reply_file=None):
      '''Create an instance of QuestionInfo.

      @param text: The question text to be displayed to the user.
      @type text: LocalizableMessage

      @param question_type: the type of question. Default is YES_NO.
      @type question_type: Type

      @param default: The default answer to the question, an index to the
         choices corresponding to a question type
      @type default: integer

      @param reply_file: the reply file. Auto generated if unset.
      @type reply_file: string
      '''
      self.id = None
      self.type = question_type or QuestionInfo.Type.YES_NO
      self.default = default
      self.text = text
      self.reply_file = reply_file

   @classmethod
   def __deserialize__(cls, jObject):
      '''Decode the object from json object.
      '''
      qInfo = QuestionInfo(None, None)
      for a in vars(qInfo):
         setattr(qInfo, a, jObject[a])
      return qInfo

   def __eq__(self, other):
      if not isinstance(other, QuestionInfo):
         return False
      return self.type == other.type and\
         self.default == other.default and\
         self.text == other.text

   def __ne__(self, other):
      return not self.__eq__(other)

   def __hash__(self, other):
      return hash((self.type, self.default, self.text))

class ExecutionStatusInfo(json_utils.Serializable):
   '''The public exection status information, the public status file
   contains an exact json dump of this class instance.
   '''
   def __init__(self, progress=None, info=None, warning=None,
                error=None, question=None, start_time=None, end_time=None):
      '''Initializes the status.
      @param progress: execution progress information
      @type progress: ProgressData

      @param info: informational message
      @type info: array of MessageInfo

      @param warning: warning message
      @type warning: array of MessageInfo

      @param error: error information from exception
      @type error: ErrorInfo

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

      @param start_time: The start time of the task - 2017-02-15T05:38:30.086Z
      @type start_time: str

      @param end_time: The end time of the task - 2017-02-15T05:40:54.380Z
      @type end_time: str
      '''
      if progress is None:
         self.progress = 0
         self.progress_message = None
         self.status = ProgressData.State.RUNNING
      else:
         self.progress = progress.percentage
         self.progress_message = progress.progress_message
         self.status = progress.status

      self.info = info or []
      self.warning = warning or []
      self.question = question
      self.setError(error)
      self.start_time = start_time
      self.end_time = end_time

   def setSuccess(self):
      '''Mark the execution as succeeded
      '''
      self.progress = 100
      self.status = ProgressData.State.SUCCESS

   def setError(self, ex) :
      '''Mark the execution error.
      @param ex: error exception info.
      @type ex: ErrorInfo
      '''
      self.error = ex
      if ex:
         self.status = ProgressData.State.ERROR

   def __eq__(self, other):
      if not isinstance(other, ExecutionStatusInfo):
         return False
      return self.progress == other.progress and\
          self.progress_message == other.progress_message and\
          self.status == other.status and\
          self.info == other.info and\
          self.warning == other.warning and\
          self.error == other.error and\
          self.question == other.question and\
          self.end_time == other.end_time and\
          self.start_time == other.start_time

   def __ne__(self, other):
      return not self.__eq__(other)

class ComponentsExecutionStatusInfo(json_utils.Serializable):
   '''The internal status from all components
   '''
   def __init__(self):
      '''Initialization.
      '''
      self.allProgress = {}
      self.info = []
      self.warning = []
      self.error = None
      self.question = None
      self.reply = None
      self.lastProgressMessage = None

   @classmethod
   def __deserialize__(cls, jObject):
      '''Decode the object from json object.
      '''
      statusObj = ComponentsExecutionStatusInfo()
      for a in vars(statusObj):
         setattr(statusObj, a, jObject[a])
      return statusObj

   def setQuestion(self, question):
      '''Set a question.
      @param question: the question to post
      @type question: QuestionInfo
      '''
      self.question = question

   def appendInfo(self, infoMsg) :
      '''Add an information message to the execution status.
      @param info: informational message
      @type info: MessageInfo
      '''
      assert(infoMsg)
      self.info.append(infoMsg)

   def appendWarning(self, warnMsg) :
      '''Add a warning message to the execution status.

      @param warning: warning message
      @type warning: MessageInfo
      '''
      assert(warnMsg)
      self.warning.append(warnMsg)

   def setError(self, errInfo) :
      '''Mark the execution exception.
      @param errInfo: error information
      @type errInfo: ErrorInfo
      '''
      if not errInfo:
         self.error = None
      else:
         self.error = errInfo
         compKey = self.error.componentKey
         assert(compKey)
         self.allProgress.setdefault(compKey, ProgressData()).status = ProgressData.State.ERROR

   def updateComponentProgress(self, componentKey, progress):
      '''Update the execution progress of a component

      @param componentKey: the component key for the status
      @type componentKey: string

      @param progress: execution progress of the component
      @type componentStatus: ProgressData
      '''
      assert(componentKey and progress)
      self.allProgress[componentKey] = progress
      if progress.progress_message is not None:
         self.lastProgressMessage = progress.progress_message

   def getComponentProgress(self, componentKey):
      '''Get the execution progress of a component

      @param componentKey: the component key
      @type componentKey: string
      '''
      assert(componentKey)
      if componentKey in self.allProgress:
         return self.allProgress[componentKey]
      else:
         return None
