/* Copyright 2016 Software Freedom Conservancy Inc.
 *
 * This software is licensed under the GNU LGPL (version 2.1 or later).
 * See the COPYING file in this distribution.
 */

namespace Debug {
    private const LogLevelFlags DEFAULT_LOG_MASK =
        LogLevelFlags.LEVEL_CRITICAL |
        LogLevelFlags.LEVEL_WARNING |
        LogLevelFlags.LEVEL_MESSAGE;
    
    public const string VIEWER_PREFIX = "V";
    public const string LIBRARY_PREFIX = "L";
    
    // Ideally, there would be a LogLevelFlags.NONE constant to use as
    // empty value but failing that, 0 works as well
    private LogLevelFlags log_mask = 0;
    private string log_app_version_prefix;
    // log_file_stream is the canonical reference to the file stream (and owns
    // it), while log_out and log_err are indirections that can point to
    // log_file_stream or stdout and stderr respectively
    private unowned FileStream log_out = null;
    private unowned FileStream log_err = null;
    private FileStream log_file_stream = null;
    
    public static void init(string app_version_prefix) {
        log_app_version_prefix = app_version_prefix;
        
        // default to stdout/stderr if file cannot be opened or console is specified
        log_out = stdout;
        log_err = stderr;
        
        string log_file_error_msg = null;
        
        // logging to disk is currently off for viewer more; see http://trac.yorba.org/ticket/2078
        File? log_file = (log_app_version_prefix == LIBRARY_PREFIX) ? AppDirs.get_log_file() : null;
        if(log_file != null) {
            File log_dir = log_file.get_parent();
            try {
                if (log_dir.query_exists(null) == false) {
                    if (!log_dir.make_directory_with_parents(null)) {
                        log_file_error_msg = "Unable to create data directory %s".printf(log_dir.get_path());
                    }
                } 
            } catch (Error err) {
                log_file_error_msg = err.message;
            }
            // overwrite the log file every time the application is started
            // to ensure it doesn't grow too large; if there is a need for
            // keeping the log, the 'w' should be replaced by 'a' and some sort
            // of log rotation implemented
            log_file_stream = FileStream.open(log_file.get_path(), "w");
            if(log_file_stream != null) {
                log_out = log_file_stream;
                log_err = log_file_stream;
            } else {
                log_file_error_msg = "Unable to open or create log file %s".printf(log_file.get_path());
            }
        }
        
        if (Environment.get_variable("SHOTWELL_LOG") != null) {
            log_mask = LogLevelFlags.LEVEL_MASK;
        } else {
            log_mask = ((Environment.get_variable("SHOTWELL_INFO") != null) ?
                log_mask | LogLevelFlags.LEVEL_INFO :
                log_mask);
            log_mask = ((Environment.get_variable("SHOTWELL_DEBUG") != null) ?
                log_mask | LogLevelFlags.LEVEL_DEBUG :
                log_mask);
            log_mask = ((Environment.get_variable("SHOTWELL_MESSAGE") != null) ?
                log_mask | LogLevelFlags.LEVEL_MESSAGE :
                log_mask);
            log_mask = ((Environment.get_variable("SHOTWELL_WARNING") != null) ?
                log_mask | LogLevelFlags.LEVEL_WARNING :
                log_mask);
            log_mask = ((Environment.get_variable("SHOTWELL_CRITICAL") != null) ?
                log_mask | LogLevelFlags.LEVEL_CRITICAL :
                log_mask);
        }

        Log.set_handler(null, LogLevelFlags.LEVEL_INFO, info_handler);
        Log.set_handler(null, LogLevelFlags.LEVEL_DEBUG, debug_handler);
        Log.set_handler(null, LogLevelFlags.LEVEL_MESSAGE, message_handler);
        Log.set_handler(null, LogLevelFlags.LEVEL_WARNING, warning_handler);
        Log.set_handler(null, LogLevelFlags.LEVEL_CRITICAL, critical_handler);
        
        if(log_mask == 0 && log_file != null) {
            // if the log mask is still 0 and we have a log file, set the
            // mask to the default
            log_mask = DEFAULT_LOG_MASK;
        }
        
        if(log_file_error_msg != null) {
            warning("%s", log_file_error_msg);
        }
    }
    
    public static void terminate() {
    }
    
    private bool is_enabled(LogLevelFlags flag) {
        return ((log_mask & flag) > 0);
    }
    
    private void log(FileStream stream, string prefix, string message) {
        time_t now = time_t();
        stream.printf("%s %d %s [%s] %s\n",
            log_app_version_prefix,
            Posix.getpid(),
            Time.local(now).to_string(),
            prefix,
            message
        );
        stream.flush();
    }
    
    private void info_handler(string? domain, LogLevelFlags flags, string message) {
        if (is_enabled(LogLevelFlags.LEVEL_INFO))
            log(log_out, "INF", message);
    }
    
    private void debug_handler(string? domain, LogLevelFlags flags, string message) {
        if (is_enabled(LogLevelFlags.LEVEL_DEBUG))
            log(log_out, "DBG", message);
    }
    
    private void message_handler(string? domain, LogLevelFlags flags, string message) {
        if (is_enabled(LogLevelFlags.LEVEL_MESSAGE))
            log(log_err, "MSG", message);
    }

    private void warning_handler(string? domain, LogLevelFlags flags, string message) {
        if (is_enabled(LogLevelFlags.LEVEL_WARNING))
            log(log_err, "WRN", message);
    }

    private void critical_handler(string? domain, LogLevelFlags flags, string message) {
        if (is_enabled(LogLevelFlags.LEVEL_CRITICAL)) {
            log(log_err, "CRT", message);
            if (log_file_stream != null)
                log(stderr, "CRT", message);    // also log to console
        }
    }
}