/*
 * Licensed to Elasticsearch under one or more contributor
 * license agreements. See the NOTICE file distributed with
 * this work for additional information regarding copyright
 * ownership. Elasticsearch 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 org.elasticsearch.painless;

import static org.hamcrest.Matchers.containsString;

public class FunctionTests extends ScriptTestCase {
    public void testBasic() {
        assertEquals(5, exec("int get() {5;} get()"));
    }

    public void testReference() {
        assertEquals(5, exec("void get(int[] x) {x[0] = 5;} int[] y = new int[1]; y[0] = 1; get(y); y[0]"));
    }

    public void testConcat() {
        assertEquals("xyxy", exec("String catcat(String single) {single + single;} catcat('xy')"));
    }

    public void testMultiArgs() {
        assertEquals(5, exec("int add(int x, int y) {return x + y;} int x = 1, y = 2; add(add(x, x), add(x, y))"));
    }

    public void testMultiFuncs() {
        assertEquals(1, exec("int add(int x, int y) {return x + y;} int sub(int x, int y) {return x - y;} add(2, sub(3, 4))"));
        assertEquals(3, exec("int sub2(int x, int y) {sub(x, y) - y;} int sub(int x, int y) {return x - y;} sub2(5, 1)"));
    }

    public void testRecursion() {
        assertEquals(55, exec("int fib(int n) {if (n <= 1) return n; else return fib(n-1) + fib(n-2);} fib(10)"));
    }

    public void testEmpty() {
        Exception expected = expectScriptThrows(IllegalArgumentException.class, () -> {
            exec("void test(int x) {} test()");
        });
        assertThat(expected.getMessage(), containsString("Cannot generate an empty function"));
    }

    public void testReturnsAreUnboxedIfNeeded() {
        assertEquals((byte) 5, exec("byte get() {Byte.valueOf(5)} get()"));
        assertEquals((short) 5, exec("short get() {Byte.valueOf(5)} get()"));
        assertEquals(5, exec("int get() {Byte.valueOf(5)} get()"));
        assertEquals((short) 5, exec("short get() {Short.valueOf(5)} get()"));
        assertEquals(5, exec("int get() {Integer.valueOf(5)} get()"));
        assertEquals(5.0f, exec("float get() {Float.valueOf(5)} get()"));
        assertEquals(5.0d, exec("double get() {Float.valueOf(5)} get()"));
        assertEquals(5.0d, exec("double get() {Double.valueOf(5)} get()"));
        assertEquals(true, exec("boolean get() {Boolean.TRUE} get()"));
    }

    public void testDuplicates() {
        Exception expected = expectScriptThrows(IllegalArgumentException.class, () -> {
            exec("void test(int x) {x = 2;} void test(def y) {y = 3;} test()");
        });
        assertThat(expected.getMessage(), containsString("Duplicate functions"));
    }

    public void testBadCastFromMethod() {
        Exception e = expectScriptThrows(ClassCastException.class, () -> exec("int get() {5L} get()"));
        assertEquals("Cannot cast from [long] to [int].", e.getMessage());
        e = expectScriptThrows(ClassCastException.class, () -> exec("int get() {5.1f} get()"));
        assertEquals("Cannot cast from [float] to [int].", e.getMessage());
        e = expectScriptThrows(ClassCastException.class, () -> exec("int get() {5.1d} get()"));
        assertEquals("Cannot cast from [double] to [int].", e.getMessage());
    }

    public void testInfiniteLoop() {
        Error expected = expectScriptThrows(PainlessError.class, () -> {
            exec("void test() {boolean x = true; while (x) {}} test()");
        });
        assertThat(expected.getMessage(),
                containsString("The maximum number of statements that can be executed in a loop has been reached."));
    }

    public void testReturnVoid() {
        assertEquals(null, exec("void test(StringBuilder b, int i) {b.setLength(i)} test(new StringBuilder(), 1)"));
        Exception expected = expectScriptThrows(IllegalArgumentException.class, () -> {
            exec("int test(StringBuilder b, int i) {b.setLength(i)} test(new StringBuilder(), 1)");
        });
        assertEquals("Not all paths provide a return value for method [test].", expected.getMessage());
        expected = expectScriptThrows(ClassCastException.class, () -> {
            exec("int test(StringBuilder b, int i) {return b.setLength(i)} test(new StringBuilder(), 1)");
        });
        assertEquals("Cannot cast from [void] to [int].", expected.getMessage());
        expected = expectScriptThrows(ClassCastException.class, () -> {
            exec("def test(StringBuilder b, int i) {return b.setLength(i)} test(new StringBuilder(), 1)");
        });
        assertEquals("Cannot cast from [void] to [def].", expected.getMessage());
    }
}
