# @package      hubzero-submit-monitors
# @file         ProbeMonitor.py
# @copyright    Copyright (c) 2012-2020 The Regents of the University of California.
# @license      http://opensource.org/licenses/MIT MIT
#
# Copyright (c) 2012-2020 The Regents of the University of California.
#
# 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.
#
# HUBzero is a registered trademark of The Regents of the University of California.
#
import os
import re
import signal
import json
import logging

from hubzero.submit.LogMessage       import getLogIDMessage as getLogMessage
from hubzero.submit.BoundConnections import BoundConnections

class ProbeMonitor(BoundConnections):
   def __init__(self,
                listenURI,
                activeProbeDumpPath="monitorProbe.dump"):
      BoundConnections.__init__(self,listenURI,logConnection=False)

      self.logger = logging.getLogger(__name__)

      self.activeProbeDumpPath = activeProbeDumpPath

      self.activeProbes = {}
      self.terminating  = False

      self.loadActiveProbes()


   def terminate(self):
      if not self.terminating:
         self.closeListeningConnection()
         self.dumpActiveProbes()
         self.terminating = True


   def loadActiveProbes(self):
      dumpedProbes = []
      try:
         dumpFile = open(self.activeProbeDumpPath,'r')
         try:
            dumpedProbes = dumpFile.readlines()
         except (IOError,OSError):
            pass
         else:
            for dumpedProbe in dumpedProbes:
               site,timeEvaluated,status,waitRank = dumpedProbe.split()
               self.activeProbes[site] = (int(timeEvaluated),status,int(waitRank))
         finally:
            dumpFile.close()
      except (IOError,OSError):
         pass


   def dumpActiveProbes(self):
      try:
         dumpFile = open(self.activeProbeDumpPath,'w')
         try:
            for activeProbe in self.activeProbes.items():
               dumpFile.write("%s\t%s\t%s\t%s\n" % (activeProbe[0],str(activeProbe[1][0]),activeProbe[1][1],str(activeProbe[1][2])))
         except (IOError,OSError):
            pass
         finally:
            dumpFile.close()
      except (IOError,OSError):
         pass


   def processRequests(self):
      for channel in self.activeChannels:
         message = self.pullMessage(channel,0)
         while message:
            args = message.split()
            if args[0] == 'json':
               jsonMessageLength = int(args[1])
               jsonMessage = self.pullMessage(channel,jsonMessageLength)
               if len(jsonMessage) > 0:
                  try:
                     jsonObject = json.loads(jsonMessage)
                  except ValueError:
                     self.logger.log(logging.ERROR,getLogMessage("JSON object %s could not be decoded" % (jsonMessage)))
                  else:
                     probeDesignator = jsonObject['probeDesignator']
                     if   jsonObject['messageType'] == 'deleteProbe':
                        probeDeleted = False
                        if probeDesignator in self.activeProbes:
                           try:
                              del self.activeProbes[probeDesignator]
                           except:
                              self.logger.log(logging.ERROR,getLogMessage("delete %s failed" % (probeDesignator)))
                           else:
                              probeDeleted = True
                        returnMessage = {'messageType':'probeDelete',
                                         'probeDesignator':probeDesignator,
                                         'success':probeDeleted}
                        self.postJsonMessage(channel,returnMessage)
                     elif jsonObject['messageType'] == 'queryProbe':
                        if probeDesignator in self.activeProbes:
                           timeEvaluated,status,waitRank = self.activeProbes[probeDesignator]
                        else:
                           timeEvaluated,status,waitRank = (0,"FAILED",0)
                        returnMessage = {'messageType':'probeInfo',
                                         'probeDesignator':probeDesignator,
                                         'timeEvaluated':timeEvaluated,
                                         'status':status,
                                         'waitRank':waitRank}
                        self.postJsonMessage(channel,returnMessage)
                     elif jsonObject['messageType'] == 'updateProbe':
                        timeEvaluated = jsonObject['timeEvaluated']
                        status        = jsonObject['status']
                        waitTime      = jsonObject['waitTime']/60.
                        if   waitTime <= 5.:
                           waitRank = 100
                        elif waitTime <= 10.:
                           waitRank = 90
                        elif waitTime <= 15.:
                           waitRank = 80
                        elif waitTime <= 30.:
                           waitRank = 70
                        elif waitTime <= 60.:
                           waitRank = 60
                        elif waitTime <= 120.:
                           waitRank = 50
                        elif waitTime <= 180.:
                           waitRank = 40
                        elif waitTime <= 240.:
                           waitRank = 30
                        elif waitTime <= 300.:
                           waitRank = 20
                        elif waitTime <= 360.:
                           waitRank = 10
                        else:
                           waitRank = 0
                        self.activeProbes[probeDesignator] = (timeEvaluated,status,waitRank)
                        self.dumpActiveProbes()
                        returnMessage = {'messageType':'probeInfo',
                                         'probeDesignator':probeDesignator,
                                         'timeEvaluated':timeEvaluated,
                                         'status':status,
                                         'waitTime':jsonObject['waitTime'],
                                         'waitRank':waitRank}
                        self.postJsonMessage(channel,returnMessage)
                     elif jsonObject['messageType'] == 'reportProbe':
                        returnMessage = {'messageType':'probeReport',
                                         'statusReport':{}}
                        for probeDesignator in self.activeProbes:
                           timeEvaluated,status,waitRank = self.activeProbes[probeDesignator]
                           returnMessage['statusReport'][probeDesignator] = {'timeEvaluated':timeEvaluated,
                                                                             'status':status,
                                                                             'waitRank':waitRank}
                        self.postJsonMessage(channel,returnMessage)
                     else:
                        self.logger.log(logging.ERROR,getLogMessage("Discarded message type: %s" % (jsonObject['messageType'])))
               else:
                  self.pushMessage(channel,message + '\n')
                  break
            else:
               self.logger.log(logging.ERROR,getLogMessage("Discarded message: %s" % (message)))

            message = self.pullMessage(channel,0)


