# @package      hubzero-submit-common
# @file         ApplicationUtilities.py
# @copyright    Copyright (c) 2025-2025 The Regents of the University of California.
# @license      http://opensource.org/licenses/MIT MIT
#
# Copyright (c) 2025-2025 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 sys
import os
import time
import logging
from logging.handlers import SysLogHandler

from hubzero.submit.LogMessage import getLogPIDMessage as getLogMessage

class ApplicationUtilities:
   def __init__(self,
                defaultLogLevel,
                defaultSysLogLocal=None,
                stdoutLogLevel="INFO",
                stderrLogLevel="ERROR"):
      self.logger = logging.getLogger('')

      self.logLevelMap = {   "DEBUG":logging.DEBUG,
                              "INFO":logging.INFO,
                           "WARNING":logging.WARNING,
                             "ERROR":logging.ERROR,
                          "CRITICAL":logging.CRITICAL}
      self.defaultLogLevel = defaultLogLevel
      self.stdoutLogLevel  = stdoutLogLevel
      self.stderrLogLevel  = stderrLogLevel
      try:
         self.fdLogFile    = sys.stdout.fileno()
      except:
         self.fdLogFile    = None

      self.stdout = None
      self.stderr = None

      self.sysLogLocalMap = {"LOCAL0":SysLogHandler.LOG_LOCAL0,
                             "LOCAL1":SysLogHandler.LOG_LOCAL1,
                             "LOCAL2":SysLogHandler.LOG_LOCAL2,
                             "LOCAL3":SysLogHandler.LOG_LOCAL3,
                             "LOCAL4":SysLogHandler.LOG_LOCAL4,
                             "LOCAL5":SysLogHandler.LOG_LOCAL5,
                             "LOCAL6":SysLogHandler.LOG_LOCAL6,
                             "LOCAL7":SysLogHandler.LOG_LOCAL7}
      self.defaultSysLogLocal = defaultSysLogLocal


   class __EmptyFilter(logging.Filter):
      """
      This is a filter which rejects empty messages

      """

      def filter(self,
                 record):
         if record.getMessage() == "":
            emptyRecord = True
         else:
            emptyRecord = False

         return(not emptyRecord)


   def openFileLogger(self,
                      logDirectory,
                      hubLogFile,
                      logLevel,
                      hubLogCriticalFile=None):
      try:
         logLevel = self.logLevelMap[logLevel]
      except:
         logLevel = self.logLevelMap[self.defaultLogLevel]
      finally:
         self.logger.setLevel(logLevel)

      hubLogPath = os.path.join(logDirectory,hubLogFile)
      logHandler = logging.FileHandler(hubLogPath)
      self.fdLogFile = logHandler.stream.fileno()

      emptyFilter = self.__EmptyFilter()
      logHandler.addFilter(emptyFilter)

      logFormatter = logging.Formatter('%(asctime)s %(message)s','%s [%a %b %d %H:%M:%S %Y]')
      logHandler.setFormatter(logFormatter)
      self.logger.addHandler(logHandler)

      if hubLogCriticalFile:
