/*
 * ZoneMinder Logger Interface, $Date$, $Revision$
 * Copyright (C) 2001-2008 Philip Coombes
 * 
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/  

#ifndef ZM_LOGGER_H
#define ZM_LOGGER_H

#include "zm_db.h"
#include "zm_config.h"
#include "zm_define.h"
#include <map>
#include <mutex>
#include <string>

#ifdef HAVE_SYS_SYSCALL_H
#include <sys/syscall.h>
#endif // HAVE_SYS_SYSCALL_H

class Logger {
public:
  enum { 
    NOOPT=-6,
    NOLOG, // -5
    PANIC, // -4
    FATAL, // -3
    ERROR, // -2
    WARNING, // -1
    INFO, // 0
    DEBUG1,
    DEBUG2,
    DEBUG3,
    DEBUG4,
    DEBUG5,
    DEBUG6,
    DEBUG7,
    DEBUG8,
    DEBUG9
  };

  typedef int Level;

  typedef std::map<Level,std::string> StringMap;
  typedef std::map<Level,int> IntMap;

  class Options {
  public:
    int mTerminalLevel;
    int mDatabaseLevel;
    int mFileLevel;
    int mSyslogLevel;

    std::string mLogPath;
    std::string mLogFile;

    Options(
        Level terminalLevel=NOOPT,
        Level databaseLevel=NOOPT,
        Level fileLevel=NOOPT,
        Level syslogLevel=NOOPT,
        const std::string &logPath=".",
        const std::string &logFile=""
        ) :
      mTerminalLevel(terminalLevel),
      mDatabaseLevel(databaseLevel),
      mFileLevel(fileLevel),
      mSyslogLevel(syslogLevel),
      mLogPath(logPath),
      mLogFile(logFile)
    {
    }
  };

private:
  static bool smInitialised;
  static Logger *smInstance;

  std::recursive_mutex log_mutex;

  static StringMap smCodes;
  static IntMap smSyslogPriorities;

  bool mInitialised;

  std::string mId;
  std::string mIdRoot;
  std::string mIdArgs;

  Level mLevel;             // Level that is currently in operation
  Level mTerminalLevel;     // Maximum level output via terminal
  Level mDatabaseLevel;     // Maximum level output via database
  Level mFileLevel;         // Maximum level output via file
  Level mSyslogLevel;       // Maximum level output via syslog
  Level mEffectiveLevel;    // Level optimised to take account of maxima

  bool mDbConnected;

  std::string mLogPath;
  std::string mLogFile;
  FILE *mLogFileFP;

  bool mHasTerminal;
  bool mFlush;

private:
  Logger();
  ~Logger();

  int limit(const int level) const {
    if ( level > DEBUG9 )
      return DEBUG9;
    if ( level < NOLOG )
      return NOLOG;
    return level;
  }

  bool boolEnv(const std::string &name, bool defaultValue=false);
  int intEnv(const std::string &name, bool defaultValue=0);
  std::string strEnv(const std::string &name, const std::string &defaultValue="");
  char *getTargettedEnv(const std::string &name);

  void loadEnv();
  static void usrHandler(int sig);

public:
  friend void logInit(const char *name, const Options &options);
  friend void logTerm();

  static Logger *fetch() {
    if ( !smInstance ) {
      smInstance = new Logger();
      Options options;
      smInstance->initialise("undef", options);
    }
    return smInstance;
  }

  void initialise(const std::string &id, const Options &options);
  void terminate();

  const std::string &id(const std::string &id);
  const std::string &id() const { return mId; }

  Level level() const { return mLevel; }
  Level level(Level=NOOPT);

  bool debugOn() const { return mEffectiveLevel >= DEBUG1; }

  Level terminalLevel(Level=NOOPT);
  Level databaseLevel(Level=NOOPT);
  Level fileLevel(Level=NOOPT);
  Level syslogLevel(Level=NOOPT);

private:
  void logFile(const std::string &logFile);
  void openFile();
  void closeFile();
  void openSyslog();
  void closeSyslog();
  void closeDatabase();

 public:
  void logPrint(bool hex,
                const char *filepath,
                int line,
                int level,
                const char *fstring,
                ...) __attribute__((format(printf, 6, 7)));
};

void logInit(const char *name, const Logger::Options &options=Logger::Options());
void logTerm();
inline const std::string &logId() {
  return Logger::fetch()->id();
}
inline Logger::Level logLevel() {
  return Logger::fetch()->level();
}
inline Logger::Level logDebugging() {
  return Logger::fetch()->debugOn();
}

#define logPrintf(logLevel, params...)                                          \
  do {                                                                          \
    if (logLevel <= Logger::fetch()->level()) {                                 \
      Logger::fetch()->logPrint(false, __FILE__, __LINE__, logLevel, ##params); \
    }                                                                           \
  } while (0)

#define logHexdump(logLevel, data, len)                                                    \
  do {                                                                                     \
    if (logLevel <= Logger::fetch()->level()) {                                            \
      Logger::fetch()->logPrint(true, __FILE__, __LINE__, logLevel, "%p (%d)", data, len); \
    }                                                                                      \
  } while (0)

/* Debug compiled out */
#ifndef DBG_OFF
#define Debug(level,params...)  logPrintf(level,##params)
#define Hexdump(level,data,len) logHexdump(level,data,len)
#else
#define Debug(level,params...)
#define Hexdump(level,data,len)
#endif

/* Standard debug calls */
#define Info(params...)   logPrintf(Logger::INFO,##params)
#define Warning(params...)  logPrintf(Logger::WARNING,##params)
#define Error(params...)  logPrintf(Logger::ERROR,##params)
#define Fatal(params...)  logPrintf(Logger::FATAL,##params)
#define Panic(params...)  logPrintf(Logger::PANIC,##params)
#define Mark()        Info("Mark/%s/%d",__FILE__,__LINE__)
#define Log()         Info("Log")
#ifdef __GNUC__
#define Enter(level)    logPrintf(level,("Entering %s",__PRETTY_FUNCTION__))
#define Exit(level)     logPrintf(level,("Exiting %s",__PRETTY_FUNCTION__))
#else
#define Enter(level)    
#define Exit(level)     
#endif

#endif // ZM_LOGGER_H
