package com.codingchili.core.logging;

import com.codingchili.core.configuration.Environment;
import com.codingchili.core.context.CoreContext;
import com.codingchili.core.context.Delay;
import com.codingchili.core.listener.CoreListener;
import com.codingchili.core.listener.CoreService;
import io.vertx.core.Future;
import io.vertx.core.json.JsonObject;

import java.time.Instant;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Supplier;
import java.util.logging.Handler;
import java.util.logging.LogRecord;

import static com.codingchili.core.configuration.CoreStrings.*;
import static com.codingchili.core.files.Configurations.launcher;

/**
 * @author Robin Duda
 * <p>
 * Default logging implementation.
 */
public abstract class DefaultLogger extends Handler implements Logger {
    protected Map<String, Supplier<String>> metadata = new HashMap<>();
    protected CoreContext context;
    protected JsonLogger logger;
    protected Class aClass;
    private Level level = Level.INFO;

    public DefaultLogger(CoreContext context, Class aClass) {
        this.context = context;
        this.aClass = aClass;
    }

    public DefaultLogger(Class aClass) {
        this.aClass = aClass;
    }

    @Override
    public Logger log(JsonObject json) {
        logger.log(json
                .put(PROTOCOL_ROUTE, PROTOCOL_LOGGING)
                .put(LOG_TIME, Instant.now().toEpochMilli()));
        return this;
    }

    @Override
    public Logger setMetadata(String key, Supplier<String> value) {
        metadata.put(key, value);
        return this;
    }

    private JsonObject event(String name) {
        return event(name, Level.INFO);
    }

    @Override
    public JsonObject event(String name, Level level) {
        JsonObject event = new JsonObject()
                .put(PROTOCOL_ROUTE, PROTOCOL_LOGGING)
                .put(LOG_EVENT, name)
                .put(LOG_LEVEL, level)
                .put(LOG_TIME, Instant.now().toEpochMilli());
        addMetadata(event);
        return event;
    }

    /**
     * Adds metadata to logging events generated by the logger.
     *
     * @param event the log event to add metadata to.
     */
    protected void addMetadata(JsonObject event) {
        if (context != null) {
            event.put(LOG_HOST, Environment.hostname().orElse(ID_UNDEFINED))
                    .put(LOG_SOURCE, aClass.getName())
                    .put(LOG_APPLICATION, launcher().getApplication());
        }
        metadata.forEach((key, value) -> event.put(key, value.get()));
    }

    @Override
    public void onAlreadyInitialized() {
        log(event(LOG_ERROR, Level.WARNING)
                .put(PROTOCOL_MESSAGE, ERROR_ALREADY_INITIALIZED));
    }

    @Override
    public void onServiceStarted(CoreService service) {
        log(event(LOG_SERVICE_START, Level.STARTUP)
                .put(ID_NAME, service.name()));
    }

    @Override
    public void onServiceStopped(Future<Void> future, CoreService service) {
        log(event(LOG_SERVICE_STOP, Level.SEVERE)
                .put(ID_NAME, service.name()));
        Delay.forShutdown(future);
    }

    @Override
    public void onListenerStarted(CoreListener listener) {
        log(event(LOG_LISTENER_START, Level.STARTUP)
                .put(ID_TYPE, listener.getClass().getSimpleName())
                .put(ID_HANDLER, listener.toString())
        );
    }

    @Override
    public void onListenerStopped(CoreListener listener) {
        log(event(LOG_LISTENER_STOP, Level.SEVERE)
                .put(ID_TYPE, listener.getClass().getSimpleName())
                .put(ID_HANDLER, listener.toString()));
    }

    @Override
    public void onServiceFailed(Throwable cause) {
        log(event(LOG_SERVICE_FAIL, Level.SEVERE)
                .put(ID_MESSAGE, cause.getMessage()));
    }