# critical messages only
         hubLogPath = os.path.join(logDirectory,hubLogCriticalFile)
         logCriticalHandler = logging.FileHandler(hubLogPath)
         logCriticalHandler.setLevel(logging.CRITICAL)

         logCriticalHandler.addFilter(emptyFilter)

         logCriticalHandler.setFormatter(logFormatter)
         self.logger.addHandler(logCriticalHandler)


   def openStreamLogger(self,
                        stream,
                        logLevel):
      try:
         logLevel = self.logLevelMap[logLevel]
      except:
         logLevel = self.logLevelMap[self.defaultLogLevel]
      finally:
         self.logger.setLevel(logLevel)

      logHandler = logging.StreamHandler(stream)

      emptyFilter = self.__EmptyFilter()
      logHandler.addFilter(emptyFilter)

      logFormatter = logging.Formatter('%(asctime)s %(message)s','[%a %b %d %H:%M:%S %Y]')
      logHandler.setFormatter(logFormatter)
      self.logger.addHandler(logHandler)


   def openSysLogLogger(self,
                        sysLogLocal,
                        logLevel,
                        logIdentifier=None,
                        redirectStdFiles=False):
      class StdToSyslogRedirection:
         def __init__(self,
                      logger,
                      level):
            self.logger = logger
            self.level = level

         def write(self,
                   message):
            # Filter out empty lines or trailing newlines
            if message.rstrip():
               self.logger.log(self.level, message.rstrip())

         def flush(self):
            pass  # Required for file-like objects

      try:
         logLevel = self.logLevelMap[logLevel]
      except:
         logLevel = self.logLevelMap[self.defaultLogLevel]
      finally:
         self.logger.setLevel(logLevel)

      try:
         sysLogLocal = self.sysLogLocalMap[sysLogLocal]
      except:
         sysLogLocal = self.sysLogLocalMap[self.defaultSysLogLocal]
      finally:
         logSyslogHandler = SysLogHandler(address="/dev/log",facility=sysLogLocal)
         if logIdentifier:
            logSyslogHandler.ident = logIdentifier + " "

      emptyFilter = self.__EmptyFilter()
      logSyslogHandler.addFilter(emptyFilter)
      logFormatter = logging.Formatter('%(message)s')
      logSyslogHandler.setFormatter(logFormatter)
      self.logger.addHandler(logSyslogHandler)

      if redirectStdFiles:
         try:
            stdLogLocal = self.sysLogLocalMap[self.stdoutLogLevel]
         except:
            stdLogLevel = self.logLevelMap["INFO"]
         finally:
            self.stdout = StdToSyslogRedirection(self.logger,stdLogLevel)

         try:
            stdLogLocal = self.sysLogLocalMap[self.stderrLogLevel]
         except:
            stdLogLevel = self.logLevelMap["ERROR"]
         finally:
            self.stderr = StdToSyslogRedirection(self.logger,stdLogLevel)


   def logErrorMessage(self,
                       message):
      self.logger.log(logging.ERROR,getLogMessage(message))


   def daemonize(self):
      try:
         fdStdout = sys.stdout.fileno()
      except:
         fdStdout = None
      finally:
         if self.fdLogFile != fdStdout:
            try:
               devnull = open("/dev/null",'r')
               try:
                  os.dup2(devnull.fileno(),sys.stdin.fileno())
                  os.dup2(self.fdLogFile,sys.stdout.fileno())
                  os.dup2(self.fdLogFile,sys.stderr.fileno())
               except OSError:
                  self.logger.log(logging.ERROR,getLogMessage("file descriptor dup failed"))
            except (IOError,OSError):
               self.logger.log(logging.ERROR,getLogMessage("/dev/null could not be opened"))

         if self.stdout and self.stderr:
            try:
               devnull = open("/dev/null",'r')
               try:
                  os.dup2(devnull.fileno(),sys.stdin.fileno())
               except OSError:
                  self.logger.log(logging.ERROR,getLogMessage("file descriptor dup failed"))
            except (IOError,OSError):
               self.logger.log(logging.ERROR,getLogMessage("/dev/null could not be opened"))
            sys.stdout.close()
            sys.stdout = self.stdout
            sys.stderr.close()
            sys.stderr = self.stderr

      if os.fork() != 0:
         os.wait()
         os._exit(os.EX_OK)
      else:
         os.setsid()
         os.chdir("/")
         pid = os.fork()
         if pid != 0:
            os._exit(os.EX_OK)

      time.sleep(2)


   def writePidFile(self,
                    pidFile):
      pidSaved = False
      try:
         fpPid = open(pidFile,'w')
         try:
            fpPid.write("%d\n" % (os.getpid()))
         except (IOError,OSError):
            self.logger.log(logging.ERROR,getLogMessage("Unable to write pid (%d) to %s" % (os.getpid(),pidFile)))
         else:
            pidSaved = True
         finally:
            fpPid.close()
      except:
         self.logger.log(logging.ERROR,getLogMessage("Unable to open pid (%d) to %s" % (os.getpid(),pidFile)))

      if not pidSaved:
         os._exit(os.EX_CANTCREAT)


