/*
 * @author Ranides Atterwim <ranides@gmail.com>
 * @copyright Ranides Atterwim
 * @license WTFPL
 * @url http://ranides.net/projects/assira
 */
package net.ranides.assira.generic;

import java.io.Serializable;
import java.util.Comparator;
import java.util.function.Predicate;
import net.ranides.assira.collection.IntComparator;


@SuppressWarnings({
    "PMD.ShortVar", 
    "PMD.ShortMethodName"
})
public final class CompareUtils {
    
    private static final Comparator NATURAL_COMPARATOR = new NComparator();

    private CompareUtils() { }

    /**
     * Compares passed arguments usig Object#equals method.
     * <div class="message-note">{@code null} safe method</div>
     * @param a
     * @param b
     * @return
     */
    @SuppressWarnings("PMD")
    public static boolean equals(Object a, Object b) {
        return (null==a) ? (null==b) : a.equals(b);
    }

    /**
     * If both arguments are numbers ({@code instanceof Number}) then compares
     * with specified precision. Use standard comparison otherwise.
     * @param a
     * @param b
     * @param precision
     * @return
     */
    public static boolean equals(Object a, Object b, double precision) {
        if(a instanceof Number && b instanceof Number) {
            return equals((Number)a, (Number)b, precision);
        } else {
            return CompareUtils.equals(a, b);
        }
    }
    
    /**
     * Compares numbers with specified precision.
     * @param a
     * @param b
     * @param precision
     * @return
     */
    public static boolean equals(Number a, Number b, double precision) {
        return (null==a) ? (null==b) : (null!=b) && (Math.abs(a.doubleValue() - b.doubleValue()) <= precision);
    }
    
    @SuppressWarnings("unchecked")
    public static <T> int cmp(T value1, T value2) {
        return NATURAL_COMPARATOR.compare(value1, value2);
    }
    
    public static int cmp(int value1, int value2) {
        return value1 - value2;
    }
    
    public static <T> int cmp(Comparator<? super T> comparator, T value1, T value2) {
        if(null != comparator ) {
            return comparator.compare(value1, value2);
        } else {
            return cmp(value1, value2);
        }
    }
    
    public static int cmp(IntComparator comparator, int value1, int value2) {
        if(null != comparator ) {
            return comparator.compare(value1, value2);
        } else {
            return (value1 - value2);
        }
        
    }
    
    public static <T> T lower(Comparator<? super T> comparator, T value1, T value2) {
        if(null == value1) {
            return value2;
        }
        if(null == value2) {
            return value1;
        }
        return cmp(comparator, value1, value2) < 0 ? value1 : value2;
    }
    
    public static int lower(IntComparator comparator, int value1, int value2) {
        return cmp(comparator, value1, value2) < 0 ? value1 : value2;
    }
    
    public static <T> T higher(Comparator<? super T> comparator, T value1, T value2) {
        if(null == value1) {
            return value2;
        }
        if(null == value2) {
            return value1;
        }
        return cmp(comparator, value1, value2) > 0 ? value1 : value2;
    }
    
    public static int higher(IntComparator comparator, int value1, int value2) {
        return cmp(comparator, value1, value2) > 0 ? value1 : value2;
    }
    
    public static <T> Predicate<T> predicate(T a) {
        if(null == a) {
            return b -> b==null;
        } else {
            return b -> a.equals(b);
        }
    }
    
    @SuppressWarnings("unchecked")
    public static <T> Comparator<T> comparator() {
        return NATURAL_COMPARATOR;
    }
    
    @SuppressWarnings("unchecked")
    public static <T> Comparator<T> comparator(Class<?> type) {
        if(int.class.equals(type) || Integer.class.equals(type)) {
            return (Comparator)IntComparator.ASC;
        } else {
            return NATURAL_COMPARATOR;
        }
    }

    public static <T> Comparator<T> reversed(Comparator<T> comparator) {
        return (Comparator<T>) (a, b) -> comparator.compare(b, a);
    }
    
    private static final class NComparator implements Comparator<Comparable>, Serializable {
        
        private static final long serialVersionUID = 1L;
        
        @SuppressWarnings("unchecked")
        @Override
        public int compare(Comparable a, Comparable b) {
            return (a == null) ? (b == null ? 0 : -1) : (b==null ? +1 : a.compareTo(b));
        }
    }

}
