package org.xbib.logging.test;

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

import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import java.util.logging.Filter;
import java.util.logging.Handler;
import java.util.logging.LogRecord;
import java.util.regex.Pattern;

import org.xbib.logging.ExtLogRecord;
import org.xbib.logging.ExtLogRecord.FormatStyle;
import org.xbib.logging.Level;
import org.xbib.logging.Logger;
import org.xbib.logging.filters.AcceptAllFilter;
import org.xbib.logging.filters.AllFilter;
import org.xbib.logging.filters.AnyFilter;
import org.xbib.logging.filters.DenyAllFilter;
import org.xbib.logging.filters.InvertFilter;
import org.xbib.logging.filters.LevelChangingFilter;
import org.xbib.logging.filters.LevelFilter;
import org.xbib.logging.filters.LevelRangeFilter;
import org.xbib.logging.filters.RegexFilter;
import org.xbib.logging.filters.SubstituteFilter;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;

public final class FilterTests {

    private static final Filter[] NO_FILTERS = new Filter[0];

    public FilterTests() {
    }

    @Test
    public void testAcceptAllFilter() {
        final Filter filter = AcceptAllFilter.getInstance();
        final AtomicBoolean ran = new AtomicBoolean();
        final Handler handler = new CheckingHandler(ran);
        final Logger logger = Logger.getLogger("filterTest");
        logger.setUseParentHandlers(false);
        logger.addHandler(handler);
        logger.setLevel(Level.INFO);
        logger.setFilter(filter);
        handler.setLevel(Level.INFO);
        logger.info("This is a test.");
        assertTrue(ran.get(), "Handler wasn't run");
    }

    @Test
    public void testDenyAllFilter() {
        final Filter filter = DenyAllFilter.getInstance();
        final AtomicBoolean ran = new AtomicBoolean();
        final Handler handler = new CheckingHandler(ran);
        final Logger logger = Logger.getLogger("filterTest");
        logger.setUseParentHandlers(false);
        logger.addHandler(handler);
        logger.setLevel(Level.INFO);
        logger.setFilter(filter);
        handler.setLevel(Level.INFO);
        logger.info("This is a test.");
        assertFalse(ran.get(), "Handler was run");
    }

    @Test
    public void testAllFilter0() {
        final Filter filter = new AllFilter(NO_FILTERS);
        final AtomicBoolean ran = new AtomicBoolean();
        final Handler handler = new CheckingHandler(ran);
        final Logger logger = Logger.getLogger("filterTest");
        logger.setUseParentHandlers(false);
        logger.addHandler(handler);
        logger.setLevel(Level.INFO);
        logger.setFilter(filter);
        handler.setLevel(Level.INFO);
        logger.info("This is a test.");
        assertTrue(ran.get(), "Handler wasn't run");
    }

    @Test
    public void testAllFilter1() {
        final Filter filter = new AllFilter(new Filter[] {
                AcceptAllFilter.getInstance(),
                AcceptAllFilter.getInstance(),
                AcceptAllFilter.getInstance(),
        });
        final AtomicBoolean ran = new AtomicBoolean();
        final Handler handler = new CheckingHandler(ran);
        final Logger logger = Logger.getLogger("filterTest");
        logger.setUseParentHandlers(false);
        logger.addHandler(handler);
        logger.setLevel(Level.INFO);
        logger.setFilter(filter);
        handler.setLevel(Level.INFO);
        logger.info("This is a test.");
        assertTrue(ran.get(), "Handler wasn't run");
    }

    @Test
    public void testAllFilter2() {
        final Filter filter = new AllFilter(new Filter[] {
                AcceptAllFilter.getInstance(),
                DenyAllFilter.getInstance(),
                AcceptAllFilter.getInstance(),
        });
        final AtomicBoolean ran = new AtomicBoolean();
        final Handler handler = new CheckingHandler(ran);
        final Logger logger = Logger.getLogger("filterTest");
        logger.setUseParentHandlers(false);
        logger.addHandler(handler);
        logger.setLevel(Level.INFO);
        logger.setFilter(filter);
        handler.setLevel(Level.INFO);
        logger.info("This is a test.");
        assertFalse(ran.get(), "Handler was run");
    }

