# Copyright 2014 VMware, Inc.  All rights reserved. -- VMware Confidential
#
'''Simple function to json serialize and deserialize function status
objects that can be used as default status access functors for status
reporting, such as ProgressReporter, QuestionNotifier
'''
import os
from . import json_utils

from .componentStatus import ComponentsExecutionStatusInfo
from .componentStatus import ProgressData
from .componentStatus import MessageInfo
from .componentStatus import QuestionInfo
from .componentStatus import ReplyInfo
from .filelock import FileLock

def updateProgressStatus(rawStatus, cKey, progress):
   ''' Function to update an instance of ComponentsExecutionStatusInfo
   with progress for component specified by component name key.

   @param rawStatus: the status to be updated
   @type rawStatus: ComponentsExecutionStatusInfo

   @param cKey: the name of the component
   @type cKey: string

   @param progress: the progress data
   @type progress: ProgressData

   '''
   if progress.status != ProgressData.State.ERROR:
      rawStatus.updateComponentProgress(cKey, progress)
   else:
      errInfo = progress.progress_message
      rawStatus.setError(errInfo)

def handleQuestion(rawStatus, cKey, info):
   '''Function to handle posting a question, reply or clear question.

   * When posting a question, if there is already a question
     pending, this function will return false. Otherwise it return true.
     In this case, parameter is an QuestionInfo.

   * When posting a reply, this function always returns true.
     In this case, parameter is an ReplyInfo.

   * When clear question, this function always return true.
     In this case, parameter is None.

   @param rawStatus: the status to be updated
   @type rawStatus: ComponentsExecutionStatusInfo

   @param cKey: the name of the component
   @type cKey: string

   @param info: the question or reply to add to the status
   @type info: QuestionInfo or ReplyInfo
   '''
   if not info:
      # reset the question and reply
      rawStatus.question = None
      rawStatus.reply = None
      return True
   elif isinstance(info, QuestionInfo):
      # posting a question
      if rawStatus.question:
         return False
      else:
         rawStatus.question = info
         return True
   elif isinstance(info, ReplyInfo):
      # posting a reply
      assert(rawStatus.question.id == info.reply_to)
      rawStatus.reply = info
      return True
   else:
      raise TypeError("Question or Reply expected")

def updateMessage(rawStatus, cKey, info, severity):
   ''' Function to update an instance of ComponentsExecutionStatusInfo
   with progress for component specified by component name key.

   @param rawStatus: the status to be updated
   @type rawStatus: ComponentsExecutionStatusInfo

   @param cKey: the name of the component
   @type cKey: string

   @param info: the message to add to the status
   @type info: MessageInfo or ErrorInfo

   @param severity: message severity
   @type severity: messageReporter.Severity

   '''
   if severity == MessageInfo.Severity.INFO:
      rawStatus.appendInfo(info)
   elif severity == MessageInfo.Severity.WARNING:
      rawStatus.appendWarning(info)
   elif severity == MessageInfo.Severity.ERROR:
      rawStatus.setError(info)
   else:
      raise ValueError('Invalid severity %d' % severity)

def writeComponentStatus(statusFile, rawStatus):
   '''Persists the component status to local file.

   @param statusFile: the full path to the status file that
   contains status to be updated
   @type statusFile: string

   @param rawStatus: the status to be updated
   @type rawStatus: ComponentsExecutionStatusInfo
   '''
   with open(statusFile, 'w') as fp:
      fileCnt = json_utils.JsonSerializer().serialize(rawStatus)
      fp.write(fileCnt)

class SimpleComponentStatusReader(object):
   '''Read status from specified statusfile. This could be a simple function
   similar to writeComponentStatus, but it is defined as a class so that
   status aggregator can use it as simple raw status getter.
   '''
   def __init__(self, statusFile):
      '''Initialization

      @param statusFile: the full path to the status file that
      contains status to be updated
      @type statusFile: string
      '''
      self._statusFile = statusFile


   def __call__(self):
      rawStatus = None
      if os.path.exists(self._statusFile):
         with open(self._statusFile, 'r') as fp:
            fileCnt = fp.read()
            rawStatus = json_utils.JsonSerializer().deserialize(fileCnt)
      if not rawStatus:
         rawStatus = ComponentsExecutionStatusInfo()
      return rawStatus


class SimpleComponentStatusUpdater(object):
   ''' Update status from status specified statusfile.
   @param statusFile: the full path to the status file  that contains status
   to be updated
   @type statusFile: string

   @param fun: the callback to updates the component status
   @type fun: a callback takes a ComponentsExecutionStatusInfo instance
   and updates it.
   '''
   def __init__(self, statusFile, componentName, fun):
      self._statusFile = statusFile
      self._compName = componentName
      self._fun = fun

   def __call__(self, *args):
      '''
      @param callback: the callback to updates the component status
      @type callback: a callback takes a
      ComponentsExecutionStatusInfo instance and updates it.
      '''
      with FileLock(self._statusFile):
         rawStatus = SimpleComponentStatusReader(self._statusFile)()
         rtn = self._fun(rawStatus, self._compName, *args)
         writeComponentStatus(self._statusFile, rawStatus)
         return rtn

class SimpleComponentQuestionHandler(object):
   ''' Update status from status specified statusfile.
   @param statusFile: the full path to the status file  that contains status
   to be updated
   @type statusFile: string

   @param fun: the callback to updates the component status
   @type fun: a callback takes a ComponentsExecutionStatusInfo instance
   and updates it.
   '''
   def __init__(self, statusFile, componentName):
      self._statusFile = statusFile
      self._updater = SimpleComponentStatusUpdater(statusFile,
                                                   componentName,
                                                   handleQuestion)

   def setQuestion(self, question):
      '''Post question to status.

      @param question: the question add to the status
      @type question: QuestionInfo
      '''
      return self._updater(question)

   def setReply(self, reply):
      '''Post reply to status.

      @param reply: the reply to add to the status
      @type reply: ReplyInfo
      '''
      return self._updater(reply)

   def getReply(self):
      '''Retrieve reply from status if any.
      '''
      with FileLock(self._statusFile):
         rawStatus = SimpleComponentStatusReader(self._statusFile)()
         return rawStatus.reply

   def clearQuestion(self):
      '''Clear question and reply status.
      '''
      return self._updater(None)

def createSimpleComponentProgressUpdater(statusFile, componentName):
   return SimpleComponentStatusUpdater(statusFile, componentName,
                                       updateProgressStatus)

def createSimpleComponentMessageUpdater(statusFile, componentName):
   return SimpleComponentStatusUpdater(statusFile, componentName,
                                       updateMessage)
