/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 * 
 *   http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
package freemarker.cache;

import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;

import java.io.IOException;
import java.util.Arrays;
import java.util.List;

import org.junit.Test;

import freemarker.core.TemplateConfigurer;
import freemarker.template.Configuration;

public class TemplateConfigurerFactoryTest {
    
    private Configuration cfg = new Configuration(Configuration.VERSION_2_3_22);

    @Test
    public void testCondition1() throws IOException, TemplateConfigurerFactoryException {
        TemplateConfigurer tc = newTemplateConfigurer(1);
        
        TemplateConfigurerFactory tcf = new ConditionalTemplateConfigurerFactory(new FileNameGlobMatcher("*.ftlx"), tc);
        tcf.setConfiguration(cfg);
        
        assertNotApplicable(tcf, "x.ftl");
        assertApplicable(tcf, "x.ftlx", tc);
    }

    @Test
    public void testCondition2() throws IOException, TemplateConfigurerFactoryException {
        TemplateConfigurer tc = newTemplateConfigurer(1);
        
        TemplateConfigurerFactory tcf = new ConditionalTemplateConfigurerFactory(
                new FileNameGlobMatcher("*.ftlx"),
                new ConditionalTemplateConfigurerFactory(
                        new FileNameGlobMatcher("x.*"), tc));
        tcf.setConfiguration(cfg);
        
        assertNotApplicable(tcf, "x.ftl");
        assertNotApplicable(tcf, "y.ftlx");
        assertApplicable(tcf, "x.ftlx", tc);
    }

    @Test
    public void testMerging() throws IOException, TemplateConfigurerFactoryException {
        TemplateConfigurer tc1 = newTemplateConfigurer(1);
        TemplateConfigurer tc2 = newTemplateConfigurer(2);
        TemplateConfigurer tc3 = newTemplateConfigurer(3);
        
        TemplateConfigurerFactory tcf = new MergingTemplateConfigurerFactory(
                new ConditionalTemplateConfigurerFactory(new FileNameGlobMatcher("*.ftlx"), tc1),
                new ConditionalTemplateConfigurerFactory(new FileNameGlobMatcher("*a*.*"), tc2),
                new ConditionalTemplateConfigurerFactory(new FileNameGlobMatcher("*b*.*"), tc3));
        tcf.setConfiguration(cfg);
        
        assertNotApplicable(tcf, "x.ftl");
        assertApplicable(tcf, "x.ftlx", tc1);
        assertApplicable(tcf, "a.ftl", tc2);
        assertApplicable(tcf, "b.ftl", tc3);
        assertApplicable(tcf, "a.ftlx", tc1, tc2);
        assertApplicable(tcf, "b.ftlx", tc1, tc3);
        assertApplicable(tcf, "ab.ftl", tc2, tc3);
        assertApplicable(tcf, "ab.ftlx", tc1, tc2, tc3);
        
        assertNotApplicable(new MergingTemplateConfigurerFactory(), "x.ftl");
    }

    @Test
    public void testFirstMatch() throws IOException, TemplateConfigurerFactoryException {
        TemplateConfigurer tc1 = newTemplateConfigurer(1);
        TemplateConfigurer tc2 = newTemplateConfigurer(2);
        TemplateConfigurer tc3 = newTemplateConfigurer(3);
        
        FirstMatchTemplateConfigurerFactory tcf = new FirstMatchTemplateConfigurerFactory(
                new ConditionalTemplateConfigurerFactory(new FileNameGlobMatcher("*.ftlx"), tc1),
                new ConditionalTemplateConfigurerFactory(new FileNameGlobMatcher("*a*.*"), tc2),
                new ConditionalTemplateConfigurerFactory(new FileNameGlobMatcher("*b*.*"), tc3));
        tcf.setConfiguration(cfg);

        try {
            assertNotApplicable(tcf, "x.ftl");
        } catch (TemplateConfigurerFactoryException e) {
            assertThat(e.getMessage(), containsString("x.ftl"));
        }
        tcf.setNoMatchErrorDetails("Test details");
        try {
            assertNotApplicable(tcf, "x.ftl");
        } catch (TemplateConfigurerFactoryException e) {
            assertThat(e.getMessage(), containsString("Test details"));
        }
        
        tcf.setAllowNoMatch(true);
        
        assertNotApplicable(tcf, "x.ftl");
        assertApplicable(tcf, "x.ftlx", tc1);
        assertApplicable(tcf, "a.ftl", tc2);
        assertApplicable(tcf, "b.ftl", tc3);
        assertApplicable(tcf, "a.ftlx", tc1);
        assertApplicable(tcf, "b.ftlx", tc1);
        assertApplicable(tcf, "ab.ftl", tc2);
        assertApplicable(tcf, "ab.ftlx", tc1);
        
        assertNotApplicable(new FirstMatchTemplateConfigurerFactory().allowNoMatch(true), "x.ftl");
    }

