# @package      hubzero-submit-monitors
# @file         JobWSCore.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 pwd
import requests
import time
import subprocess
import select
import traceback
import logging

from hubzero.submit.LogMessage            import getLogIDMessage as getLogMessage
from hubzero.submit.DaemonsInfo           import DaemonsInfo
from hubzero.submit.InfosInfo             import InfosInfo
from hubzero.submit.SitesInfo             import SitesInfo
from hubzero.submit.Tapis2SitesInfo       import Tapis2SitesInfo
from hubzero.submit.Tapis3SitesInfo       import Tapis3SitesInfo
from hubzero.submit.FileMoversInfo        import FileMoversInfo
from hubzero.submit.RemoteIdentityManager import RemoteIdentityManager
from hubzero.submit.RemoteJobMonitor      import RemoteJobMonitor

class JobWSCore:
   def __init__(self,
                daemonsDirectory,
                daemonsConfigurationFile,
                infosDirectory,
                infosConfigurationFile,
                wsTask,
                previousState,
                nextState):
      self.logger = logging.getLogger(__name__)

      self.daemonsDirectory         = daemonsDirectory
      self.daemonsConfigurationFile = daemonsConfigurationFile
      self.infosDirectory           = infosDirectory
      self.infosConfigurationFile   = infosConfigurationFile
      self.wsTask                   = wsTask
      self.wsTaskURL                = None
      self.previousState            = previousState
      self.nextState                = nextState

      self.remoteIdentityManager = None
      self.remoteJobMonitor      = None

      self.daemonsInfo     = None
      self.infosInfo       = None
      self.sitesInfo       = None
      self.tapis2SitesInfo = None
      self.tapis3SitesInfo = None
      self.fileMoversInfo  = None
      self.settingInfo     = False

      self.bufferSize = 4096

      self.terminating = False


   def setInfo(self):
      errorInSetInfo = False
      if not self.settingInfo:
         self.settingInfo = True

         configFilePath = os.path.join(self.daemonsDirectory,self.daemonsConfigurationFile)
         self.daemonsInfo       = DaemonsInfo(configFilePath)
         self.identityListenURI = self.daemonsInfo.getDaemonListenURI('identitiesManager','tcp')
         self.jobListenURI      = self.daemonsInfo.getDaemonListenURI('jobMonitor','tcp')

         self.remoteIdentityManager = RemoteIdentityManager(self.identityListenURI)
         self.remoteJobMonitor      = RemoteJobMonitor(self.jobListenURI)

         configFilePath = os.path.join(self.infosDirectory,self.infosConfigurationFile)
         self.infosInfo = InfosInfo(configFilePath)

         self.sitesInfo      = SitesInfo(self.infosInfo.getInfoPath('sites'),
                                         allowedVenueMechanisms=['*'],
                                         pegasusVersion='*')
         self.tapis2SitesInfo = Tapis2SitesInfo(self.infosInfo.getInfoPath('tapis2sites'))
         self.tapis3SitesInfo = Tapis3SitesInfo(self.infosInfo.getInfoPath('tapis3sites'))
         self.fileMoversInfo = FileMoversInfo(self.infosInfo.getInfoPath('filemovers'))

         self.settingInfo = False

      return(errorInSetInfo)


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


   def executeCommand(self,
                      commandArgs,
                      stdin=None):
      exitStatus = 0
      outData = []
      errData = []

      if stdin:
         try:
            fpStdin = open(stdin,'rb')
         except:
            exitStatus = 1

      if exitStatus == 0:
         try:
            self.logger.log(logging.DEBUG,getLogMessage("Command: %s" % (commandArgs)))
            if stdin:
               child = subprocess.Popen(commandArgs,bufsize=self.bufferSize,
                                        stdin=fpStdin,
                                        stdout=subprocess.PIPE,
                                        stderr=subprocess.PIPE,
                                        close_fds=True)
            else:
               child = subprocess.Popen(commandArgs,bufsize=self.bufferSize,
                                        stdin=None,
                                        stdout=subprocess.PIPE,
                                        stderr=subprocess.PIPE,
                                        close_fds=True)
         except OSError as e:
            self.logger.log(logging.ERROR,getLogMessage("Command: %s\nfailed: %s." % (commandArgs,e.args[1])))
            exitStatus = e.args[0]
         else:
            childPid   = child.pid
            childout   = child.stdout
            childoutFd = childout.fileno()
            childerr   = child.stderr
            childerrFd = childerr.fileno()

            outEOF = False
            errEOF = False

            while True:
               toCheck = []
               if not outEOF:
                  toCheck.append(childoutFd)
               if not errEOF:
                  toCheck.append(childerrFd)
               ready = select.select(toCheck,[],[]) # wait for input

               if childoutFd in ready[0]:
                  outChunk = os.read(childoutFd,self.bufferSize).decode('utf-8')
                  if outChunk == '':
                     outEOF = True
                  outData.append(outChunk)

               if childerrFd in ready[0]:
                  errChunk = os.read(childerrFd,self.bufferSize).decode('utf-8')
                  if errChunk == '':
                     errEOF = True
                  errData.append(errChunk)

               if outEOF and errEOF:
                  break

            pid,exitStatus = os.waitpid(childPid,0)
            if exitStatus != 0:
               if   os.WIFSIGNALED(exitStatus):
                  self.logger.log(logging.INFO,getLogMessage("%s failed w/ signal %d" % (commandArgs,os.WTERMSIG(exitStatus))))
               else:
                  if os.WIFEXITED(exitStatus):
                     exitStatus = os.WEXITSTATUS(exitStatus)
                  self.logger.log(logging.INFO,getLogMessage("%s failed w/ exit code %d" % (commandArgs,exitStatus)))
               self.logger.log(logging.INFO,getLogMessage("%s" % ("".join(errData))))

         if stdin:
            fpStdin.close()

      return(exitStatus,"".join(outData),"".join(errData))


   def registerJobActionProcess(self,
                                action):
      actionProcessPID = os.getpid()
      now              = time.time()

      success,timeBetween = self.remoteJobMonitor.registerJobActionProcess(action,actionProcessPID,now)

      return(success,timeBetween)


   def updateJobActionProcessActivityTime(self,
                                          action):
      now = time.time()
      self.remoteJobMonitor.updateJobActionProcessActivityTime(action,now)


   def postToWS(self):
      nPostedJobs = 0
      requestParameters = {}
      if not self.wsTaskURL:
         self.wsTaskURL = self.remoteJobMonitor.getWSTaskURL(self.wsTask)
      wsJobIds = self.remoteJobMonitor.getActiveJobWSJobIds(self.previousState)
      for wsJobId in wsJobIds:
         wsURL = '/'.join([self.wsTaskURL,wsJobId])
         try:
            response = requests.post(wsURL)
         except:
            self.logger.log(logging.ERROR,getLogMessage(traceback.format_exc()))
         else:
            status = response.status_code
            if status == 200:
               self.remoteJobMonitor.updateActiveJobState(wsJobId,self.nextState,requestParameters)
               nPostedJobs += 1
         time.sleep(1)

      return(nPostedJobs)


