/*
 *  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 groovy;

import groovy.lang.Closure;
import groovy.lang.Reference;
import junit.framework.TestCase;
import org.codehaus.groovy.runtime.typehandling.DefaultTypeTransformation;

import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import static org.codehaus.groovy.runtime.DefaultGroovyMethods.any;
import static org.codehaus.groovy.runtime.DefaultGroovyMethods.collect;
import static org.codehaus.groovy.runtime.DefaultGroovyMethods.each;
import static org.codehaus.groovy.runtime.DefaultGroovyMethods.every;
import static org.codehaus.groovy.runtime.DefaultGroovyMethods.findAll;
import static org.codehaus.groovy.runtime.DefaultGroovyMethods.inject;
import static org.codehaus.groovy.runtime.DefaultGroovyMethods.join;
import static org.codehaus.groovy.runtime.DefaultGroovyMethods.max;
import static org.codehaus.groovy.runtime.DefaultGroovyMethods.sort;

/**
 * Groovy's Closure class isn't specifically designed with Java integration in
 * mind, but these tests illustrate some of the possible ways to use them from Java.
 */
public class ClosureJavaIntegrationTest extends TestCase {
    Map<String, Integer> zoo = new LinkedHashMap<>();
    List<String> animals = Arrays.asList("ant", "bear", "camel");

    @Override
    protected void setUp() throws Exception {
        super.setUp();
        zoo.put("Monkeys", 3);
        zoo.put("Giraffe", 2);
        zoo.put("Lions", 5);
    }

    public void testJoinListNonClosureCase() {
        assertEquals(join((Iterable)animals, ", "), "ant, bear, camel");
    }

    public void testEachList() {
        final List<Integer> result = new ArrayList<>();
        each(animals, new Closure(null) {
            public void doCall(String arg) {
                result.add(arg.length());
            }
        });
        assertEquals(Arrays.asList(3, 4, 5), result);
    }

    public void testEachMap() {
        final List<String> result = new ArrayList<>();
        each(zoo, new Closure(null) {
            public void doCall(String k, Integer v) {
                result.add("k=" + k + ",v=" + v);
            }
        });
        assertEquals(Arrays.asList("k=Monkeys,v=3", "k=Giraffe,v=2", "k=Lions,v=5" ), result);
    }

    public void testCollectList() {
        assertEquals(Arrays.asList(3, 4, 5), collect(animals, new Closure<Integer>(null) {
            public Integer doCall(String it) {
                return it.length();
            }
        }));
    }

    public void testMaxMap() {
        Map.Entry<String, Integer> lionEntry = null;
        for (Map.Entry<String, Integer> entry : zoo.entrySet()) {
            if (entry.getKey().equals("Lions")) lionEntry = entry;
        }
        assertEquals(lionEntry, max(zoo.entrySet(), new Closure<Integer>(null) {
            public Integer doCall(Map.Entry<String, Integer> e) {
                return e.getKey().length() * e.getValue();
            }
        }));
    }

    public void testSortMapKeys() {
        assertEquals(Arrays.asList("Monkeys", "Lions", "Giraffe"), sort(zoo.keySet(), new Closure<Integer>(null) {
            public Integer doCall(String a, String b) {
                return -a.compareTo(b);
            }
        }));
        assertEquals(Arrays.asList("Giraffe", "Lions", "Monkeys"), sort(zoo.keySet(), new Closure<Integer>(null) {
            public Integer doCall(String a, String b) {
                return a.compareTo(b);
            }
        }));
    }

    public void testAnyMap() {
        assertTrue(any(zoo, new Closure<Boolean>(null) {
            public Boolean doCall(String k, Integer v) {
                return k.equals("Lions") && v == 5;
            }
        }));
    }

    public void testFindAllAndCurry() {
        Map<String, Integer> expected = new HashMap<>(zoo);
        expected.remove("Lions");
        Closure<Boolean> keyBiggerThan = new Closure<Boolean>(null) {
            public Boolean doCall(Map.Entry<String, Integer> e, Integer size) {
                return e.getKey().length() > size;
            }
        };
        Closure<Boolean> keyBiggerThan6 = keyBiggerThan.rcurry(6);
        assertEquals(expected, findAll(zoo, keyBiggerThan6));
    }

    public void testListArithmetic() {
        List<List> numLists = new ArrayList<>();
        numLists.add(Arrays.asList(1, 2, 3));
        numLists.add(Arrays.asList(10, 20, 30));
        assertEquals(Arrays.asList(6, 60), collect(numLists, new Closure<Integer>(null) {
            public Integer doCall(Integer a, Integer b, Integer c) {
                return a + b + c;
            }
        }));
        Closure<Integer> arithmeticClosure = new Closure<Integer>(null) {
            public Integer doCall(Integer a, Integer b, Integer c) {
                return a * b + c;
            }
        };
        Closure<Integer> tensAndUnits = arithmeticClosure.curry(10);
        assertEquals(35, (int) tensAndUnits.call(3, 5));
        tensAndUnits = arithmeticClosure.ncurry(0, 10);
        assertEquals(35, (int) tensAndUnits.call(3, 5));
        tensAndUnits = arithmeticClosure.ncurry(1, 10);
        assertEquals(35, (int) tensAndUnits.call(3, 5));
        Closure<Integer> timesPlus5 = arithmeticClosure.rcurry(5);
        assertEquals(35, (int) timesPlus5.call(15, 2));
        timesPlus5 = arithmeticClosure.ncurry(2, 5);
        assertEquals(35, (int) timesPlus5.call(15, 2));
    }

    public void testComposition() {
        Closure<String> toUpperCase = new Closure<String>(null) {
            public String doCall(String s) {
                return s.toUpperCase();
            }
        };
        Closure<Boolean> hasCapitalA = new Closure<Boolean>(null) {
            public Boolean doCall(String s) {
                return s.contains("A");
            }
        };
        Closure<Boolean> hasA = toUpperCase.rightShift(hasCapitalA);
        assertTrue(every(animals, hasA));
        Closure<Boolean> alsoHasA = hasCapitalA.leftShift(toUpperCase);
        assertTrue(every(animals, alsoHasA));
    }

    public void testTrampoline() {
        final Reference<Closure<BigInteger>> ref = new Reference<>();
        ref.set(new Closure<BigInteger>(null) {
            public Object doCall(Integer n, BigInteger total) {
                return n > 1 ? ref.get().trampoline(n - 1, total.multiply(BigInteger.valueOf(n))) : total;
            }
        }.trampoline());
        Closure<BigInteger> factorial = new Closure<BigInteger>(null) {
            public BigInteger doCall(Integer n) {
                return ref.get().call(n, BigInteger.ONE);
            }
        };
        assertEquals(BigInteger.valueOf(479001600), factorial.call(12));
    }

    public void testInject() {
        Collection<Integer> c = Arrays.asList(2, 4, 5, 20);
        Number initial = BigDecimal.ZERO;
        Closure<? extends Number> closure = new Closure<BigDecimal>(c) {
            BigDecimal doCall(BigDecimal total, Integer next) {
                return total.add(BigDecimal.ONE.divide(new BigDecimal(next)));
            }
        };
        assertTrue(DefaultTypeTransformation.compareEqual(BigDecimal.ONE, inject(c, initial, closure)));
    }
}
