package org.apache.velocity.test;

/*
 * 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.
 */

import org.apache.velocity.app.VelocityEngine;
import org.apache.velocity.runtime.RuntimeConstants;

import java.util.HashMap;

/**
 * This class tests the directive scope controls
 */
public class ScopeTestCase extends BaseTestCase
{
    public ScopeTestCase(String name)
    {
       super(name);
    }

    @Override
    protected void setUpEngine(VelocityEngine engine)
    {
        engine.setProperty("a.provide.scope.control", "true");
        engine.setProperty("define.provide.scope.control", "true");
        engine.setProperty("evaluate.provide.scope.control", "true");
        engine.setProperty("foo.provide.scope.control", "true");
        engine.setProperty("macro.provide.scope.control", "true");
        engine.setProperty("template.provide.scope.control", "true");
        engine.setProperty("vm.provide.scope.control", "true");
        engine.setProperty("space.gobbling", "bc");
    }

    public void testScopeGetLeakIntoInner()
    {
        addTemplate("foo", "#foreach($i in [1..1])#set($foreach.a=$i)"+
                           "#foreach($j in [2..2])$foreach.a#set($foreach.a=$j)"+
                           "#foreach($k in [3..3])$foreach.a#end#end$foreach.a#end");
        assertTmplEquals("121", "foo");
    }

    public void testScopeGetLeakDoesntHideNullset()
    {
        addTemplate("a", "#macro(a)#set($macro.a='a')#b()$macro.a#end"+
                         "#macro(b)$macro.a#set($macro.a=$null)$!macro.a#end"+
                         "#a()");
        assertTmplEquals("aa", "a");
    }

    public void testRootTemplateMergeScope()
    {
        addTemplate("foo", "foo#break($template)bar");
        assertTmplEquals("foo", "foo");
        assertNull(context.get("template"));
    }

    public void testParseScope()
    {
        addTemplate("test", "$template.info.depth"+
                            "$!parse.parent.info.depth"+
                            "#set( $template.foo = 'bar' )"+
                            "$template.foo"+
                            "#break($template)"+
                            "woogie");
        assertEvalEquals("1bar", "#parse( 'test' )");
        assertNull(context.get("template"));
    }

    public void testNestedParseScope()
    {
        HashMap grab = new HashMap();
        context.put("grab", grab);

        addTemplate("inner", "Inner depth: $template.info.depth"+
                             "#set( $template.foo = '?' )"+
                             "$!grab.put('inner',$template)"+
                             "#break($template)$template.foo");
        addTemplate("outer", "#set( $template.foo = '!' )"+
                             "Outer depth: $template.info.depth "+
                             "#parse('inner')"+
                             "$!grab.put('outer', $template)"+
                             "$template.foo");
        assertEvalEquals("Outer depth: 1 Inner depth: 2!", "#parse('outer')");
        // make extra sure that the outer control was restored after the stop
        assertFalse(grab.get("inner") == grab.get("outer"));
        // make sure the outer control was cleaned up
        assertNull(context.get("template"));

        addTemplate("3", "$template.topmost.foo#set( $template.topmost.foo = 'bar' )");
        addTemplate("2", "#parse( '3' )$!parse.foo");
        addTemplate("1", "#set( $template.foo = 'foo' )#parse('2')$template.foo");
        assertEvalEquals("foobar", "#parse('1')$!parse");
        // make sure the top control was cleaned up
        assertNull(context.get("template"));
    }

    public void testForeachScope()
    {
        String template = "#foreach( $i in [0..2] )"+
                          "#if( $i > 1 )#break($foreach)#end"+
                          "$foreach.index:$foreach.count:$foreach.hasNext,"+
                          "#end";
        assertEvalEquals("0:1:true,1:2:true,", template);
        assertNull(context.get("foreach"));
    }

    public void testNestedForeachScope()
    {
        String template = "#foreach( $i in [1..5] )"+
                            "#foreach( $j in [1..2] )"+
                              "#if ( $i > $foreach.count + $foreach.index + $foreach.info.depth )#break($foreach.topmost)#end"+
                            "#end"+
                            "$i"+
                          "#end";
        assertEvalEquals("123", template);
        assertNull(context.get("foreach"));
    }

