/**
 * Logback: the reliable, generic, fast and flexible logging framework.
 * Copyright (C) 1999-2015, QOS.ch. All rights reserved.
 *
 * This program and the accompanying materials are dual-licensed under
 * either the terms of the Eclipse Public License v1.0 as published by
 * the Eclipse Foundation
 *
 *   or (per the licensee's choosing)
 *
 * under the terms of the GNU Lesser General Public License version 2.1
 * as published by the Free Software Foundation.
 */
package ch.qos.logback.core.joran;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.net.URLConnection;
import java.util.HashMap;
import java.util.function.Supplier;
import java.util.jar.JarOutputStream;
import java.util.zip.ZipEntry;

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import ch.qos.logback.core.Context;
import ch.qos.logback.core.ContextBase;
import ch.qos.logback.core.CoreConstants;
import ch.qos.logback.core.joran.action.Action;
import ch.qos.logback.core.joran.action.TopElementAction;
import ch.qos.logback.core.joran.action.ext.IncAction;
import ch.qos.logback.core.joran.spi.ElementSelector;
import ch.qos.logback.core.joran.spi.JoranException;
import ch.qos.logback.core.status.Status;
import ch.qos.logback.core.testUtil.CoreTestConstants;
import ch.qos.logback.core.testUtil.RandomUtil;
import ch.qos.logback.core.testUtil.TrivialStatusListener;

public class TrivialConfiguratorTest {

    Context context = new ContextBase();
    HashMap<ElementSelector, Supplier<Action>> rulesMap = new HashMap<>();

    @BeforeEach
    public void setUp() {
        // rule store is case-insensitive
        rulesMap.put(new ElementSelector("x"), () -> new TopElementAction());
        rulesMap.put(new ElementSelector("x/inc"), () -> new IncAction());

    }

    public void doTest(String filename) throws Exception {
        TrivialConfigurator trivialConfigurator = new TrivialConfigurator(rulesMap);

        trivialConfigurator.setContext(context);
        trivialConfigurator.doConfigure(filename);
    }

    @Test
    public void smoke() throws Exception {
        int oldBeginCount = IncAction.beginCount;
        int oldEndCount = IncAction.endCount;
        int oldErrorCount = IncAction.errorCount;
        doTest(CoreTestConstants.TEST_SRC_PREFIX + "input/joran/" + "inc.xml");
        Assertions.assertEquals(oldErrorCount, IncAction.errorCount);
        Assertions.assertEquals(oldBeginCount + 1, IncAction.beginCount);
        Assertions.assertEquals(oldEndCount + 1, IncAction.endCount);
    }

    @Test
    public void inexistentFile() {
        TrivialStatusListener tsl = new TrivialStatusListener();
        tsl.start();
        String filename = CoreTestConstants.TEST_SRC_PREFIX + "input/joran/" + "nothereBLAH.xml";
        context.getStatusManager().add(tsl);
        try {
            doTest(filename);
        } catch (Exception e) {
            Assertions.assertTrue(e.getMessage().startsWith("Could not open ["));
        }
        Assertions.assertTrue(tsl.list.size() >= 1, tsl.list.size() + " should be greater than or equal to 1");
        Status s0 = tsl.list.get(0);
        Assertions.assertTrue(s0.getMessage().startsWith("Could not open ["));
    }

    @Test
    public void illFormedXML() {
        TrivialStatusListener tsl = new TrivialStatusListener();
        tsl.start();
        String filename = CoreTestConstants.TEST_SRC_PREFIX + "input/joran/" + "illformed.xml";
        context.getStatusManager().add(tsl);
        try {
            doTest(filename);
        } catch (Exception e) {
        }
        Assertions.assertEquals(2, tsl.list.size());
        Status s0 = tsl.list.get(0);
        Assertions.assertTrue(s0.getMessage().startsWith(CoreConstants.XML_PARSING));
    }

    @Test
    public void lbcore105() throws IOException, JoranException {
        String jarEntry = "buzz.xml";
        File jarFile = makeRandomJarFile();
        fillInJarFile(jarFile, jarEntry);
        URL url = asURL(jarFile, jarEntry);
        TrivialConfigurator tc = new TrivialConfigurator(rulesMap);
        tc.setContext(context);
        tc.doConfigure(url);
        // deleting an open file fails
        Assertions.assertTrue(jarFile.delete());
        Assertions.assertFalse(jarFile.exists());
    }

    @Test
    public void lbcore127() throws IOException, JoranException {
        String jarEntry = "buzz.xml";
        String jarEntry2 = "lightyear.xml";

        File jarFile = makeRandomJarFile();
        fillInJarFile(jarFile, jarEntry, jarEntry2);

        URL url1 = asURL(jarFile, jarEntry);
        URL url2 = asURL(jarFile, jarEntry2);

        URLConnection urlConnection2 = url2.openConnection();
        urlConnection2.setUseCaches(false);
        InputStream is = urlConnection2.getInputStream();

        TrivialConfigurator tc = new TrivialConfigurator(rulesMap);
        tc.setContext(context);
        tc.doConfigure(url1);

        is.read();
        is.close();

        // deleting an open file fails
        Assertions.assertTrue(jarFile.delete());
        Assertions.assertFalse(jarFile.exists());
    }

    File makeRandomJarFile() {
        File outputDir = new File(CoreTestConstants.OUTPUT_DIR_PREFIX);
        outputDir.mkdirs();
        int randomPart = RandomUtil.getPositiveInt();
        return new File(CoreTestConstants.OUTPUT_DIR_PREFIX + "foo-" + randomPart + ".jar");
    }

    private void fillInJarFile(File jarFile, String jarEntryName) throws IOException {
        fillInJarFile(jarFile, jarEntryName, null);
    }

    private void fillInJarFile(File jarFile, String jarEntryName1, String jarEntryName2) throws IOException {
        JarOutputStream jos = new JarOutputStream(new FileOutputStream(jarFile));
        jos.putNextEntry(new ZipEntry(jarEntryName1));
        jos.write("<x/>".getBytes());
        jos.closeEntry();
        if (jarEntryName2 != null) {
            jos.putNextEntry(new ZipEntry(jarEntryName2));
            jos.write("<y/>".getBytes());
            jos.closeEntry();
        }
        jos.close();
    }

    URL asURL(File jarFile, String jarEntryName) throws IOException {
        URL innerURL = jarFile.toURI().toURL();
        return new URL("jar:" + innerURL + "!/" + jarEntryName);
    }

}
