/*
 * Copyright 2002-2017 the original author or authors.
 *
 * Licensed 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 org.springframework.expression.spel;

import org.junit.Test;

import org.springframework.expression.EvaluationContext;
import org.springframework.expression.EvaluationException;
import org.springframework.expression.Expression;
import org.springframework.expression.ParseException;
import org.springframework.expression.ParserContext;
import org.springframework.expression.common.CompositeStringExpression;
import org.springframework.expression.common.TemplateParserContext;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;

import static org.junit.Assert.*;

/**
 * @author Andy Clement
 * @author Juergen Hoeller
 */
public class TemplateExpressionParsingTests extends AbstractExpressionTests {

	public static final ParserContext DEFAULT_TEMPLATE_PARSER_CONTEXT = new ParserContext() {
		@Override
		public String getExpressionPrefix() {
			return "${";
		}
		@Override
		public String getExpressionSuffix() {
			return "}";
		}
		@Override
		public boolean isTemplate() {
			return true;
		}
	};

	public static final ParserContext HASH_DELIMITED_PARSER_CONTEXT = new ParserContext() {
		@Override
		public String getExpressionPrefix() {
			return "#{";
		}
		@Override
		public String getExpressionSuffix() {
			return "}";
		}
		@Override
		public boolean isTemplate() {
			return true;
		}
	};


	@Test
	public void testParsingSimpleTemplateExpression01() throws Exception {
		SpelExpressionParser parser = new SpelExpressionParser();
		Expression expr = parser.parseExpression("hello ${'world'}", DEFAULT_TEMPLATE_PARSER_CONTEXT);
		Object o = expr.getValue();
		assertEquals("hello world", o.toString());
	}

	@Test
	public void testParsingSimpleTemplateExpression02() throws Exception {
		SpelExpressionParser parser = new SpelExpressionParser();
		Expression expr = parser.parseExpression("hello ${'to'} you", DEFAULT_TEMPLATE_PARSER_CONTEXT);
		Object o = expr.getValue();
		assertEquals("hello to you", o.toString());
	}

	@Test
	public void testParsingSimpleTemplateExpression03() throws Exception {
		SpelExpressionParser parser = new SpelExpressionParser();
		Expression expr = parser.parseExpression("The quick ${'brown'} fox jumped over the ${'lazy'} dog",
				DEFAULT_TEMPLATE_PARSER_CONTEXT);
		Object o = expr.getValue();
		assertEquals("The quick brown fox jumped over the lazy dog", o.toString());
	}

	@Test
	public void testParsingSimpleTemplateExpression04() throws Exception {
		SpelExpressionParser parser = new SpelExpressionParser();
		Expression expr = parser.parseExpression("${'hello'} world", DEFAULT_TEMPLATE_PARSER_CONTEXT);
		Object o = expr.getValue();
		assertEquals("hello world", o.toString());

		expr = parser.parseExpression("", DEFAULT_TEMPLATE_PARSER_CONTEXT);
		o = expr.getValue();
		assertEquals("", o.toString());

		expr = parser.parseExpression("abc", DEFAULT_TEMPLATE_PARSER_CONTEXT);
		o = expr.getValue();
		assertEquals("abc", o.toString());

		expr = parser.parseExpression("abc", DEFAULT_TEMPLATE_PARSER_CONTEXT);
		o = expr.getValue((Object)null);
		assertEquals("abc", o.toString());
	}