    public void testMacroScope()
    {
        String template = "#macro( foo $i )"+
                          "#if($i > 2 )#break($macro)#end"+
                          "$i#end"+
                          "#foo( 0 )#foo( 1 )#foo( 2 )";
        assertEvalEquals("012", template);
        assertNull(context.get("macro"));
    }

    public void testRecursiveMacroScope()
    {
        String template = "#macro( foo )$macro.info.depth"+
                          "#if($macro.info.depth > 2 )#break($macro.topmost)#end"+
                          "#foo()#end#foo()";
        assertEvalEquals("123", template);
        assertNull(context.get("macro"));
    }

    public void testNestedMacroScope()
    {
        String template = "#macro( a )$macro.info.depth#set($macro.c = 'a')$macro.c#end"+
                          "#macro( b )#set($macro.c = 'b' )#a()$macro.c#end"+
                          "#b()";
        assertEvalEquals("2ab", template);
        assertNull(context.get("macro"));
    }

    public void testBodyMacroScope()
    {
        String template = "#macro( foo $bar )$bodyContent$macro.bar#end"+
                          "#@foo( 'bar' )#set( $macro.bar = 'foo'+$bar )"+
                          "#set( $foo.d = $foo.info.depth )$foo.d #end";
        assertEvalEquals("1 foobar", template);
        assertNull(context.get("foo"));
        assertNull(context.get("macro"));
    }

    public void testRecursiveBodyMacroScope()
    {
        engine.setProperty(RuntimeConstants.VM_MAX_DEPTH, "5");
        String template = "#macro( foo )$bodyContent$macro.i#end"+
                          "#@foo()#set( $macro.i = \"$!macro.i$foo.info.depth,\" )"+
                          "$!bodyContent#end";
        assertEvalEquals("1,2,3,4,5,", template);
        assertNull(context.get("foo"));
        assertNull(context.get("macro"));
    }

    public void testDefineScope()
    {
        String template = "#define( $foo )#set( $define.bar = 'bar'+$define.info.depth )$define.bar#end$foo";
        assertEvalEquals("bar1", template);
        assertNull(context.get("define"));
    }

    public void testNestedDefineScope()
    {
        String template = "#define($a)$b c#end"+
                          "#define($b)$define.info.depth#break($define.topmost)#end"+
                          "$a";
        assertEvalEquals("2", template);
        assertNull(context.get("define"));
    }

    public void testRecursiveDefineScope()
    {
        engine.setProperty(RuntimeConstants.DEFINE_DIRECTIVE_MAXDEPTH, "10");
        String template = "#define($a)$define.info.depth"+
                          "#if($define.info.depth == 5)#break($define)#end,$a#end$a";
        assertEvalEquals("1,2,3,4,5", template);
        assertNull(context.get("define"));
    }

    public void testRootEvaluateScope()
    {
        assertEvalEquals("1", "$evaluate.info.depth");
        assertEvalEquals("foo", "foo#break($evaluate)bar");
        assertNull(context.get("evaluate"));
    }

    public void testEvaluateScope()
    {
        context.put("h", "#");
        context.put("d", "$");
        String template = "${h}set( ${d}evaluate.foo = 'bar' )"+
                          "${d}evaluate.foo ${d}evaluate.info.depth";
        addTemplate("eval", "#evaluate(\""+template+"\")");
        assertTmplEquals("bar 1", "eval");
        assertNull(context.get("evaluate"));
        assertNull(context.get("template"));
    }

    public void testNestedEvaluateScope()
    {
        context.put("h", "#");
        context.put("d", "$");
        addTemplate("e", "#evaluate(\"${h}evaluate( '${d}evaluate.info.depth${h}stop(${d}evaluate) blah' )\")");
        assertTmplEquals("2", "e");
        assertNull(context.get("evaluate"));
        assertNull(context.get("template"));
    }

    public void testTurningOffTemplateScope()
    {
        engine.setProperty("template."+RuntimeConstants.PROVIDE_SCOPE_CONTROL, "false");
        // root
        addTemplate("test", "$template.info.depth");
        assertTmplEquals("$template.info.depth", "test");
        // #parse
        assertEvalEquals("$template.info.depth", "#parse('test')");
    }

