package org.xbib.logging.log4j.test;

import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.Arrays;
import java.util.UUID;

import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.Marker;
import org.apache.logging.log4j.MarkerManager;
import org.apache.logging.log4j.message.AbstractMessageFactory;
import org.apache.logging.log4j.message.Message;
import org.apache.logging.log4j.message.MessageFactory;
import org.apache.logging.log4j.message.MessageFactory2;
import org.xbib.logging.ExtLogRecord;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;

public class LoggerTest extends AbstractTest {

    private final String loggerName = LoggerTest.class.getPackage().getName();

    private final Marker marker = MarkerManager.getMarker("test");

    private TestQueueHandler handler;

    private org.xbib.logging.Logger lmLogger;

    @BeforeEach
    public void setup() {
        lmLogger = org.xbib.logging.Logger.getLogger("org.xbib.logging.log4j");
        lmLogger.setLevel(java.util.logging.Level.INFO);
        final TestQueueHandler handler = new TestQueueHandler();
        lmLogger.addHandler(handler);
        this.handler = handler;
    }

    @AfterEach
    public void tearDown() {
        handler.close();
        lmLogger.removeHandler(handler);
    }

    @Test
    public void testNamedLogger() {
        final Logger logger = LogManager.getLogger(loggerName);
        logger.info("Test message");
        ExtLogRecord record = handler.poll();
        assertNotNull(record);
        assertEquals("Test message", record.getMessage());
        logger.info("Test message parameter {}", 1);
        record = handler.poll();
        assertNotNull(record);
        assertEquals("Test message parameter 1", record.getMessage());
    }

    @SuppressWarnings("PlaceholderCountMatchesArgumentCount")
    @Test
    public void testNamedFormatterLogger() {
        final Logger logger = LogManager.getFormatterLogger(loggerName);
        logger.info("Test message parameter %s", 1);
        final ExtLogRecord record = handler.poll();
        assertNotNull(record);
        assertEquals("Test message parameter 1", record.getMessage());
    }

    @Test
    public void testCurrentClassLogger() {
        final String expectedName = LoggerTest.class.getName();
        final Logger logger = LogManager.getLogger();
        assertEquals(expectedName, logger.getName());
        logger.info("Test message");
        final ExtLogRecord record = handler.poll();
        assertNotNull(record);
        assertEquals("Test message", record.getMessage());
        assertEquals(expectedName, record.getLoggerName());
    }

    @Test
    public void testObjectLogger() {
        final String expectedName = LoggerTest.class.getName();
        final Logger logger = LogManager.getLogger(this);
        assertEquals(expectedName, logger.getName());
        logger.info("Test message");
        final ExtLogRecord record = handler.poll();
        assertNotNull(record);
        assertEquals("Test message", record.getMessage());
        assertEquals(expectedName, record.getLoggerName());
    }

    @Test
    public void testCurrentClassMessageFactoryLogger() {
        final String prefix = generatePrefix();
        final String expectedName = LoggerTest.class.getName();
        final MessageFactory messageFactory = new TestMessageFactory(prefix);
        final Logger logger = LogManager.getLogger(messageFactory);
        assertEquals(expectedName, logger.getName());
        logger.info("Test message");
        final ExtLogRecord record = handler.poll();
        assertNotNull(record);
        assertEquals(prefix + "Test message", record.getMessage());
        assertEquals(expectedName, record.getLoggerName());
    }

    @Test
    public void testObjectMessageFactoryLogger() {
        final String prefix = generatePrefix();
        final String expectedName = LoggerTest.class.getName();
        final MessageFactory messageFactory = new TestMessageFactory(prefix);
        final Logger logger = LogManager.getLogger(this, messageFactory);
        assertEquals(expectedName, logger.getName());
        logger.info("Test message");
        final ExtLogRecord record = handler.poll();
        assertNotNull(record);
        assertEquals(prefix + "Test message", record.getMessage());
        assertEquals(expectedName, record.getLoggerName());
    }

    @Test
    public void testNamedMessageFactoryLogger() {
        final String prefix = generatePrefix();
        final String expectedName = loggerName;
        final MessageFactory messageFactory = new TestMessageFactory(prefix);
        final Logger logger = LogManager.getLogger(loggerName, messageFactory);
        assertEquals(expectedName, logger.getName());
        logger.info("Test message");
        final ExtLogRecord record = handler.poll();
        assertNotNull(record);
        assertEquals(prefix + "Test message", record.getMessage());
        assertEquals(expectedName, record.getLoggerName());
    }

    @Test
    public void tesTypeMessageFactoryLogger() {
        final String prefix = generatePrefix();
        final String expectedName = LoggerTest.class.getName();
        final MessageFactory messageFactory = new TestMessageFactory(prefix);
        final Logger logger = LogManager.getLogger(LoggerTest.class, messageFactory);
        assertEquals(expectedName, logger.getName());
        logger.info("Test message");
        final ExtLogRecord record = handler.poll();
        assertNotNull(record);
        assertEquals(prefix + "Test message", record.getMessage());
        assertEquals(expectedName, record.getLoggerName());
    }

    @Test
    public void testAllEnabled() {
        lmLogger.setLevel(org.xbib.logging.Level.ALL);
        testLevelEnabled(LogManager.getLogger(loggerName), Level.ALL);
        testLevelEnabled(LogManager.getFormatterLogger(loggerName), Level.ALL);
    }

