package org.xbib.logging.test;

import static org.junit.jupiter.api.Assertions.*;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
import java.nio.charset.StandardCharsets;
import java.util.logging.Formatter;
import java.util.logging.Handler;
import java.util.logging.LogRecord;

import org.xbib.logging.ExtHandler;
import org.xbib.logging.ExtLogRecord;
import org.xbib.logging.Level;
import org.xbib.logging.formatters.SimpleFormatter;
import org.xbib.logging.handlers.FileHandler;
import org.xbib.logging.handlers.OutputStreamHandler;
import org.xbib.logging.handlers.WriterHandler;
import org.junit.jupiter.api.Test;

public final class HandlerTests {

    private final Formatter testFormatter = new SimpleFormatter();

    public HandlerTests() {
    }

    @Test
    public void testNullHandler() {
        try (final ExtHandler handler = new ExtHandler() {}) {
            handler.setLevel(Level.ALL);
            handler.publish(new ExtLogRecord(Level.INFO, "Test message", null));
        }
    }

    private void initHandler(ExtHandler handler) throws UnsupportedEncodingException {
        handler.setFormatter(testFormatter);
        handler.setLevel(Level.ALL);
        handler.setAutoFlush(true);
        handler.setEncoding("utf-8");
    }

    private void testPublish(ExtHandler handler) {
        testPublish(handler, Level.INFO);
    }

    private void testPublish(ExtHandler handler, Level level) {
        handler.publish(new ExtLogRecord(level, "Test message", null));
    }

    @Test
    public void testWriterHandler() throws Throwable {
        final WriterHandler handler = new WriterHandler();
        initHandler(handler);
        final StringWriter writer = new StringWriter();
        handler.setWriter(writer);
        testPublish(handler);
        assertEquals("Test message", writer.toString());
    }

    @Test
    public void testOutputStreamHandler() throws Throwable {
        final OutputStreamHandler handler = new OutputStreamHandler();
        initHandler(handler);
        final ByteArrayOutputStream stream = new ByteArrayOutputStream();
        handler.setOutputStream(stream);
        testPublish(handler);
        assertEquals("Test message", stream.toString(StandardCharsets.UTF_8));
    }

    @Test
    public void testFileHandler() throws Throwable {
        final FileHandler handler = new FileHandler();
        initHandler(handler);
        final File tempFile = File.createTempFile("jblm-", ".log");
        try {
            handler.setAppend(true);
            handler.setFile(tempFile);
            testPublish(handler);
            handler.setFile(tempFile);
            testPublish(handler);
            handler.setAppend(false);
            handler.setFile(tempFile);
            testPublish(handler);
            handler.close();
            try (ByteArrayOutputStream os = new ByteArrayOutputStream();
                 FileInputStream is = new FileInputStream(tempFile)) {
                int r;
                while ((r = is.read()) != -1)
                    os.write(r);
                assertEquals("Test message", os.toString(StandardCharsets.UTF_8));
                tempFile.deleteOnExit();
            }
        } finally {
            tempFile.delete();
        }
    }

    @Test
    public void testEnableDisableHandler() {
        final StringListHandler handler = new StringListHandler();
        testPublish(handler);
        assertEquals(1, handler.size());
        handler.setEnabled(false);
        testPublish(handler);
        assertEquals(1, handler.size());
        handler.setEnabled(true);
        testPublish(handler);
        assertEquals(2, handler.size());
    }

    @Test
    public void testHandlerDelegation() throws Throwable {
        final StringListHandler debugHandler = new StringListHandler();
        debugHandler.setLevel(Level.DEBUG);

        final StringListHandler infoHandler = new StringListHandler();
        infoHandler.setLevel(Level.INFO);

        final StringListHandler errorHandler = new StringListHandler();
        errorHandler.setLevel(Level.ERROR);

        final MultiHandler handler = MultiHandler.of(debugHandler, infoHandler, errorHandler);
        // Turn off the level for the handler
        handler.setLevel(Level.OFF);

        // Log a debug message
        testPublish(handler, Level.DEBUG);
        assertEquals(1, debugHandler.size());
        assertEquals(0, infoHandler.size());
        assertEquals(0, errorHandler.size());

        // Log an info message
        testPublish(handler, Level.INFO);
        assertEquals(2, debugHandler.size());
        assertEquals(1, infoHandler.size());
        assertEquals(0, errorHandler.size());

        // Log a warn message
        testPublish(handler, Level.WARN);
        assertEquals(3, debugHandler.size());
        assertEquals(2, infoHandler.size());
        assertEquals(0, errorHandler.size());

        // Log an error message
        testPublish(handler, Level.ERROR);
        assertEquals(4, debugHandler.size());
        assertEquals(3, infoHandler.size());
        assertEquals(1, errorHandler.size());
    }

    static class MultiHandler extends ExtHandler {
        protected MultiHandler() {
            setErrorManager(AssertingErrorManager.of());
        }

        public void publish(final LogRecord record) {
            if (isEnabled() && record != null) {
                doPublish(ExtLogRecord.wrap(record));
            }
        }

        public void publish(final ExtLogRecord record) {
            if (isEnabled() && record != null) {
                doPublish(record);
            }
        }

        static MultiHandler of(final ExtHandler... handlers) {
            final MultiHandler result = new MultiHandler();
            result.setHandlers(handlers);
            return result;
        }

        @Override
        protected void doPublish(final ExtLogRecord record) {
            // Process child handlers
            for (Handler handler : getHandlers()) {
                if (handler.isLoggable(record)) {
                    handler.publish(record);
                }
            }
            super.doPublish(record);
        }
    }
}
