/** ==========================================================================
 * 2011 by KjellKod.cc. This is PUBLIC DOMAIN to use at your own risk and comes
 * with no warranties. This code is yours to share, use and modify with no
 * strings attached and no restrictions or obligations.
 *
 * For more information see g3log/LICENSE or refer refer to http://unlicense.org
 * ============================================================================
 *
 * Filename:g3log.cpp  Framework for Logging and Design By Contract
 * Created: 2011 by Kjell Hedström
 *
 * PUBLIC DOMAIN and Not copywrited since it was built on public-domain software and at least in "spirit" influenced
 * from the following sources
 * 1. kjellkod.cc ;)
 * 2. Dr.Dobbs, Petru Marginean:  http://drdobbs.com/article/printableArticle.jhtml?articleId=201804215&dept_url=/cpp/
 * 3. Dr.Dobbs, Michael Schulze: http://drdobbs.com/article/printableArticle.jhtml?articleId=225700666&dept_url=/cpp/
 * 4. Google 'glog': http://google-glog.googlecode.com/svn/trunk/doc/glog.html
 * 5. Various Q&A at StackOverflow
 * ********************************************* */

#include "g3log/g3log.hpp"
#include "g3log/std2_make_unique.hpp"
#include "g3log/logworker.hpp"
#include "g3log/crashhandler.hpp"
#include "g3log/logmessage.hpp"
#include "g3log/loglevels.hpp"


#include <mutex>
#include <memory>
#include <iostream>
#include <thread>
#include <atomic>
#include <cstdlib>
#include <sstream>

namespace {
   std::once_flag g_initialize_flag;
   g3::LogWorker *g_logger_instance = nullptr; // instantiated and OWNED somewhere else (main)
   std::mutex g_logging_init_mutex;

   std::unique_ptr<g3::LogMessage> g_first_unintialized_msg = {nullptr};
   std::once_flag g_set_first_uninitialized_flag;
   std::once_flag g_save_first_unintialized_flag;
   const std::function<void(void)> g_pre_fatal_hook_that_does_nothing = [] { /*does nothing */};
   std::function<void(void)> g_fatal_pre_logging_hook;


   std::atomic<size_t> g_fatal_hook_recursive_counter = {0};
}





namespace g3 {
   // signalhandler and internal clock is only needed to install once
   // for unit testing purposes the initializeLogging might be called
   // several times...
   //                    for all other practical use, it shouldn't!

   void initializeLogging(LogWorker *bgworker) {
      std::call_once(g_initialize_flag, [] {
         installCrashHandler();
      });
      std::lock_guard<std::mutex> lock(g_logging_init_mutex);
      if (internal::isLoggingInitialized() || nullptr == bgworker) {
         std::ostringstream exitMsg;
         exitMsg << __FILE__ "->" << __FUNCTION__ << ":" << __LINE__ << std::endl;
         exitMsg << "\tFatal exit due to illegal initialization of g3::LogWorker\n";
         exitMsg << "\t(due to multiple initializations? : " << std::boolalpha << internal::isLoggingInitialized();
         exitMsg << ", due to nullptr == bgworker? : " << std::boolalpha << (nullptr == bgworker) << ")";
         std::cerr << exitMsg.str() << std::endl;
         std::exit(EXIT_FAILURE);
      }

      // Save the first uninitialized message, if any
      std::call_once(g_save_first_unintialized_flag, [&bgworker] {
         if (g_first_unintialized_msg) {
            bgworker->save(LogMessagePtr {std::move(g_first_unintialized_msg)});
         }
      });

      g_logger_instance = bgworker;
      // by default the pre fatal logging hook does nothing
      // if it WOULD do something it would happen in
      setFatalPreLoggingHook(g_pre_fatal_hook_that_does_nothing);
      // recurvise crash counter re-set to zero
      g_fatal_hook_recursive_counter.store(0);
   }


   /**
   *  default does nothing, @ref ::g_pre_fatal_hook_that_does_nothing
   *  It will be called just before sending the fatal message, @ref pushFatalmessageToLogger
   *  It will be reset to do nothing in ::initializeLogging(...)
   *     so please call this function, if you ever need to, after initializeLogging(...)
   */
   void setFatalPreLoggingHook(std::function<void(void)>  pre_fatal_hook) {
      static std::mutex m;
      std::lock_guard<std::mutex> lock(m);
      g_fatal_pre_logging_hook = pre_fatal_hook;
   }




   // By default this function pointer goes to \ref pushFatalMessageToLogger;
   std::function<void(FatalMessagePtr) > g_fatal_to_g3logworker_function_ptr = internal::pushFatalMessageToLogger;

   /** REPLACE fatalCallToLogger for fatalCallForUnitTest
    * This function switches the function pointer so that only
    * 'unitTest' mock-fatal calls are made.
    * */
   void setFatalExitHandler(std::function<void(FatalMessagePtr) > fatal_call) {
      g_fatal_to_g3logworker_function_ptr = fatal_call;
   }


   namespace internal {

      bool isLoggingInitialized() {
         return g_logger_instance != nullptr;
      }

      /**
       * Shutdown the logging by making the pointer to the background logger to nullptr. The object is not deleted
       * that is the responsibility of its owner. *
       */
      void shutDownLogging() {
         std::lock_guard<std::mutex> lock(g_logging_init_mutex);
         g_logger_instance = nullptr;

      }