    @Test
    public void testAllFilter3() {
        final Filter filter = new AllFilter(new Filter[] {
                DenyAllFilter.getInstance(),
                DenyAllFilter.getInstance(),
                DenyAllFilter.getInstance(),
        });
        final AtomicBoolean ran = new AtomicBoolean();
        final Handler handler = new CheckingHandler(ran);
        final Logger logger = Logger.getLogger("filterTest");
        logger.setUseParentHandlers(false);
        logger.addHandler(handler);
        logger.setLevel(Level.INFO);
        logger.setFilter(filter);
        handler.setLevel(Level.INFO);
        logger.info("This is a test.");
        assertFalse(ran.get(), "Handler was run");
    }

    @Test
    public void testAnyFilter0() {
        final Filter filter = new AnyFilter(NO_FILTERS);
        final AtomicBoolean ran = new AtomicBoolean();
        final Handler handler = new CheckingHandler(ran);
        final Logger logger = Logger.getLogger("filterTest");
        logger.setUseParentHandlers(false);
        logger.addHandler(handler);
        logger.setLevel(Level.INFO);
        logger.setFilter(filter);
        handler.setLevel(Level.INFO);
        logger.info("This is a test.");
        assertFalse(ran.get(), "Handler was run");
    }

    @Test
    public void testAnyFilter1() {
        final Filter filter = new AnyFilter(new Filter[] {
                AcceptAllFilter.getInstance(),
                AcceptAllFilter.getInstance(),
                AcceptAllFilter.getInstance(),
        });
        final AtomicBoolean ran = new AtomicBoolean();
        final Handler handler = new CheckingHandler(ran);
        final Logger logger = Logger.getLogger("filterTest");
        logger.setUseParentHandlers(false);
        logger.addHandler(handler);
        logger.setLevel(Level.INFO);
        logger.setFilter(filter);
        handler.setLevel(Level.INFO);
        logger.info("This is a test.");
        assertTrue(ran.get(), "Handler wasn't run");
    }

    @Test
    public void testAnyFilter2() {
        final Filter filter = new AnyFilter(new Filter[] {
                AcceptAllFilter.getInstance(),
                DenyAllFilter.getInstance(),
                AcceptAllFilter.getInstance(),
        });
        final AtomicBoolean ran = new AtomicBoolean();
        final Handler handler = new CheckingHandler(ran);
        final Logger logger = Logger.getLogger("filterTest");
        logger.setUseParentHandlers(false);
        logger.addHandler(handler);
        logger.setLevel(Level.INFO);
        logger.setFilter(filter);
        handler.setLevel(Level.INFO);
        logger.info("This is a test.");
        assertTrue(ran.get(), "Handler wasn't run");
    }

    @Test
    public void testAnyFilter3() {
        final Filter filter = new AnyFilter(new Filter[] {
                DenyAllFilter.getInstance(),
                DenyAllFilter.getInstance(),
                DenyAllFilter.getInstance(),
        });
        final AtomicBoolean ran = new AtomicBoolean();
        final Handler handler = new CheckingHandler(ran);
        final Logger logger = Logger.getLogger("filterTest");
        logger.setUseParentHandlers(false);
        logger.addHandler(handler);
        logger.setLevel(Level.INFO);
        logger.setFilter(filter);
        handler.setLevel(Level.INFO);
        logger.info("This is a test.");
        assertFalse(ran.get(), "Handler was run");
    }

    @Test
    public void testInvertFilter0() {
        final Filter filter = new InvertFilter(AcceptAllFilter.getInstance());
        final AtomicBoolean ran = new AtomicBoolean();
        final Handler handler = new CheckingHandler(ran);
        final Logger logger = Logger.getLogger("filterTest");
        logger.setUseParentHandlers(false);
        logger.addHandler(handler);
        logger.setLevel(Level.INFO);
        logger.setFilter(filter);
        handler.setLevel(Level.INFO);
        logger.info("This is a test.");
        assertFalse(ran.get(), "Handler was run");
    }

