// © 2016 and later: Unicode, Inc. and others.
// License & terms of use: http://www.unicode.org/copyright.html
/*
 *******************************************************************************
 * Copyright (C) 2004-2016, International Business Machines Corporation and         *
 * others. All Rights Reserved.                                                *
 *******************************************************************************
 */
package com.ibm.icu.dev.test;

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.Set;
import java.util.TreeSet;

import com.ibm.icu.text.UnicodeSet;

/**
 * To use, override the abstract and the protected methods as necessary.
 * Tests boilerplate invariants:
 * <br>a.equals(a)
 * <br>!a.equals(null)
 * <br>if a.equals(b) then 
 * <br>(1) a.hashCode() == b.hashCode  // note: the reverse is not necessarily true.
 * <br>(2) a functions in all aspects as equivalent to b
 * <br>(3) b.equals(a)
 * <br>if b = clone(a)
 * <br>(1) b.equals(a), and the above checks
 * <br>(2) if mutable(a), then a.clone() != a // note: the reverse is not necessarily true.
 * @author Davis
 */
public abstract class TestBoilerplate<T> extends TestFmwk {

    protected static Random random = new Random(12345);

    protected final void _test() throws Exception {
        List<T> list = new LinkedList<T>();
        while (_addTestObject(list)) {
        }
        T[] testArray = (T[]) list.toArray();
        for (int i = 0; i < testArray.length; ++i) {
            //logln("Testing " + i);
            T a = testArray[i];
            int aHash = a.hashCode();
            if (a.equals(null)) {
                errln("Equality/Null invariant fails: " + i);
            }
            if (!a.equals(a)) {
                errln("Self-Equality invariant fails: " + i);
            }
            T b;                
            if (_canClone(a)) {
                b = _clone(a);
                if (b == a) {
                    if (_isMutable(a)) {
                        errln("Clone/Mutability invariant fails: " + i);
                    }
                } else {
                    if (!a.equals(b)) {
                        errln("Clone/Equality invariant fails: " + i);
                    }
                }
                _checkEquals(i, -1, a, aHash, b);
            }
            for (int j = i; j < testArray.length; ++j) {
                b = testArray[j];
                if (a.equals(b)) _checkEquals(i, j, a, aHash, b);
            }
        }
    }

    private void _checkEquals(int i, int j, T a, int aHash, T b) {
        int bHash = b.hashCode();
        if (!b.equals(a)) errln("Equality/Symmetry",i, j);
        if (aHash != bHash) errln("Equality/Hash",i, j);
        if (a != b && !_hasSameBehavior(a,b)) {
            errln("Equality/Equivalence",i, j);
        }
    }

    private void errln(String title, int i, int j) {
        if (j < 0) errln("Clone/" + title + "invariant fails: " + i);
        else errln(title + "invariant fails: " + i + "," + j);
    }

    /**
     * Must be overridden to check whether a and be behave the same
     */
    protected abstract boolean _hasSameBehavior(T a, T b);

    /**
     * This method will be called multiple times until false is returned.
     * The results should be a mixture of different objects of the same
     * type: some equal and most not equal.
     * The subclasser controls how many are produced (recommend about 
     * 100, based on the size of the objects and how costly they are
     * to run this test on. The running time grows with the square of the
     * count.
     * NOTE: this method will only be called if the objects test as equal.
     */
    protected abstract boolean _addTestObject(List<T> c);
    /**
     * Override if the tested objects are mutable.
     * <br>Since Java doesn't tell us, we need a function to tell if so.
     * The default is true, so must be overridden if not.
     */
    protected boolean _isMutable(T a) {
        return true;
    }
    /**
     * Override if the tested objects can be cloned.
     */
    protected boolean _canClone(T a) {
        return true;
    }
    /**
     * Produce a clone of the object. Tries two methods
     * (a) clone
     * (b) constructor
     * Must be overridden if _canClone returns true and
     * the above methods don't work.
     * @param a
     * @return clone
     */
    protected T _clone(T a) throws Exception {
        Class<?> aClass = a.getClass();
        try {
            Method cloner = aClass.getMethod("clone", (Class[])null);
            return (T) cloner.invoke(a,(Object[])null);
        } catch (NoSuchMethodException e) {
            Constructor<?> constructor = aClass.getConstructor(new Class[] {aClass});
            return (T) constructor.newInstance(new Object[]{a});
        }
    }
    
    /* Utilities */
    public static boolean verifySetsIdentical(AbstractTestLog here, UnicodeSet set1, UnicodeSet set2) {
        if (set1.equals(set2)) return true;
        TestFmwk.errln("Sets differ:");
        TestFmwk.errln("UnicodeMap - HashMap");
        TestFmwk.errln(new UnicodeSet(set1).removeAll(set2).toPattern(true));
        TestFmwk.errln("HashMap - UnicodeMap");
        TestFmwk.errln(new UnicodeSet(set2).removeAll(set1).toPattern(true));
        return false;
    }

    public static <T> boolean verifySetsIdentical(AbstractTestLog here, Set<T> values1, Set<T> values2) {
        if (values1.equals(values2)) return true;
        Set<T> temp;
        TestFmwk.errln("Values differ:");
        TestFmwk.errln("UnicodeMap - HashMap");
        temp = new TreeSet<>(values1);
        temp.removeAll(values2);
        TestFmwk.errln(show(temp));
        TestFmwk.errln("HashMap - UnicodeMap");
        temp = new TreeSet<>(values2);
        temp.removeAll(values1);
        TestFmwk.errln(show(temp));
        return false;
    }

    public static <K, V> String show(Map<K, V> m) {
        StringBuilder buffer = new StringBuilder();
        for (Map.Entry<K, V> e : m.entrySet()) {
            buffer.append(e.getKey() + "=>" + e.getValue() + "\r\n");
        }
        return buffer.toString();
    }
    
    public static <T> UnicodeSet getSet(Map<Integer, T> m, T value) {
        UnicodeSet result = new UnicodeSet();
        for (Iterator<Integer> it = m.keySet().iterator(); it.hasNext();) {
            Integer key = it.next();
            T val = m.get(key);
            if (!val.equals(value)) continue;
            result.add(key.intValue());
        }
        return result;
    }

    public static <T> String show(Collection<T> c) {
        StringBuilder buffer = new StringBuilder();
        for (T item : c) {
            buffer.append(item + "\r\n");
        }
        return buffer.toString();
    }
}