	@Test
	public void testCompositeStringExpression() throws Exception {
		SpelExpressionParser parser = new SpelExpressionParser();
		Expression ex = parser.parseExpression("hello ${'world'}", DEFAULT_TEMPLATE_PARSER_CONTEXT);
		checkString("hello world", ex.getValue());
		checkString("hello world", ex.getValue(String.class));
		checkString("hello world", ex.getValue((Object)null, String.class));
		checkString("hello world", ex.getValue(new Rooty()));
		checkString("hello world", ex.getValue(new Rooty(), String.class));

		EvaluationContext ctx = new StandardEvaluationContext();
		checkString("hello world", ex.getValue(ctx));
		checkString("hello world", ex.getValue(ctx, String.class));
		checkString("hello world", ex.getValue(ctx, null, String.class));
		checkString("hello world", ex.getValue(ctx, new Rooty()));
		checkString("hello world", ex.getValue(ctx, new Rooty(), String.class));
		checkString("hello world", ex.getValue(ctx, new Rooty(), String.class));
		assertEquals("hello ${'world'}", ex.getExpressionString());
		assertFalse(ex.isWritable(new StandardEvaluationContext()));
		assertFalse(ex.isWritable(new Rooty()));
		assertFalse(ex.isWritable(new StandardEvaluationContext(), new Rooty()));

		assertEquals(String.class,ex.getValueType());
		assertEquals(String.class,ex.getValueType(ctx));
		assertEquals(String.class,ex.getValueTypeDescriptor().getType());
		assertEquals(String.class,ex.getValueTypeDescriptor(ctx).getType());
		assertEquals(String.class,ex.getValueType(new Rooty()));
		assertEquals(String.class,ex.getValueType(ctx, new Rooty()));
		assertEquals(String.class,ex.getValueTypeDescriptor(new Rooty()).getType());
		assertEquals(String.class,ex.getValueTypeDescriptor(ctx, new Rooty()).getType());

		try {
			ex.setValue(ctx, null);
			fail();
		}
		catch (EvaluationException ee) {
			// success
		}
		try {
			ex.setValue((Object)null, null);
			fail();
		}
		catch (EvaluationException ee) {
			// success
		}
		try {
			ex.setValue(ctx, null, null);
			fail();
		}
		catch (EvaluationException ee) {
			// success
		}
	}

	static class Rooty {}

	@Test
	public void testNestedExpressions() throws Exception {
		SpelExpressionParser parser = new SpelExpressionParser();
		// treat the nested ${..} as a part of the expression
		Expression ex = parser.parseExpression("hello ${listOfNumbersUpToTen.$[#this<5]} world",DEFAULT_TEMPLATE_PARSER_CONTEXT);
		String s = ex.getValue(TestScenarioCreator.getTestEvaluationContext(),String.class);
		assertEquals("hello 4 world",s);

		// not a useful expression but tests nested expression syntax that clashes with template prefix/suffix
		ex = parser.parseExpression("hello ${listOfNumbersUpToTen.$[#root.listOfNumbersUpToTen.$[#this%2==1]==3]} world",DEFAULT_TEMPLATE_PARSER_CONTEXT);
		assertEquals(CompositeStringExpression.class,ex.getClass());
		CompositeStringExpression cse = (CompositeStringExpression)ex;
		Expression[] exprs = cse.getExpressions();
		assertEquals(3,exprs.length);
		assertEquals("listOfNumbersUpToTen.$[#root.listOfNumbersUpToTen.$[#this%2==1]==3]",exprs[1].getExpressionString());
		s = ex.getValue(TestScenarioCreator.getTestEvaluationContext(),String.class);
		assertEquals("hello  world",s);

		ex = parser.parseExpression("hello ${listOfNumbersUpToTen.$[#this<5]} ${listOfNumbersUpToTen.$[#this>5]} world",DEFAULT_TEMPLATE_PARSER_CONTEXT);
		s = ex.getValue(TestScenarioCreator.getTestEvaluationContext(),String.class);
		assertEquals("hello 4 10 world",s);

		try {
			ex = parser.parseExpression("hello ${listOfNumbersUpToTen.$[#this<5]} ${listOfNumbersUpToTen.$[#this>5] world",DEFAULT_TEMPLATE_PARSER_CONTEXT);
			fail("Should have failed");
		}
		catch (ParseException pe) {
			assertEquals("No ending suffix '}' for expression starting at character 41: ${listOfNumbersUpToTen.$[#this>5] world", pe.getSimpleMessage());
		}

		try {
			ex = parser.parseExpression("hello ${listOfNumbersUpToTen.$[#root.listOfNumbersUpToTen.$[#this%2==1==3]} world",DEFAULT_TEMPLATE_PARSER_CONTEXT);
			fail("Should have failed");
		}
		catch (ParseException pe) {
			assertEquals("Found closing '}' at position 74 but most recent opening is '[' at position 30", pe.getSimpleMessage());
		}
	}