    @Test
    public void testInvertFilter1() {
        final Filter filter = new InvertFilter(DenyAllFilter.getInstance());
        final AtomicBoolean ran = new AtomicBoolean();
        final Handler handler = new CheckingHandler(ran);
        final Logger logger = Logger.getLogger("filterTest");
        logger.setUseParentHandlers(false);
        logger.addHandler(handler);
        logger.setLevel(Level.INFO);
        logger.setFilter(filter);
        handler.setLevel(Level.INFO);
        logger.info("This is a test.");
        assertTrue(ran.get(), "Handler wasn't run");
    }

    @Test
    public void testLevelChangingFilter0() {
        final Filter filter = new LevelChangingFilter(Level.INFO);
        final AtomicBoolean ran = new AtomicBoolean();
        final Handler handler = new CheckingHandler(ran);
        final Logger logger = Logger.getLogger("filterTest");
        logger.setUseParentHandlers(false);
        logger.addHandler(handler);
        logger.setLevel(Level.FINEST);
        logger.setFilter(filter);
        handler.setLevel(Level.INFO);
        logger.finest("This is a test.");
        assertTrue(ran.get(), "Handler wasn't run");
    }

    @Test
    public void testLevelFilter0() {
        final Filter filter = new LevelFilter(Level.INFO);
        final AtomicBoolean ran = new AtomicBoolean();
        final Handler handler = new CheckingHandler(ran);
        final Logger logger = Logger.getLogger("filterTest");
        logger.setUseParentHandlers(false);
        logger.addHandler(handler);
        logger.setLevel(Level.INFO);
        logger.setFilter(filter);
        handler.setLevel(Level.INFO);
        logger.info("This is a test.");
        assertTrue(ran.get(), "Handler wasn't run");
    }

    @Test
    public void testLevelFilter1() {
        final Filter filter = new LevelFilter(Level.WARNING);
        final AtomicBoolean ran = new AtomicBoolean();
        final Handler handler = new CheckingHandler(ran);
        final Logger logger = Logger.getLogger("filterTest");
        logger.setUseParentHandlers(false);
        logger.addHandler(handler);
        logger.setLevel(Level.INFO);
        logger.setFilter(filter);
        handler.setLevel(Level.INFO);
        logger.info("This is a test.");
        assertFalse(ran.get(), "Handler was run");
    }

    @Test
    public void testLevelRangeFilter0() {
        final Filter filter = new LevelRangeFilter(Level.DEBUG, true, Level.WARN, true);
        final AtomicBoolean ran = new AtomicBoolean();
        final Handler handler = new CheckingHandler(ran);
        final Logger logger = Logger.getLogger("filterTest");
        logger.setUseParentHandlers(false);
        logger.addHandler(handler);
        logger.setLevel(Level.INFO);
        logger.setFilter(filter);
        handler.setLevel(Level.INFO);
        logger.info("This is a test.");
        assertTrue(ran.get(), "Handler wasn't run");
    }

    @Test
    public void testLevelRangeFilter1() {
        final Filter filter = new LevelRangeFilter(Level.DEBUG, true, Level.WARN, true);
        final AtomicBoolean ran = new AtomicBoolean();
        final Handler handler = new CheckingHandler(ran);
        final Logger logger = Logger.getLogger("filterTest");
        logger.setUseParentHandlers(false);
        logger.addHandler(handler);
        logger.setLevel(Level.INFO);
        logger.setFilter(filter);
        handler.setLevel(Level.INFO);
        logger.severe("This is a test.");
        assertFalse(ran.get(), "Handler was run");
    }

    @Test
    public void testLevelRangeFilter2() {
        final Filter filter = new LevelRangeFilter(Level.DEBUG, true, Level.WARN, true);
        final AtomicBoolean ran = new AtomicBoolean();
        final Handler handler = new CheckingHandler(ran);
        final Logger logger = Logger.getLogger("filterTest");
        logger.setUseParentHandlers(false);
        logger.addHandler(handler);
        logger.setLevel(Level.DEBUG);
        logger.setFilter(filter);
        handler.setLevel(Level.DEBUG);
        logger.log(Level.DEBUG, "This is a test.");
        assertTrue(ran.get(), "Handler wasn't run");
    }