    public void testTurningOffEvaluateScope()
    {
        engine.setProperty("evaluate."+RuntimeConstants.PROVIDE_SCOPE_CONTROL, "false");
        // root
        assertSchmoo("$evaluate.info.depth");
        // #evaluate
        assertEvalEquals("$evaluate.info.depth", "#evaluate( '$evaluate.info.depth' )");
    }

    public void testTurningOffMacroScope()
    {
        engine.setProperty("macro."+RuntimeConstants.PROVIDE_SCOPE_CONTROL, "false");
        engine.setProperty("foo."+RuntimeConstants.PROVIDE_SCOPE_CONTROL, "false");
        // macro definition
        assertEvalEquals("$macro", "#macro(a)$macro#end#a()");
        // macro body
        assertEvalEquals("$macro $foo", "#macro(foo)$bodyContent#end#@foo()$macro $foo#end");
    }

    public void testTurningOffDefineScope()
    {
        engine.setProperty("define."+RuntimeConstants.PROVIDE_SCOPE_CONTROL, "false");
        assertEvalEquals("$define", "#define($a)$define#end$a");
    }

    public void testTurningOffForeachScope()
    {
        engine.setProperty("foreach."+RuntimeConstants.PROVIDE_SCOPE_CONTROL, "false");
        assertEvalEquals("$foreach$foreach", "#foreach($i in [0..1])$foreach#end");
    }

    public void testTemplateReplaced()
    {
        context.put("template", "foo");
        addTemplate("test", "$template.replaced");
        assertTmplEquals("foo", "test");
        assertEvalEquals("foo", "#parse('test')");
        assertContextValue("template", "foo");
    }

    public void testEvaluateReplaced()
    {
        context.put("evaluate","foo");
        assertEvalEquals("foo", "$evaluate.replaced");
        assertEvalEquals("foo", "#evaluate('$evaluate.replaced')");
        assertContextValue("evaluate", "foo");
    }

    public void testMacroReplaced()
    {
        context.put("macro", "foo");
        assertEvalEquals("foo foo foo", "$macro #macro(a)$macro.replaced#end#a() $macro");
        assertContextValue("macro", "foo");
    }

    public void testForeachReplaced()
    {
        context.put("foreach", "foo");
        assertEvalEquals("foofoofoo", "$foreach#foreach($i in [1..1])$foreach.replaced#end$foreach");
        assertEquals("foo", context.get("foreach"));
        context.put("foreach", "a");
        assertEvalEquals("a", "#foreach($i in [1..1])#foreach($j in [1..1])$foreach.replaced#end#end");
        assertContextValue("foreach", "a");
    }

    public void testDefineReplaced()
    {
        context.put("define", "a");
        assertEvalEquals("a", "#define($a)$define.replaced#end$a");
        assertContextValue("define", "a");
    }

    public void testBodyContentReplaced()
    {
        context.put("vm", "a");
        assertEvalEquals("a", "#macro(vm)$bodyContent#end#@vm()$vm.replaced#end");
        assertContextValue("vm", "a");
    }

    public void testInfoDepth()
    {
        String template = "#foreach($i in [1..1])"+
                            "#foreach($j in [0..0])"+
                                "$foreach.info.depth"+
                            "#end"+
                          "#end";
        assertEvalEquals("2", template);
    }

    public void testInfoName()
    {
        String template = "#foreach($i in [1..1])"+
                            "$foreach.info.name #evaluate('$evaluate.info.name')"+
                          "#end";
        assertEvalEquals("foreach evaluate", template);
    }

    public void testInfoType()
    {
        addTemplate("info", "#foreach($i in [1..1])"+
                                "$foreach.info.type"+
                            "#end "+
                            "#evaluate('$evaluate.info.type') "+
                            "$template.info.type");
        assertTmplEquals("block line utf-8", "info");
    }

    public void testInfoLineAndColumn()
    {
        String template = " #evaluate('$evaluate.info.line, $evaluate.info.column')";
        assertEvalEquals(" 1, 2", template);
        assertEvalEquals("\n\n   3, 4", "\n\n  "+template);
    }

    public void testInfoTemplate()
    {
        addTemplate("test", "#evaluate('$evaluate.info.template')");
        assertTmplEquals("test", "test");
        assertEvalEquals("test", "#parse('test')");
    }

}