	@Test

	public void testClashingWithSuffixes() throws Exception {
		// Just wanting to use the prefix or suffix within the template:
		Expression ex = parser.parseExpression("hello ${3+4} world",DEFAULT_TEMPLATE_PARSER_CONTEXT);
		String s = ex.getValue(TestScenarioCreator.getTestEvaluationContext(),String.class);
		assertEquals("hello 7 world", s);

		ex = parser.parseExpression("hello ${3+4} wo${'${'}rld",DEFAULT_TEMPLATE_PARSER_CONTEXT);
		s = ex.getValue(TestScenarioCreator.getTestEvaluationContext(),String.class);
		assertEquals("hello 7 wo${rld", s);

		ex = parser.parseExpression("hello ${3+4} wo}rld",DEFAULT_TEMPLATE_PARSER_CONTEXT);
		s = ex.getValue(TestScenarioCreator.getTestEvaluationContext(),String.class);
		assertEquals("hello 7 wo}rld", s);
	}

	@Test
	public void testParsingNormalExpressionThroughTemplateParser() throws Exception {
		Expression expr = parser.parseExpression("1+2+3");
		assertEquals(6, expr.getValue());
	}

	@Test
	public void testErrorCases() throws Exception {
		try {
			parser.parseExpression("hello ${'world'", DEFAULT_TEMPLATE_PARSER_CONTEXT);
			fail("Should have failed");
		}
		catch (ParseException pe) {
			assertEquals("No ending suffix '}' for expression starting at character 6: ${'world'", pe.getSimpleMessage());
			assertEquals("hello ${'world'", pe.getExpressionString());
		}
		try {
			parser.parseExpression("hello ${'wibble'${'world'}", DEFAULT_TEMPLATE_PARSER_CONTEXT);
			fail("Should have failed");
		}
		catch (ParseException pe) {
			assertEquals("No ending suffix '}' for expression starting at character 6: ${'wibble'${'world'}", pe.getSimpleMessage());
		}
		try {
			parser.parseExpression("hello ${} world", DEFAULT_TEMPLATE_PARSER_CONTEXT);
			fail("Should have failed");
		}
		catch (ParseException pe) {
			assertEquals("No expression defined within delimiter '${}' at character 6", pe.getSimpleMessage());
		}
	}

	@Test
	public void testTemplateParserContext() {
		TemplateParserContext tpc = new TemplateParserContext("abc","def");
		assertEquals("abc", tpc.getExpressionPrefix());
		assertEquals("def", tpc.getExpressionSuffix());
		assertTrue(tpc.isTemplate());

		tpc = new TemplateParserContext();
		assertEquals("#{", tpc.getExpressionPrefix());
		assertEquals("}", tpc.getExpressionSuffix());
		assertTrue(tpc.isTemplate());

		ParserContext pc = ParserContext.TEMPLATE_EXPRESSION;
		assertEquals("#{", pc.getExpressionPrefix());
		assertEquals("}", pc.getExpressionSuffix());
		assertTrue(pc.isTemplate());
	}

	// ---

	private void checkString(String expectedString, Object value) {
		if (!(value instanceof String)) {
			fail("Result was not a string, it was of type " + value.getClass() + "  (value=" + value + ")");
		}
		if (!value.equals(expectedString)) {
			fail("Did not get expected result.  Should have been '" + expectedString + "' but was '" + value + "'");
		}
	}

}