    @Test
    public void testComplex() throws IOException, TemplateConfigurerFactoryException {
        TemplateConfigurer tcA = newTemplateConfigurer(1);
        TemplateConfigurer tcBSpec = newTemplateConfigurer(2);
        TemplateConfigurer tcBCommon = newTemplateConfigurer(3);
        TemplateConfigurer tcHH = newTemplateConfigurer(4);
        TemplateConfigurer tcHtml = newTemplateConfigurer(5);
        TemplateConfigurer tcXml = newTemplateConfigurer(6);
        TemplateConfigurer tcNWS = newTemplateConfigurer(7);
        
        TemplateConfigurerFactory tcf = new MergingTemplateConfigurerFactory(
                new FirstMatchTemplateConfigurerFactory(
                        new ConditionalTemplateConfigurerFactory(new PathGlobMatcher("a/**"), tcA),
                        new ConditionalTemplateConfigurerFactory(new PathGlobMatcher("b/**"),
                                new MergingTemplateConfigurerFactory(
                                    new ConditionalTemplateConfigurerFactory(new FileNameGlobMatcher("*"), tcBCommon),
                                    new ConditionalTemplateConfigurerFactory(new FileNameGlobMatcher("*.s.*"), tcBSpec))))
                        .allowNoMatch(true),
                new FirstMatchTemplateConfigurerFactory(
                        new ConditionalTemplateConfigurerFactory(new FileNameGlobMatcher("*.hh"), tcHH),
                        new ConditionalTemplateConfigurerFactory(new FileNameGlobMatcher("*.*h"), tcHtml),
                        new ConditionalTemplateConfigurerFactory(new FileNameGlobMatcher("*.*x"), tcXml))
                        .allowNoMatch(true),
                new ConditionalTemplateConfigurerFactory(new FileNameGlobMatcher("*.nws.*"), tcNWS));
        tcf.setConfiguration(cfg);
        
        assertNotApplicable(tcf, "x.ftl");
        assertApplicable(tcf, "b/x.ftl", tcBCommon);
        assertApplicable(tcf, "b/x.s.ftl", tcBCommon, tcBSpec);
        assertApplicable(tcf, "b/x.s.ftlh", tcBCommon, tcBSpec, tcHtml);
        assertApplicable(tcf, "b/x.s.nws.ftlx", tcBCommon, tcBSpec, tcXml, tcNWS);
        assertApplicable(tcf, "a/x.s.nws.ftlx", tcA, tcXml, tcNWS);
        assertApplicable(tcf, "a.hh", tcHH);
        assertApplicable(tcf, "a.nws.hh", tcHH, tcNWS);
    }

    @Test
    public void testSetConfiguration() {
        TemplateConfigurer tc = new TemplateConfigurer();
        ConditionalTemplateConfigurerFactory tcf = new ConditionalTemplateConfigurerFactory(new FileNameGlobMatcher("*"), tc);
        assertNull(tcf.getConfiguration());
        assertNull(tc.getParentConfiguration());
        
        tcf.setConfiguration(cfg);
        assertEquals(cfg, tcf.getConfiguration());
        assertEquals(cfg, tc.getParentConfiguration());
        
        // Ignored:
        tcf.setConfiguration(cfg);
        
        try {
            tcf.setConfiguration(Configuration.getDefaultConfiguration());
            fail();
        } catch (IllegalStateException e) {
            assertThat(e.getMessage(), containsString("TemplateConfigurerFactory"));
        }
    }

    @SuppressWarnings("boxing")
    private TemplateConfigurer newTemplateConfigurer(int id) {
        TemplateConfigurer tc = new TemplateConfigurer();
        tc.setCustomAttribute("id", id);
        tc.setCustomAttribute("contains" + id, true);
        return tc;
    }

    private void assertNotApplicable(TemplateConfigurerFactory tcf, String sourceName)
            throws IOException, TemplateConfigurerFactoryException {
        assertNull(tcf.get(sourceName, "dummy"));
    }

    private void assertApplicable(TemplateConfigurerFactory tcf, String sourceName, TemplateConfigurer... expectedTCs)
            throws IOException, TemplateConfigurerFactoryException {
        TemplateConfigurer mergedTC = tcf.get(sourceName, "dummy");
        assertNotNull("TC should have its parents Configuration set", mergedTC.getParentConfiguration());
        List<String> mergedTCAttNames = Arrays.asList(mergedTC.getCustomAttributeNames());

        for (TemplateConfigurer expectedTC : expectedTCs) {
            Integer tcId = (Integer) expectedTC.getCustomAttribute("id");
            if (tcId == null) {
                fail("TemplateConfigurer-s must be created with newTemplateConfigurer(id) in this test");
            }
            if (!mergedTCAttNames.contains("contains" + tcId)) {
                fail("TemplateConfigurer with ID " + tcId + " is missing from the asserted value");
            }
        }
        
        for (String attName: mergedTCAttNames) {
            if (!containsCustomAttr(attName, expectedTCs)) {
                fail("The asserted TemplateConfigurer contains an unexpected custom attribute: " + attName);
            }
        }
        
        assertEquals(expectedTCs[expectedTCs.length - 1].getCustomAttribute("id"), mergedTC.getCustomAttribute("id"));
    }

    private boolean containsCustomAttr(String attName, TemplateConfigurer... expectedTCs) {
        for (TemplateConfigurer expectedTC : expectedTCs) {
            if (expectedTC.getCustomAttribute(attName) != null) {
                return true;
            }
        }
        return false;
    }
    
}
