/*
 * Copyright (C) 2025  Tim Zimmermann <tim@aglais.org>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 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 Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package org.aglais.zms.util;

import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.ServiceLoader;

/**
 * API for writing logging output with support for different log levels and message types.
 * This class provides a simple interface for logging various types of messages using
 * Log4j as the underlying logging framework. It supports six log levels: VERBOSE, DEBUG,
 * INFO, WARN, ERROR, and ASSERT (fatal).
 * <p>
 * Each logging method accepts a tag to identify the source, a message, and optionally
 * an exception.
 */
public abstract class Log {

    /**
     * Priority constant for the println method; use org.aglais.zms.util.Log.v.
     */
    public static final int VERBOSE = 2;

    /**
     * Priority constant for the println method; use org.aglais.zms.util.Log.d.
     */
    public static final int DEBUG = 3;

    /**
     * Priority constant for the println method; use org.aglais.zms.util.Log.i.
     */
    public static final int INFO = 4;

    /**
     * Priority constant for the println method; use org.aglais.zms.util.Log.w.
     */
    public static final int WARN = 5;

    /**
     * Priority constant for the println method; use org.aglais.zms.util.Log.e.
     */
    public static final int ERROR = 6;

    /**
     * Priority constant for the println method.
     */
    public static final int ASSERT = 7;

    private static final Log LOGGER;

    static {
        ServiceLoader<Log> loggers = ServiceLoader.load(Log.class);
        LOGGER = loggers.findFirst().orElse(null);
    }

    /**
     * Logging implementation
     *
     * @param level The log level
     * @param tag   The log tag
     * @param msg   The message to log
     * @param tr    The {@link Throwable} to include in the log, may be null
     */
    protected abstract void logImpl(int level, String tag, String msg, Throwable tr);

    /**
     * Send a {@link #VERBOSE} log message.
     *
     * @param tag Used to identify the source of a log message.  It usually identifies
     *            the class or activity where the log call occurs.
     * @param msg The message you would like logged.
     */
    public static void v(@Nullable String tag, @NotNull String msg) {
        log(VERBOSE, tag, msg);
    }

    /**
     * Send a {@link #VERBOSE} log message and log the exception.
     *
     * @param tag Used to identify the source of a log message.  It usually identifies
     *            the class or activity where the log call occurs.
     * @param msg The message you would like logged.
     * @param tr  An exception to log.
     */
    public static void v(@Nullable String tag, @Nullable String msg, @Nullable Throwable tr) {
        log(VERBOSE, tag, msg, tr);
    }

    /**
     * Send a {@link #DEBUG} log message.
     *
     * @param tag Used to identify the source of a log message.  It usually identifies
     *            the class or activity where the log call occurs.
     * @param msg The message you would like logged.
     */
    public static void d(@Nullable String tag, @NotNull String msg) {
        log(DEBUG, tag, msg);
    }

    /**
     * Send a {@link #DEBUG} log message and log the exception.
     *
     * @param tag Used to identify the source of a log message.  It usually identifies
     *            the class or activity where the log call occurs.
     * @param msg The message you would like logged.
     * @param tr  An exception to log.
     */
    public static void d(@Nullable String tag, @Nullable String msg, @Nullable Throwable tr) {
        log(DEBUG, tag, msg, tr);
    }

    /**
     * Send an {@link #INFO} log message.
     *
     * @param tag Used to identify the source of a log message.  It usually identifies
     *            the class or activity where the log call occurs.
     * @param msg The message you would like logged.
     */
    public static void i(@Nullable String tag, @NotNull String msg) {
        log(INFO, tag, msg);
    }

    /**
     * Send a {@link #INFO} log message and log the exception.
     *
     * @param tag Used to identify the source of a log message.  It usually identifies
     *            the class or activity where the log call occurs.
     * @param msg The message you would like logged.
     * @param tr  An exception to log.
     */
    public static void i(@Nullable String tag, @Nullable String msg, @Nullable Throwable tr) {
        log(INFO, tag, msg, tr);
    }

    /**
     * Send a {@link #WARN} log message.
     *
     * @param tag Used to identify the source of a log message.  It usually identifies
     *            the class or activity where the log call occurs.
     * @param msg The message you would like logged.
     */
    public static void w(@Nullable String tag, @NotNull String msg) {
        log(WARN, tag, msg);
    }

    /**
     * Send a {@link #WARN} log message and log the exception.
     *
     * @param tag Used to identify the source of a log message.  It usually identifies
     *            the class or activity where the log call occurs.
     * @param msg The message you would like logged.
     * @param tr  An exception to log.
     */
    public static void w(@Nullable String tag, @Nullable String msg, @Nullable Throwable tr) {
        log(WARN, tag, msg, tr);
    }

    /**
     * Send a {@link #WARN} log message and log the exception.
     *
     * @param tag Used to identify the source of a log message.  It usually identifies
     *            the class or activity where the log call occurs.
     * @param tr  An exception to log.
     */
    public static void w(@Nullable String tag, @Nullable Throwable tr) {
        log(WARN, tag, "", tr);
    }

    /**
     * Send an {@link #ERROR} log message.
     *
     * @param tag Used to identify the source of a log message.  It usually identifies
     *            the class or activity where the log call occurs.
     * @param msg The message you would like logged.
     */
    public static void e(@Nullable String tag, @NotNull String msg) {
        log(ERROR, tag, msg);
    }

    /**
     * Send a {@link #ERROR} log message and log the exception.
     *
     * @param tag Used to identify the source of a log message.  It usually identifies
     *            the class or activity where the log call occurs.
     * @param msg The message you would like logged.
     * @param tr  An exception to log.
     */
    public static void e(@Nullable String tag, @Nullable String msg, @Nullable Throwable tr) {
        log(ERROR, tag, msg, tr);
    }

    /**
     * What a Terrible Failure: Report a condition that should never happen.
     * The error will always be logged at level ASSERT with the call stack.
     *
     * @param tag Used to identify the source of a log message.
     * @param msg The message you would like logged.
     */
    public static void wtf(@Nullable String tag, @Nullable String msg) {
        log(ASSERT, tag, msg);
    }

    /**
     * What a Terrible Failure: Report an exception that should never happen.
     * Similar to {@link #wtf(String, String)}, with an exception to log.
     *
     * @param tag Used to identify the source of a log message.
     * @param tr  An exception to log.
     */
    public static void wtf(@Nullable String tag, @NotNull Throwable tr) {
        log(ASSERT, tag, tr.getMessage(), tr);
    }

    /**
     * What a Terrible Failure: Report an exception that should never happen.
     * Similar to {@link #wtf(String, Throwable)}, with a message as well.
     *
     * @param tag Used to identify the source of a log message.
     * @param msg The message you would like logged.
     * @param tr  An exception to log.  May be null.
     */
    public static void wtf(@Nullable String tag, @Nullable String msg, @Nullable Throwable tr) {
        log(ASSERT, tag, msg, tr);
    }

    private static void log(int level, String tag, String msg, Throwable tr) {
        if (LOGGER == null) {
            System.err.println("No Log implementation loaded, cannot log!");
            return;
        }

        LOGGER.logImpl(level, tag, msg, tr);
    }

    private static void log(int level, String tag, String msg) {
        log(level, tag, msg, null);
    }
}