    @Override
    public void onMetricsSnapshot(JsonObject metrics) {
        log(event(LOG_METRICS)
                .put(ID_DATA, metrics));
    }

    @Override
    public void onHandlerMissing(String route) {
        log(event(LOG_HANDLER_MISSING, Level.WARNING)
                .put(LOG_MESSAGE, quote(route)));
    }

    @Override
    public void onFileLoaded(String path) {
        log(event(LOG_FILE_LOADED, Level.INFO)
                .put(LOG_MESSAGE, path));
    }

    @Override
    public void onError(Throwable cause) {
        log(event(LOG_ERROR, Level.SEVERE)
                .put(LOG_MESSAGE, cause.getMessage()));
    }

    @Override
    public Logger level(Level level) {
        this.level = level;
        return this;
    }

    @Override
    public void publish(LogRecord record) {
        log(addTrace(event(LOG_VERTX, Level.valueOf(record.getLevel().getName())), record)
                .put(LOG_MESSAGE, record.getMessage()));
    }

    @Override
    public void onFileLoadError(String fileName) {
        log(event(LOG_FILE_ERROR, Level.SEVERE)
                .put(LOG_MESSAGE, fileName));
    }

    @Override
    public void onFileSaved(String saver, String path) {
        log(event(LOG_FILE_SAVED, Level.INFO)
                .put(LOG_AGENT, saver)
                .put(LOG_MESSAGE, path));
    }

    @Override
    public void onFileSaveError(String fileName) {
        log(event(LOG_FILE_SAVED, Level.SEVERE)
                .put(LOG_MESSAGE, fileName));
    }

    @Override
    public void onConfigurationDefaultsLoaded(String path, Class<?> clazz) {
        log(event(LOG_CONFIG_DEFAULTED, Level.WARNING)
                .put(LOG_MESSAGE, getFileLoadDefaults(path, clazz)));
    }

    @Override
    public void onInvalidConfigurable(Class<?> clazz) {
        log(event(LOG_CONFIGURATION_INVALID, Level.SEVERE)
                .put(LOG_MESSAGE, getErrorInvalidConfigurable(clazz)));
    }

    @Override
    public void onCacheCleared(String component) {
        log(event(LOG_CACHE_CLEARED, Level.WARNING)
                .put(LOG_AGENT, component));
    }

    @Override
    public void onSecurityDependencyMissing(String target, String identifier) {
        log(event(LOG_SECURITY, Level.SEVERE)
                .put(LOG_MESSAGE, getSecurityDependencyMissing(target, identifier)));
    }

    @Override
    public Logger log(String line) {
        log(event(LOG_MESSAGE, level)
                .put(PROTOCOL_MESSAGE, line));
        return this;
    }

    @Override
    public Logger log(String line, Level level) {
        log(event(LOG_MESSAGE, level)
                .put(PROTOCOL_MESSAGE, line));
        return this;
    }

    @Override
    public void onTimerSourceChanged(String name, int initialTimeout, int newTimeout) {
        log(event(LOG_TIMER_CHANGE, Level.INFO)
                .put(ID_NAME, name)
                .put(LOG_PREVIOUS, initialTimeout)
                .put(LOG_NEW, newTimeout));
    }

    @SuppressWarnings("ThrowableResultOfMethodCallIgnored")
    private JsonObject addTrace(JsonObject log, LogRecord record) {

        if (record.getThrown() != null) {
            StackTraceElement element = record.getThrown().getStackTrace()[0];

            if (element != null)
                log.put(LOG_TRACE,
                        element.getClassName() + "." +
                                element.getMethodName() + " (" +
                                element.getLineNumber() + ")"
                );

            record.getThrown().printStackTrace();
        }

        return log;
    }

    @Override
    public void flush() {
    }

    @Override
    public void close() throws SecurityException {
    }
}

@@ -2466,24 +2466,30 @@
 , aClass.get
+Simple
 Name())%0D%0A   

Use simple class names when logging.