      /** Same as the Shutdown above but called by the destructor of the LogWorker, thus ensuring that no further
       *  LOG(...) calls can happen to  a non-existing LogWorker.
       *  @param active MUST BE the LogWorker initialized for logging. If it is not then this call is just ignored
       *         and the logging continues to be active.
       * @return true if the correct worker was given,. and shutDownLogging was called
       */
      bool shutDownLoggingForActiveOnly(LogWorker *active) {
         if (isLoggingInitialized() && nullptr != active && (active != g_logger_instance)) {
            LOG(WARNING) << "\n\t\tAttempted to shut down logging, but the ID of the Logger is not the one that is active."
                         << "\n\t\tHaving multiple instances of the g3::LogWorker is likely a BUG"
                         << "\n\t\tEither way, this call to shutDownLogging was ignored"
                         << "\n\t\tTry g3::internal::shutDownLogging() instead";
            return false;
         }
         shutDownLogging();
         return true;
      }




      /** explicits copy of all input. This is makes it possibly to use g3log across dynamically loaded libraries
      * i.e. (dlopen + dlsym)  */
      void saveMessage(const char *entry, const char *file, int line, const char *function, const LEVELS &level,
                       const char *boolean_expression, int fatal_signal, const char *stack_trace) {
         LEVELS msgLevel {level};
         LogMessagePtr message {std2::make_unique<LogMessage>(file, line, function, msgLevel)};
         message.get()->write().append(entry);
         message.get()->setExpression(boolean_expression);


         if (internal::wasFatal(level)) {
            auto fatalhook = g_fatal_pre_logging_hook;
            // In case the fatal_pre logging actually will cause a crash in its turn
            // let's not do recursive crashing!
            setFatalPreLoggingHook(g_pre_fatal_hook_that_does_nothing);
            ++g_fatal_hook_recursive_counter; // thread safe counter
            // "benign" race here. If two threads crashes, with recursive crashes
            // then it's possible that the "other" fatal stack trace will be shown
            // that's OK since it was anyhow the first crash detected
            static const std::string first_stack_trace = stack_trace;
            fatalhook();
            message.get()->write().append(stack_trace);

            if (g_fatal_hook_recursive_counter.load() > 1) {
               message.get()->write()
               .append("\n\n\nWARNING\n"
                       "A recursive crash detected. It is likely the hook set with 'setFatalPreLoggingHook(...)' is responsible\n\n")
               .append("---First crash stacktrace: ").append(first_stack_trace).append("\n---End of first stacktrace\n");
            }
            FatalMessagePtr fatal_message { std2::make_unique<FatalMessage>(*(message._move_only.get()), fatal_signal) };
            // At destruction, flushes fatal message to g3LogWorker
            // either we will stay here until the background worker has received the fatal
            // message, flushed the crash message to the sinks and exits with the same fatal signal
            //..... OR it's in unit-test mode then we throw a std::runtime_error (and never hit sleep)
            fatalCall(fatal_message);
         } else {
            pushMessageToLogger(message);
         }
      }

      /**
       * save the message to the logger. In case of called before the logger is instantiated
       * the first message will be saved. Any following subsequent unitnialized log calls
       * will be ignored.
       *
       * The first initialized log entry will also save the first uninitialized log message, if any
       * @param log_entry to save to logger
       */
      void pushMessageToLogger(LogMessagePtr incoming) { // todo rename to Push SavedMessage To Worker
         // Uninitialized messages are ignored but does not CHECK/crash the logger
         if (!internal::isLoggingInitialized()) {
            std::call_once(g_set_first_uninitialized_flag, [&] {
               g_first_unintialized_msg = incoming.release();
               std::string err = {"LOGGER NOT INITIALIZED:\n\t\t"};
               err.append(g_first_unintialized_msg->message());
               std::string &str = g_first_unintialized_msg->write();
               str.clear();
               str.append(err); // replace content
               std::cerr << str << std::endl;
            });
            return;
         }

         // logger is initialized
         g_logger_instance->save(incoming);
      }

      /** Fatal call saved to logger. This will trigger SIGABRT or other fatal signal
       * to exit the program. After saving the fatal message the calling thread
       * will sleep forever (i.e. until the background thread catches up, saves the fatal
       * message and kills the software with the fatal signal.
       */
      void pushFatalMessageToLogger(FatalMessagePtr message) {
         if (!isLoggingInitialized()) {
            std::ostringstream error;
            error << "FATAL CALL but logger is NOT initialized\n"
                  << "CAUSE: " << message.get()->reason()
                  << "\nMessage: \n" << message.get()->toString() << std::flush;
            std::cerr << error.str() << std::flush;
            internal::exitWithDefaultSignalHandler(message.get()->_level, message.get()->_signal_id);
         }
         g_logger_instance->fatal(message);
         while (shouldBlockForFatalHandling()) {
            std::this_thread::sleep_for(std::chrono::seconds(1));
         }
      }

      /** The default, initial, handling to send a 'fatal' event to g3logworker
       *  the caller will stay here, eternally, until the software is aborted
       * ... in the case of unit testing it is the given "Mock" fatalCall that will
       * define the behaviour.
       */
      void fatalCall(FatalMessagePtr message) {
         g_fatal_to_g3logworker_function_ptr(FatalMessagePtr {std::move(message)});
      }


   } // internal
} // g3