    @Test
    public void testLevelRangeFilter3() {
        final Filter filter = new LevelRangeFilter(Level.DEBUG, false, Level.WARN, true);
        final AtomicBoolean ran = new AtomicBoolean();
        final Handler handler = new CheckingHandler(ran);
        final Logger logger = Logger.getLogger("filterTest");
        logger.setUseParentHandlers(false);
        logger.addHandler(handler);
        logger.setLevel(Level.DEBUG);
        logger.setFilter(filter);
        handler.setLevel(Level.DEBUG);
        logger.log(Level.DEBUG, "This is a test.");
        assertFalse(ran.get(), "Handler was run");
    }

    @Test
    public void testLevelRangeFilter4() {
        final Filter filter = new LevelRangeFilter(Level.DEBUG, true, Level.WARN, false);
        final AtomicBoolean ran = new AtomicBoolean();
        final Handler handler = new CheckingHandler(ran);
        final Logger logger = Logger.getLogger("filterTest");
        logger.setUseParentHandlers(false);
        logger.addHandler(handler);
        logger.setLevel(Level.INFO);
        logger.setFilter(filter);
        handler.setLevel(Level.INFO);
        logger.warning("This is a test.");
        assertFalse(ran.get(), "Handler was run");
    }

    @Test
    public void testRegexFilter0() {
        final Filter filter = new RegexFilter("test");
        final AtomicBoolean ran = new AtomicBoolean();
        final Handler handler = new CheckingHandler(ran);
        final Logger logger = Logger.getLogger("filterTest");
        logger.setUseParentHandlers(false);
        logger.addHandler(handler);
        logger.setLevel(Level.INFO);
        logger.setFilter(filter);
        handler.setLevel(Level.INFO);
        logger.info("This is a test.");
        assertTrue(ran.get(), "Handler wasn't run");
    }

    @Test
    public void testRegexFilter1() {
        final Filter filter = new RegexFilter("pest");
        final AtomicBoolean ran = new AtomicBoolean();
        final Handler handler = new CheckingHandler(ran);
        final Logger logger = Logger.getLogger("filterTest");
        logger.setUseParentHandlers(false);
        logger.addHandler(handler);
        logger.setLevel(Level.INFO);
        logger.setFilter(filter);
        handler.setLevel(Level.INFO);
        logger.info("This is a test.");
        assertFalse(ran.get(), "Handler was run");
    }

    @Test
    @Disabled("This test is testing essentially invalid/coincidental behavior")
    public void testRegexFilter2() {
        final Filter filter = new RegexFilter("pest");
        final AtomicBoolean ran = new AtomicBoolean();
        final Handler handler = new CheckingHandler(ran);
        final Logger logger = Logger.getLogger("filterTest");
        final ExtLogRecord record = new ExtLogRecord(Level.INFO, "This is a test %s", FormatStyle.PRINTF, "filterTest");
        record.setParameters(new String[] { "pest" });
        logger.setUseParentHandlers(false);
        logger.addHandler(handler);
        logger.setLevel(Level.INFO);
        logger.setFilter(filter);
        handler.setLevel(Level.INFO);
        logger.log(record);
        assertTrue(ran.get(), "Handler wasn't run");
    }

    @Test
    public void testRegexFilter3() {
        final Filter filter = new RegexFilter("pest");
        final AtomicBoolean ran = new AtomicBoolean();
        final Handler handler = new CheckingHandler(ran);
        final Logger logger = Logger.getLogger("filterTest");
        final ExtLogRecord record = new ExtLogRecord(Level.INFO, "This is a test %s", FormatStyle.PRINTF, "filterTest");
        record.setParameters(new String[] { "test" });
        logger.setUseParentHandlers(false);
        logger.addHandler(handler);
        logger.setLevel(Level.INFO);
        logger.setFilter(filter);
        handler.setLevel(Level.INFO);
        logger.log(record);
        assertFalse(ran.get(), "Handler was run");
    }

    @Test
    public void regexFilterExceptionNullMessageTest() {
        final ExtLogRecord logRecord = new ExtLogRecord(Level.ALL, null, null);
        final Filter filter = new RegexFilter("test");
        boolean isLoggable = filter.isLoggable(logRecord);
        assertFalse(isLoggable);
        assertNull(logRecord.getFormattedMessage());
    }