    @Test
    public void testTraceEnabled() {
        lmLogger.setLevel(org.xbib.logging.Level.TRACE);
        testLevelEnabled(LogManager.getLogger(loggerName), Level.TRACE);
        testLevelEnabled(LogManager.getFormatterLogger(loggerName), Level.TRACE);
    }

    @Test
    public void testDebugEnabled() {
        lmLogger.setLevel(org.xbib.logging.Level.DEBUG);
        testLevelEnabled(LogManager.getLogger(loggerName), Level.DEBUG);
        testLevelEnabled(LogManager.getFormatterLogger(loggerName), Level.DEBUG);
    }

    @Test
    public void testInfoEnabled() {
        testLevelEnabled(LogManager.getFormatterLogger(loggerName), Level.INFO);
    }

    @Test
    public void testWarnEnabled() {
        testLevelEnabled(LogManager.getLogger(loggerName), Level.WARN);
        testLevelEnabled(LogManager.getFormatterLogger(loggerName), Level.WARN);
    }

    @Test
    public void testErrorEnabled() {
        testLevelEnabled(LogManager.getLogger(loggerName), Level.ERROR);
        testLevelEnabled(LogManager.getFormatterLogger(loggerName), Level.ERROR);
    }

    @Test
    public void testFatalEnabled() {
        testLevelEnabled(LogManager.getLogger(loggerName), Level.FATAL);
        testLevelEnabled(LogManager.getFormatterLogger(loggerName), Level.FATAL);
    }

    @Test
    public void testOffEnabled() {
        assertFalse(LogManager.getLogger(loggerName).isEnabled(Level.OFF));
        assertFalse(LogManager.getLogger(loggerName).isEnabled(Level.OFF, marker));
        assertFalse(LogManager.getFormatterLogger(loggerName).isEnabled(Level.OFF));
        assertFalse(LogManager.getFormatterLogger(loggerName).isEnabled(Level.OFF, marker));
    }

    private void testLevelEnabled(final Logger logger, final Level level) {
        final String msg = String.format("Expected level %s to be enabled on logger %s", level, logger);
        final String markerMsg = String.format("Expected level %s to be enabled on logger %s with marker %s", level, logger,
                marker);
        assertTrue(logger.isEnabled(level), msg);
        assertTrue(logger.isEnabled(level, marker), markerMsg);
        if (level.equals(Level.FATAL)) {
            assertTrue(logger.isFatalEnabled(), msg);
            assertTrue(logger.isFatalEnabled(marker), markerMsg);
        } else if (level.equals(Level.ERROR)) {
            assertTrue(logger.isErrorEnabled(), msg);
            assertTrue(logger.isErrorEnabled(marker), markerMsg);
        } else if (level.equals(Level.WARN)) {
            assertTrue(logger.isWarnEnabled(), msg);
            assertTrue(logger.isWarnEnabled(marker), markerMsg);
        } else if (level.equals(Level.INFO)) {
            assertTrue(logger.isInfoEnabled(), msg);
            assertTrue(logger.isInfoEnabled(marker), markerMsg);
        } else if (level.equals(Level.DEBUG)) {
            assertTrue(logger.isDebugEnabled(), msg);
            assertTrue(logger.isDebugEnabled(marker), markerMsg);
        } else if (level.equals(Level.TRACE)) {
            assertTrue(logger.isTraceEnabled(), msg);
            assertTrue(logger.isTraceEnabled(marker), markerMsg);
        }
    }

    private static String generatePrefix() {
        return "[" + UUID.randomUUID() + "] ";
    }

    @SuppressWarnings("serial")
    private static class TestMessageFactory extends AbstractMessageFactory implements MessageFactory2 {

        private final String prefix;

        private TestMessageFactory(final String prefix) {
            this.prefix = prefix;
        }

        @Override
        public Message newMessage(final CharSequence charSequence) {
            return newMessage(String.valueOf(charSequence));
        }

        /*
         * (non-Javadoc)
         *
         * @see org.apache.logging.log4j.message.MessageFactory#newMessage(java.lang.Object)
         */
        @Override
        public Message newMessage(final Object message) {
            return newMessage(String.valueOf(message));
        }

        /*
         * (non-Javadoc)
         *
         * @see org.apache.logging.log4j.message.MessageFactory#newMessage(java.lang.String)
         */
        @Override
        public Message newMessage(final String message) {
            return new TestMessage(prefix, null, message);
        }

        @Override
        public Message newMessage(final String message, final Object... params) {
            return new TestMessage(prefix, null, message, params);
        }
    }

    @SuppressWarnings("serial")
    private static class TestMessage implements Message {

        private final String format;

        private transient final Object[] params;

        private final Throwable cause;

        TestMessage(final String prefix, final Throwable cause, final String format, final Object... params) {
            this.format = prefix == null ? "" : prefix + format;
            this.params = params == null ? new Object[0] : Arrays.copyOf(params, params.length);
            this.cause = cause;
        }

        @Override
        public String getFormattedMessage() {
            if (format == null) {
                return null;
            }
            if (cause == null) {
                return String.format(format, params);
            }
            final StringWriter writer = new StringWriter();
            writer.append(String.format(format, params));
            writer.write(System.lineSeparator());
            cause.printStackTrace(new PrintWriter(writer));
            return writer.toString();
        }

        @Override
        public String getFormat() {
            return format;
        }

        @Override
        public Object[] getParameters() {
            return params;
        }

        @Override
        public Throwable getThrowable() {
            return cause;
        }
    }
}