    @Test
    public void testSubstitueFilter0() {
        final Filter filter = new SubstituteFilter(Pattern.compile("test"), "lunch", true);
        final AtomicReference<String> result = new AtomicReference<String>();
        final Handler handler = new MessageCheckingHandler(result);
        final Logger logger = Logger.getLogger("filterTest");
        logger.setUseParentHandlers(false);
        logger.addHandler(handler);
        logger.setLevel(Level.INFO);
        logger.setFilter(filter);
        handler.setLevel(Level.INFO);
        logger.info("This is a test test.");
        assertEquals("This is a lunch lunch.", result.get(), "Substitution was not correctly applied");
    }

    @Test
    public void testSubstituteFilter1() {
        final Filter filter = new SubstituteFilter(Pattern.compile("test"), "lunch", false);
        final AtomicReference<String> result = new AtomicReference<String>();
        final Handler handler = new MessageCheckingHandler(result);
        final Logger logger = Logger.getLogger("filterTest");
        logger.setUseParentHandlers(false);
        logger.addHandler(handler);
        logger.setLevel(Level.INFO);
        logger.setFilter(filter);
        handler.setLevel(Level.INFO);
        logger.info("This is a test test.");
        assertEquals("This is a lunch test.", result.get(), "Substitution was not correctly applied");
    }

    @Test
    public void testSubstituteFilter2() {
        final Filter filter = new SubstituteFilter(Pattern.compile("t(es)t"), "lunch$1", true);
        final AtomicReference<String> result = new AtomicReference<String>();
        final Handler handler = new MessageCheckingHandler(result);
        final Logger logger = Logger.getLogger("filterTest");
        logger.setUseParentHandlers(false);
        logger.addHandler(handler);
        logger.setLevel(Level.INFO);
        logger.setFilter(filter);
        handler.setLevel(Level.INFO);
        logger.info("This is a test test.");
        assertEquals("This is a lunches lunches.", result.get(), "Substitution was not correctly applied");
    }

    @Test
    public void testSubstituteFilter3() {
        final Filter filter = new SubstituteFilter(Pattern.compile("t(es)t"), "lunch$1", true);
        final ExtLogRecord record = new ExtLogRecord(Level.INFO, "This is a test %s", FormatStyle.PRINTF,
                FilterTests.class.getName());
        record.setParameters(new String[] { "test" });
        filter.isLoggable(record);
        assertEquals("This is a lunches lunches", record.getFormattedMessage(), "Substitution was not correctly applied");
    }

    @Test
    public void substituteFilterExceptionNullMessageTest() {
        final ExtLogRecord logRecord = new ExtLogRecord(Level.ALL, null, null);
        final Filter filter = new SubstituteFilter(Pattern.compile("test"), "lunch", true);
        filter.isLoggable(logRecord);
        assertEquals("null", logRecord.getFormattedMessage());
    }

    @Test
    public void substitutionFilterWithLogRecord() {
        final AtomicReference<String> result = new AtomicReference<String>();
        final Handler handler = new MessageCheckingHandler(result);
        final Logger logger = Logger.getLogger("filterTest");
        final Filter filter = new SubstituteFilter(Pattern.compile("test"), "lunch", true);

        logger.setUseParentHandlers(false);
        logger.addHandler(handler);
        logger.setLevel(Level.INFO);
        logger.setFilter(filter);
        handler.setLevel(Level.INFO);

        final LogRecord record = new LogRecord(Level.INFO, "{0}");
        record.setLoggerName("filterTest");
        record.setParameters(new Object[] { "test" });

        logger.log(record);
        assertEquals("lunch", result.get(), "The substitution was not correctly applied");
    }

    private static final class MessageCheckingHandler extends Handler {
        private final AtomicReference<String> msg;

        private MessageCheckingHandler(final AtomicReference<String> msg) {
            this.msg = msg;
        }

        public void publish(final LogRecord record) {
            msg.set(record.getMessage());
        }

        public void flush() {
        }

        public void close() throws SecurityException {
        }
    }

    private static final class CheckingHandler extends Handler {
        private final AtomicBoolean ran;

        public CheckingHandler(final AtomicBoolean ran) {
            this.ran = ran;
        }

        public void publish(final LogRecord record) {
            if (isLoggable(record)) {
                ran.set(true);
            }
        }

        public void flush() {
        }

        public void close() throws SecurityException {
        }
    }
}
