/*
 * Copyright 2013-2014 Richard M. Hightower
 * 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.
 *
 * __________                              _____          __   .__
 * \______   \ ____   ____   ____   /\    /     \ _____  |  | _|__| ____    ____
 *  |    |  _//  _ \ /  _ \ /    \  \/   /  \ /  \\__  \ |  |/ /  |/    \  / ___\
 *  |    |   (  <_> |  <_> )   |  \ /\  /    Y    \/ __ \|    <|  |   |  \/ /_/  >
 *  |______  /\____/ \____/|___|  / \/  \____|__  (____  /__|_ \__|___|  /\___  /
 *         \/                   \/              \/     \/     \/       \//_____/
 *      ____.                     ___________   _____    ______________.___.
 *     |    |____ ___  _______    \_   _____/  /  _  \  /   _____/\__  |   |
 *     |    \__  \\  \/ /\__  \    |    __)_  /  /_\  \ \_____  \  /   |   |
 * /\__|    |/ __ \\   /  / __ \_  |        \/    |    \/        \ \____   |
 * \________(____  /\_/  (____  / /_______  /\____|__  /_______  / / ______|
 *               \/           \/          \/         \/        \/  \/
 */

package org.boon;

import org.boon.collections.DoubleList;
import org.boon.collections.FloatList;
import org.boon.collections.IntList;
import org.boon.collections.LongList;
import org.boon.core.*;
import org.boon.core.reflection.*;
import org.boon.core.reflection.fields.FieldAccess;
import org.boon.primitive.CharBuf;

import java.lang.reflect.Array;
import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList;

public class Lists {

    public static <T> List<T> lazyAdd(List<T> list, T... items) {
        list = list == null ? new ArrayList<T>() : list;

        for (T item : items) {
            list.add(item);
        }
        return list;
    }

    public static <T> List<T> lazyAdd(ArrayList<T> list, T... items) {
        list = list == null ? new ArrayList<T>() : list;

        for (T item : items) {
            list.add(item);
        }
        return list;    }

    public static <T> List<T> safeLazyAdd(CopyOnWriteArrayList<T> list, T... items) {
        list = list == null ? new CopyOnWriteArrayList<T>() : list;
        for (T item : items) {
            list.add(item);
        }
        return list;
    }

    public static <T> List<T> lazyAdd(CopyOnWriteArrayList<T> list, T... items) {
        list = list == null ? new CopyOnWriteArrayList<T>() : list;
        for (T item : items) {
            list.add(item);
        }
        return list;
    }

    public static <T> List<T> lazyCreate(List<T> list) {
        return list == null ? new ArrayList<T>() : list;
    }

    public static <T> List<T> lazyCreate(ArrayList<T> list) {
        return list == null ? new ArrayList<T>() : list;
    }

    public static <T> List<T> lazyCreate(CopyOnWriteArrayList<T> list) {
        return list == null ? new CopyOnWriteArrayList<T>() : list;
    }

    public static <T> List<T> safeLazyCreate(CopyOnWriteArrayList<T> list) {
        return list == null ? new CopyOnWriteArrayList<T>() : list;
    }

    public static <T> T fromList( List<Object> list, Class<T> clazz ) {
        return MapObjectConversion.fromList( list, clazz );
    }

    public static <V> List<V> list( Class<V> clazz ) {
        return new ArrayList<>();
    }

    public static <V> List<V> copy( Collection<V> collection ) {
        return new ArrayList<>( collection );
    }

    public static <V> List<V> deepCopy( Collection<V> collection ) {
        List<V> list = new ArrayList<>(collection.size());

        for (V v : collection) {
            list.add( BeanUtils.copy( v ));
        }
        return list;
    }

    public static <V> List<V> deepCopyToList( Collection<V> src,  List<V> dst) {

        for (V v : src) {
            dst.add( BeanUtils.copy( v ));
        }
        return  dst;
    }

    public static <V,T> List<T> deepCopy( Collection<V> src, Class<T> dest  ) {
        List<T> list = new ArrayList<>(src.size());

        for (V v : src) {
            list.add( BeanUtils.createFromSrc( v, dest ));
        }
        return list;
    }

    /**
     * Clones each list item into a new instance with copied fields.
     * It is like doing a clone operation.
     *
     * If the passed list is a LinkedList then the returned list will be a
     * LinkedList.
     *
     * If the passed list is a CopyOnWriteArrayList then the returned list will
     * be a CopyOnWriteArrayList list.
     *
     * All other lists become ArrayList.
     *
     * @param list list to clone
     * @param <V> generics
     * @return new list
     */
    @Universal
    public static <V> List<V> deepCopy( List<V> list ) {
        if ( list instanceof LinkedList ) {
            return deepCopyToList( list, new LinkedList<V>(  ) );
        } else if ( list instanceof CopyOnWriteArrayList ) {
            return deepCopyToList( list, new CopyOnWriteArrayList<V>(  ));
        } else {
            return deepCopy( (Collection)list);
        }
    }

    public static <V> List<List<V>> lists( Collection<V>... collections ) {
        List<List<V>> lists = new ArrayList<>(collections.length);
        for (Collection<V> collection : collections) {
            lists.add(new ArrayList<>(collection));
        }
        return lists;
    }

    public static <V> List<V> list( Iterable<V> iterable ) {
        List<V> list = new ArrayList<>();
        for ( V o : iterable ) {
            list.add( o );
        }
        return list;
    }

    public static <V> List<V> list( Collection<V> collection ) {
        return new ArrayList<>(collection);
    }

    public static <V> List<V> linkedList( Iterable<V> iterable ) {
        List<V> list = new LinkedList<>();
        for ( V o : iterable ) {
            list.add( o );
        }
        return list;
    }

    public static List<?> toListOrSingletonList( Object item ) {
        if ( item == null ) {
            return new ArrayList<>();
        } else if ( item.getClass().isArray() ) {
            final int length = Array.getLength( item );
            List<Object> list = new ArrayList<>();
            for ( int index = 0; index < length; index++ ) {
                list.add( Array.get( item, index ) );
            }
            return list;
        } else if ( item instanceof Collection ) {
            return list( ( Collection ) item );
        } else if ( item instanceof Iterator ) {
            return list( ( Iterator ) item );
        } else if ( item instanceof Enumeration ) {
            return list( ( Enumeration ) item );
        } else if ( item instanceof Iterable ) {
            return list( ( Iterable ) item );
        } else {
            List<Object> list = new ArrayList<>();
            list.add( item );
            return list;
        }
    }

    public static <PROP> List<PROP> toList( List<?> inputList, Class<PROP> cls, String propertyPath ) {
        List<PROP> outputList = new ArrayList<>();

        for (Object o : inputList) {
            outputList.add((PROP) BeanUtils.idx(o, propertyPath));
        }

        return outputList;
    }

    public static IntList toIntList( List<?> inputList, String propertyPath ) {

        return IntList.toIntList(inputList, propertyPath);
    }

    public static FloatList toFloatList( List<?> inputList, String propertyPath ) {

        return FloatList.toFloatList(inputList, propertyPath);
    }

    public static DoubleList toDoubleList( List<?> inputList, String propertyPath ) {

        return DoubleList.toDoubleList(inputList, propertyPath);
    }

    public static LongList toLongList( List<?> inputList, String propertyPath ) {

        return LongList.toLongList(inputList, propertyPath);
    }

    public static List<?> toList( List<?> inputList, String propertyPath ) {
        List<Object> outputList = new ArrayList<>();

        for (Object o : inputList) {
            outputList.add(BeanUtils.idx(o, propertyPath));
        }

        return outputList;
    }

    public static List<?> toList( Object item ) {
       if ( item!= null && item.getClass().isArray() ) {
            final int length = Array.getLength( item );
            List<Object> list = new ArrayList<>();
            for ( int index = 0; index < length; index++ ) {
                list.add( Array.get( item, index ) );
            }
            return list;
        } else if ( item instanceof Collection ) {
            return list( ( Collection ) item );
        } else if ( item instanceof Iterator ) {
            return list( ( Iterator ) item );
        } else if ( item instanceof Enumeration ) {
            return list( ( Enumeration ) item );
        } else if ( item instanceof Iterable ) {
            return list( ( Iterable ) item );
        } else {
            return MapObjectConversion.toList( item );
        }
    }

    public static <V, WRAP> List<WRAP> convert(Class<WRAP> wrapper, Iterable<V> collection ) {
        List<WRAP> list = new ArrayList<>(  );

        for (V v : collection) {

            list.add ( Conversions.coerce(wrapper, v) );
        }
        return list;
    }

    public static <V, WRAP> List<WRAP> convert(Class<WRAP> wrapper, Collection<V> collection ) {
        List<WRAP> list = new ArrayList<>( collection.size() );

        for (V v : collection) {

            list.add ( Conversions.coerce(wrapper, v) );
        }
        return list;
    }

    public static <V, WRAP> List<WRAP> convert(Class<WRAP> wrapper, V[] collection ) {
        List<WRAP> list = new ArrayList<>( collection.length );

        for (V v : collection) {

            list.add ( Conversions.coerce(wrapper, v) );
        }
        return list;
    }

    public static <V, WRAP> List<WRAP> wrap(Class<WRAP> wrapper, Iterable<V> collection ) {
        List<WRAP> list = new ArrayList<>(  );

        for (V v : collection) {
            WRAP wrap = Reflection.newInstance ( wrapper, v );
            list.add ( wrap );
        }
        return list;
    }

    public static <V, WRAP> List<WRAP> wrap(Class<WRAP> wrapper, Collection<V> collection ) {

        if (collection.size()==0) {
            return Collections.EMPTY_LIST;
        }

        List<WRAP> list = new ArrayList<>( collection.size () );

        ClassMeta <WRAP> cls = ClassMeta.classMeta(wrapper);
        ConstructorAccess<WRAP> declaredConstructor = cls.declaredConstructor(collection.iterator().next().getClass());

        for (V v : collection) {
            WRAP wrap = declaredConstructor.create (  v );
            list.add ( wrap );
        }
        return list;
    }

    public static <V, WRAP> List<WRAP> wrap(Class<WRAP> wrapper, V[] collection ) {
        List<WRAP> list = new ArrayList<>( collection.length );

        for (V v : collection) {
            WRAP wrap = Reflection.newInstance ( wrapper, v );
            list.add ( wrap );
        }
        return list;
    }

    public static <V> List<V> list( Enumeration<V> enumeration ) {
        List<V> list = new ArrayList<>();
        while ( enumeration.hasMoreElements() ) {
            list.add( enumeration.nextElement() );
        }
        return list;
    }

    public static <V> Enumeration<V> enumeration( final List<V> list ) {
        final Iterator<V> iter = list.iterator();
        return new Enumeration<V>() {
            @Override
            public boolean hasMoreElements() {
                return iter.hasNext();
            }

            @Override
            public V nextElement() {
                return iter.next();
            }
        };

    }

    public static <V> List<V> list( Iterator<V> iterator ) {
        List<V> list = new ArrayList<>();
        while ( iterator.hasNext() ) {
            list.add( iterator.next() );
        }
        return list;
    }

    @SafeVarargs
    public static <V> List<V> list( final V... array ) {
        if ( array == null ) {
            return new ArrayList<>();
        }
        List<V> list = new ArrayList<>( array.length );
        Collections.addAll( list, array );
        return list;
    }

    public static <V> List<V> safeList(Class<V> cls) {
        return new CopyOnWriteArrayList<>(  );
    }

    @SafeVarargs
    public static <V> List<V> safeList( final V... array ) {
        return new CopyOnWriteArrayList<>( array );
    }

    @SafeVarargs
    public static <V> List<V> linkedList( final V... array ) {
        if ( array == null ) {
            return new LinkedList<>();
        }
        List<V> list = new LinkedList<>();
        Collections.addAll( list, array );
        return list;
    }

    public static <V> List<V> safeList( Collection<V> collection ) {
        return new CopyOnWriteArrayList<>( collection );
    }

    public static <V> List<V> linkedList( Collection<V> collection ) {
        return new LinkedList<>( collection );
    }

    /**
     * Universal methods
     */
    @Universal
    public static int len( List<?> list ) {
        return list.size();
    }

    @Universal
    public static int lengthOf( List<?> list ) {
        return len (list);
    }

    public static boolean isEmpty( List<?> list ) {
        return list == null || list.size() == 0;
    }

    @Universal
    public static <V> boolean in( V value, List<?> list ) {
        return list.contains( value );
    }

    @Universal
    public static <V> void add( List<V> list, V value ) {
        list.add( value );
    }

    @Universal
    public static <V> void add( List<V> list, V... values ) {
        for (V v : values) {
            list.add( v );
        }
    }

    @Universal
    public static <T> T atIndex( List<T> list, final int index ) {

        return idx(list, index);

    }

    @Universal
    public static <T> T idx( List<T> list, final int index ) {
        int i = calculateIndex( list, index );
        if ( i > list.size() - 1 ) {
            i = list.size() - 1;
        }
        return list.get( i );

    }

    public static <T> List idxList( List<T> list, final int index ) {
        return (List) idx(list, index);
    }

    public static <T> Map idxMap( List<T> list, final int index ) {
        return (Map) idx(list, index);
    }

    @Universal
    public static <V> void atIndex( List<V> list, int index, V v ) {
        idx (list, index, v);
    }

    @Universal
    public static <V> void idx( List<V> list, int index, V v ) {
        int i = calculateIndex( list, index );
        list.set( i, v );
    }

    @Universal
    public static <V> List<V> sliceOf( List<V> list, int startIndex, int endIndex ) {
        return slc(list, startIndex, endIndex);
    }

    @Universal
    public static <V> List<V> slc( List<V> list, int startIndex, int endIndex ) {
        int start = calculateIndex( list, startIndex );
        int end = calculateIndex( list, endIndex );
        return list.subList( start, end );
    }

    @Universal
    public static <V> List<V> sliceOf( List<V> list, int startIndex ) {
        return slc(list, startIndex);
    }

    @Universal
    public static <V> List<V> slc( List<V> list, int startIndex ) {
        return slc( list, startIndex, list.size() );
    }

    @Universal
    public static <V> List<V> endSliceOf( List<V> list, int endIndex ) {
        return slcEnd( list, endIndex );
    }

    @Universal
    public static <V> List<V> slcEnd( List<V> list, int endIndex ) {
        return slc( list, 0, endIndex );
    }

    @Universal
    public static <V> List<V> copy( List<V> list ) {
        if ( list instanceof LinkedList ) {
            return new LinkedList<>( list );
        } else if ( list instanceof CopyOnWriteArrayList ) {
            return new CopyOnWriteArrayList<>( list );
        } else {
            return new ArrayList<>( list );
        }
    }

    @Universal
    public static <V> List<V> copy( CopyOnWriteArrayList<V> list ) {
        return new CopyOnWriteArrayList<>( list );
    }

    @Universal
    public static <V> List<V> copy( ArrayList<V> list ) {
        return new ArrayList<>( list );
    }

    @Universal
    public static <V> List<V> copy( LinkedList<V> list ) {
        return new LinkedList<>( list );
    }

    @Universal
    public static <V> void insert( List<V> list, int index, V v ) {
        int i = calculateIndex( list, index );
        list.add( i, v );
    }

    /* End universal methods. */
    private static <T> int calculateIndex( List<T> list, int originalIndex ) {
        final int length = list.size();

        int index = originalIndex;

        /* Adjust for reading from the right as in
        -1 reads the 4th element if the length is 5
         */
        if ( index < 0 ) {
            index = ( length + index );
        }

        /* Bounds check
            if it is still less than 0, then they
            have an negative index that is greater than length
         */
        if ( index < 0 ) {
            index = 0;
        }
        if ( index > length ) {
            index = length;
        }
        return index;
    }

    public static <T> List<T> listFromProperty( Class<T> propertyType, String propertyPath, Collection<?> list ) {
        List<T> newList = new ArrayList<>( list.size() );

        for ( Object item : list ) {
            T newItem = ( T ) BeanUtils.idx( item, propertyPath );
            newList.add( newItem );
        }

        return newList;

    }

    public static <T> List<T> listFromProperty( Class<T> propertyType, String propertyPath, Iterable<?> list ) {
        List<T> newList = new ArrayList<>(  );

        for ( Object item : list ) {
            T newItem = ( T ) BeanUtils.idx( item, propertyPath );
            newList.add( newItem );
        }

        return newList;

    }
    public static List<Map<String, Object>> toListOfMaps( List<?> list ) {
        return MapObjectConversion.toListOfMaps( list );
    }

    public static void setListProperty(List<?> list, String propertyName, Object value) {
        for (Object object : list) {
            BeanUtils.idx(object, propertyName, value);
        }
    }

    public static List<?> mapBy( Object[] objects, Object instance, String methodName) {

        List list = new ArrayList(objects.length);
        for (Object o : objects) {
            list.add( Invoker.invoke(instance, methodName, o ));
        }
        return list;
    }

    public static List<?> mapBy(Object[] objects, Class<?> cls, String methodName) {

        List list = new ArrayList(objects.length);
        for (Object o : objects) {
            list.add( Invoker.invoke(cls,methodName, o ));
        }
        return list;
    }

    public static List<?> mapBy(Iterable<?> objects, Class<?> cls, String methodName) {

        List list = new ArrayList();
        for (Object o : objects) {
            list.add( Invoker.invoke(cls, methodName, o ));
        }
        return list;
    }

    public static List<?> mapBy(Iterable<?> objects, Object instance, String methodName) {

        List list = new ArrayList();
        for (Object o : objects) {
            list.add( Invoker.invoke(instance, methodName, o ));
        }
        return list;
    }

    public static List<?> mapBy(Collection<?> objects, Class<?> cls, String methodName) {

        List list = new ArrayList(objects.size());

        MethodAccess methodAccess = Invoker.invokeMethodAccess(cls, methodName);

        for (Object o : objects) {
            list.add( methodAccess.invokeStatic(o));
        }
        return list;
    }

    public static List<?> mapBy(Collection<?> objects, Object function) {

            MethodAccess methodAccess = Invoker.invokeFunctionMethodAccess(function);

            List list = new ArrayList();
            for (Object o : objects) {
                list.add( methodAccess.invoke(function, o));
            }

            return list;
    }

    public static <T> List<T> mapBy(Class<T> cls, Collection<?> objects, Object function) {
        return (List<T>) mapBy(objects, function);
    }

    public static List<?> mapBy(Iterable<?> objects, Object function) {

        MethodAccess methodAccess = Invoker.invokeFunctionMethodAccess(function);

        List list = new ArrayList();
        for (Object o : objects) {
            list.add( methodAccess.invoke(function, o));
        }
        return list;
    }

    public static List<?> mapBy(Object[] objects, Object function) {

        MethodAccess methodAccess = Invoker.invokeFunctionMethodAccess(function);

        List list = new ArrayList(objects.length);
        for (Object o : objects) {
            list.add( methodAccess.invoke(function, o));
        }
        return list;
    }

     public static List<?> mapBy(Collection<?> objects, Object object, String methodName) {

         MethodAccess methodAccess = Invoker.invokeMethodAccess(object.getClass(), methodName);

        List list = new ArrayList(objects.size());
        for (Object o : objects) {
            list.add( methodAccess.invoke(object, o));
        }
        return list;
    }

    public static <V, N> List<N> mapBy(  final V[] array, Function<V, N> function ) {
        List<N> list = new ArrayList<>( array.length );

        for ( V v : array ) {
            list.add( function.apply( v ) );
        }
        return list;
    }

    public static <V, N> List<N> mapBy( final Collection<V> array, Function<V, N> function ) {
        List<N> list = new ArrayList<>( array.size() );

        for ( V v : array ) {
            list.add( function.apply( v ) );
        }
        return list;
    }

    public static <V, N> List<N> mapBy( final Iterable<V> array, Function<V, N> function ) {
        List<N> list = new ArrayList<>(  );

        for ( V v : array ) {
            list.add( function.apply( v ) );
        }
        return list;
    }

    public static <V, SUM> SUM reduceBy( final Iterable<V> array, Reducer<V, SUM> function ) {

        SUM sum = null;
        for ( V v : array ) {
            sum = function.apply( sum, v ) ;
        }
        return sum;
    }

    public static Object reduceBy( final Iterable<?> array, Object object ) {

        Object sum = null;
        for ( Object v : array ) {
            sum = Invoker.invokeReducer(object, sum, v);
        }
        return sum;
    }

    public static <T> List<T> filterBy( final Iterable<T> array, Predicate<T> predicate ) {
        List<T> list = new ArrayList<>(  );

        for ( T v : array ) {
            if ( predicate.test(v)) {
                list.add(  v  );
            }
        }
        return list;
    }

    public static <T> List<T> filterBy(  final Collection<T> array, Predicate<T> predicate ) {
        List<T> list = new ArrayList<>( array.size()  );

        for ( T v : array ) {
            if ( predicate.test(v)) {
                list.add(  v  );
            }
        }
        return list;
    }

    public static <T> List<T> filterBy( Predicate<T> predicate, final T[] array ) {
        List<T> list = new ArrayList<>( array.length  );

        for ( T v : array ) {
            if ( predicate.test(v)) {
                list.add(  v  );
            }
        }
        return list;
    }

    public static <T> List<T> filterBy(  final Iterable<T> array, Object object ) {
        List<T> list = new ArrayList<>(  );

        for ( T v : array ) {
            if ( Invoker.invokeBooleanReturn(object, v) ) {
                list.add(  v  );
            }
        }
        return list;
    }

    public static <T> List<T> filterBy( final Collection<T> array, Object object ) {
        List<T> list = new ArrayList<>( array.size()  );

        for ( T v : array ) {
            if ( Invoker.invokeBooleanReturn(object, v) ) {
                list.add(  v  );
            }
        }
        return list;
    }

    public static <T> List<T> filterBy(  final T[] array, Object object ) {
        List<T> list = new ArrayList<>( array.length  );

        for ( T v : array ) {
            if ( Invoker.invokeBooleanReturn(object, v) ) {
                list.add(  v  );
            }
        }
        return list;
    }

    public static <T> List<T> filterBy(  final Iterable<T> array, Object object, String methodName ) {
        List<T> list = new ArrayList<>(  );

        for ( T v : array ) {
            if ( (boolean) Invoker.invokeEither(object, methodName, v) ) {
                list.add(  v  );
            }
        }
        return list;
    }

    public static <T> List<T> filterBy( final Collection<T> array, Object object, String methodName ) {
        List<T> list = new ArrayList<>( array.size()  );

        for ( T v : array ) {
            if ( (boolean) Invoker.invokeEither(object, methodName, v) ) {
                list.add(  v  );
            }
        }
        return list;
    }

    public static <T> List<T> filterBy(  final T[] array, Object object, String methodName ) {
        List<T> list = new ArrayList<>( array.length  );

        for ( T v : array ) {
            if ( (boolean) Invoker.invokeEither(object, methodName, v) ) {
                list.add(  v  );
            }
        }
        return list;
    }

    public static String toPrettyJson(List list) {
        CharBuf buf = CharBuf.createCharBuf();
        return buf.prettyPrintCollection(list, false, 0).toString();
    }
}


/*
 * Copyright 2013-2014 Richard M. Hightower
 * 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.
 *
 * __________                              _____          __   .__
 * \______   \ ____   ____   ____   /\    /     \ _____  |  | _|__| ____    ____
 *  |    |  _//  _ \ /  _ \ /    \  \/   /  \ /  \\__  \ |  |/ /  |/    \  / ___\
 *  |    |   (  <_> |  <_> )   |  \ /\  /    Y    \/ __ \|    <|  |   |  \/ /_/  >
 *  |______  /\____/ \____/|___|  / \/  \____|__  (____  /__|_ \__|___|  /\___  /
 *         \/                   \/              \/     \/     \/       \//_____/
 *      ____.                     ___________   _____    ______________.___.
 *     |    |____ ___  _______    \_   _____/  /  _  \  /   _____/\__  |   |
 *     |    \__  \\  \/ /\__  \    |    __)_  /  /_\  \ \_____  \  /   |   |
 * /\__|    |/ __ \\   /  / __ \_  |        \/    |    \/        \ \____   |
 * \________(____  /\_/  (____  / /_______  /\____|__  /_______  / / ______|
 *               \/           \/          \/         \/        \/  \/
 */

package org.boon.core.reflection;

import org.boon.Sets;
import org.boon.core.Typ;
import org.boon.core.reflection.fields.FieldAccess;

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.List;
import java.util.Map;
import java.util.Set;

import static org.boon.Exceptions.die;

/**
 * Created by Richard on 2/17/14.
 */
public class Fields {
    private final static Set<String> fieldSortNames = Sets.safeSet( "name", "orderBy", "title", "key" );
    private final static Set<String> fieldSortNamesSuffixes = Sets.safeSet( "Name", "Title", "Key" );

    private static void setSortableField( Class<?> clazz, String fieldName ) {
        Reflection.context()._sortableFields.put( clazz.getName(), fieldName );
    }

    private static String getSortableField( Class<?> clazz ) {
        return Reflection.context()._sortableFields.get( clazz.getName() );
    }

    /**
     * Checks to see if we have a string field.
     *
     * @param value1
     * @param name
     * @return
     */
    public static boolean hasStringField( final Object value1, final String name ) {

        Class<?> clz = value1.getClass();
        return classHasStringField( clz, name );
    }

    /**
     * Checks to see if this class has a string field.
     *
     * @param clz
     * @param name
     * @return
     */
    public static boolean classHasStringField( Class<?> clz, String name ) {

        List<Field> fields = Reflection.getAllFields( clz );
        for ( Field field : fields ) {
            if (
                    field.getType().equals( Typ.string ) &&
                            field.getName().equals( name ) &&
                            !Modifier.isStatic( field.getModifiers() ) &&
                            field.getDeclaringClass() == clz
                    ) {
                return true;
            }
        }

        return false;
    }

    /**
     * Checks to if an instance has a field
     *
     * @param value1
     * @param name
     * @return
     */
    public static boolean hasField( Object value1, String name ) {
        return classHasField( value1.getClass(), name );
    }

    /**
     * Checks to see if a class has a field.
     *
     * @param clz
     * @param name
     * @return
     */
    public static boolean classHasField( Class<?> clz, String name ) {
        List<Field> fields = Reflection.getAllFields( clz );
        for ( Field field : fields ) {
            if ( field.getName().equals( name )
                    && !Modifier.isStatic( field.getModifiers() )
                    && field.getDeclaringClass() == clz ) {
                return true;
            }
        }

        return false;
    }

    /**
     * This can be used for default sort.
     *
     * @param value1 value we are analyzing
     * @return first field that is comparable or primitive.
     */
    public static String getFirstComparableOrPrimitive( Object value1 ) {
        return getFirstComparableOrPrimitiveFromClass( value1.getClass() );
    }

    /**
     * This can be used for default sort.
     *
     * @param clz class we are analyzing
     * @return first field that is comparable or primitive or null if not found.
     */
    public static String getFirstComparableOrPrimitiveFromClass( Class<?> clz ) {
        List<Field> fields = Reflection.getAllFields( clz );
        for ( Field field : fields ) {

            if ( ( field.getType().isPrimitive() || Typ.isComparable( field.getType() )
                    && !Modifier.isStatic( field.getModifiers() )
                    && field.getDeclaringClass() == clz )
                    ) {
                return field.getName();
            }
        }

        return null;
    }

    /**
     * getFirstStringFieldNameEndsWith
     *
     * @param value object we are looking at
     * @param name  name
     * @return field name or null
     */
    public static String getFirstStringFieldNameEndsWith( Object value, String name ) {
        return getFirstStringFieldNameEndsWithFromClass( value.getClass(), name );
    }

    /**
     * getFirstStringFieldNameEndsWithFromClass
     *
     * @param clz  class we are looking at
     * @param name name
     * @return field name or null
     */
    public static String getFirstStringFieldNameEndsWithFromClass( Class<?> clz, String name ) {
        List<Field> fields = Reflection.getAllFields( clz );
        for ( Field field : fields ) {
            if (
                    field.getName().endsWith( name )
                            && field.getType().equals( Typ.string )
                            && !Modifier.isStatic( field.getModifiers() )
                            && field.getDeclaringClass() == clz ) {

                return field.getName();
            }
        }

        return null;
    }

    /**
     * Gets the first sortable fields found.
     *
     * @param value1
     * @return sortable field
     */
    public static String getSortableField( Object value1 ) {

        if (value1 instanceof Map) {
            return getSortableFieldFromMap( (Map<String, ?>) value1);
        } else {
            return getSortableFieldFromClass( value1.getClass() );
        }
    }

    private static String getSortableFieldFromMap(Map<String, ?> map) {

            /* See if we have this sortable field and look for string first. */
        for (String name : fieldSortNames) {
            if (map.containsKey(name)) {
                return name;
            }
        }

            /*
             Now see if we can find one of our predefined suffixes.
             */
        for (String suffix : fieldSortNamesSuffixes) {
            for (String key : map.keySet()) {
                if (key.endsWith(suffix)) {
                    return key;
                }
            }
        }

        for (Object object : map.entrySet()) {
            Map.Entry<String, Object> entry = (Map.Entry<String, Object>) object;
            if (Typ.isBasicType(entry.getValue())) {
                return entry.getKey();
            }
        }

        for (Object object : map.entrySet()) {
            Map.Entry<String, Object> entry = (Map.Entry<String, Object>) object;
            if (entry.getValue() instanceof Comparable) {
                return entry.getKey();
            }
        }

        return die(String.class, "No suitable sort key was found");
    }

    /**
     * Gets the first sortable field.
     *
     * @param clazz the class we are getting the sortable field from.
     * @return sortable field
     */
    public static String getSortableFieldFromClass( Class<?> clazz ) {

        /** See if the fieldName is in the field listStream already.
         * We keep a hash-map cache.
         * */
        String fieldName = getSortableField( clazz );

        /**
         * Not found in cache.
         */
        if ( fieldName == null ) {

            /* See if we have this sortable field and look for string first. */
            for ( String name : fieldSortNames ) {
                if ( classHasStringField( clazz, name ) ) {
                    fieldName = name;
                    break;
                }
            }

            /*
             Now see if we can find one of our predefined suffixes.
             */
            if ( fieldName == null ) {
                for ( String name : fieldSortNamesSuffixes ) {
                    fieldName = getFirstStringFieldNameEndsWithFromClass( clazz, name );
                    if ( fieldName != null ) {
                        break;
                    }
                }
            }

            /**
             * Ok. We still did not find it so give us the first comparable or
             * primitive that we can find.
             */
            if ( fieldName == null ) {
                fieldName = getFirstComparableOrPrimitiveFromClass( clazz );
            }

            /* We could not find a sortable field. */
            if ( fieldName == null ) {
                setSortableField( clazz, "NOT FOUND" );
                die( "Could not find a sortable field for type " + clazz );

            }

            /* We found a sortable field. */
            setSortableField( clazz, fieldName );
        }
        return fieldName;

    }

    public static boolean hasField( Class<?> aClass, String name ) {
        Map<String, FieldAccess> fields = Reflection.getAllAccessorFields( aClass );
        return fields.containsKey( name );
    }
}


/*
 * Copyright 2013-2014 Richard M. Hightower
 * 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.
 *
 * __________                              _____          __   .__
 * \______   \ ____   ____   ____   /\    /     \ _____  |  | _|__| ____    ____
 *  |    |  _//  _ \ /  _ \ /    \  \/   /  \ /  \\__  \ |  |/ /  |/    \  / ___\
 *  |    |   (  <_> |  <_> )   |  \ /\  /    Y    \/ __ \|    <|  |   |  \/ /_/  >
 *  |______  /\____/ \____/|___|  / \/  \____|__  (____  /__|_ \__|___|  /\___  /
 *         \/                   \/              \/     \/     \/       \//_____/
 *      ____.                     ___________   _____    ______________.___.
 *     |    |____ ___  _______    \_   _____/  /  _  \  /   _____/\__  |   |
 *     |    \__  \\  \/ /\__  \    |    __)_  /  /_\  \ \_____  \  /   |   |
 * /\__|    |/ __ \\   /  / __ \_  |        \/    |    \/        \ \____   |
 * \________(____  /\_/  (____  / /_______  /\____|__  /_______  / / ______|
 *               \/           \/          \/         \/        \/  \/
 */

package org.boon.cache;

import java.util.concurrent.atomic.AtomicInteger;

import static org.boon.Exceptions.die;

/**
 * A cache entry.
 * @param <KEY> the key for the cache entry
 * @param <VALUE> the value
 */
class CacheEntry<KEY, VALUE> implements Comparable<CacheEntry> {

    /** Keep track of the read count. */
    final AtomicInteger readCount = new AtomicInteger();

    /** order. */
    final int order;

    /** The value. */
    VALUE value;

    /** The key. */
    final KEY key;

    /** The type of cache.*/
    final CacheType type;

    /** The time the entry was added. */
    final long time;

    /** Creates a cache entry.
     *
     * @param key key
     * @param value value
     * @param order order
     * @param type type
     * @param time time of entry
     */
    CacheEntry( KEY key, VALUE value, int order, CacheType type, long time ) {
        this.order = order;
        this.value = value;
        this.key = key;
        this.time = time;
        this.type = type;

    }

    /**
     * Comparison of entries this determines what we will order the cache by
     * which determines which type of cache it is.
     * @param other the other entry
     * @return results
     */
    @Override
    public final int compareTo( CacheEntry other ) {
        switch ( type ) {

            case LFU:
                return compareToLFU( other );
            case LRU:
                return compareToLRU( other );
            case FIFO:
                return compareToFIFO( other );
            default:
                die();
                return 0;

        }
    }

    /**
     *
     * @param other other entry to compare to
     * @return
     */
    private final int compareReadCount( CacheEntry other ) {

        if ( readCount.get() > other.readCount.get() ) {  //this read count is greater so it has higher priority
            return 1;
        } else if ( readCount.get() < other.readCount.get() ) {//this read count is lower so it has lower priority
            return -1;
        } else if ( readCount.get() == other.readCount.get() ) {
            return 0;
        }
        die();
        return 0;
    }

    /**
     * Compare the time.
     * @param other the other entry
     * @return results
     */
    private final int compareTime( CacheEntry other ) {

        if ( time > other.time ) {  //this time stamp is  greater so it has higher priority
            return 1;
        } else if ( time < other.time ) {//this time stamp is lower so it has lower priority
            return -1;
        } else if ( time == other.time ) {//equal priority
            return 0;
        }
        die();
        return 0;
    }

    /**
     * Compare the order.
     * @param other the other entry
     * @return results
     */
    private final int compareOrder( CacheEntry other ) {

        if ( order > other.order ) {  //this order is lower so it has higher priority
            return 1;
        } else if ( order < other.order ) {//this order is higher so it has lower priority
            return -1;
        } else if ( order == other.order ) {//equal priority
            return 0;
        }
        die();
        return 0;
    }

    /**
     * Compares the read counts.
     * @param other read count
     * @return results
     */
    private final int compareToLFU( CacheEntry other ) {

        int cmp = compareReadCount( other );
        if ( cmp != 0 ) {
            return cmp;
        }

        cmp = compareTime( other );
        if ( cmp != 0 ) {
            return cmp;
        }

        return compareOrder( other );
    }

    /**
     * Compare the time.
     * @param other other entry to compare to
     * @return results
     */
    private final int compareToLRU( CacheEntry other ) {

        int cmp = compareTime( other );
        if ( cmp != 0 ) {
            return cmp;
        }

        cmp = compareOrder( other );
        if ( cmp != 0 ) {
            return cmp;
        }

        return compareReadCount( other );
    }

    /**
     * Compare for FIFO
     * @param other the other entry
     * @return results
     */
    private final int compareToFIFO( CacheEntry other ) {
        int cmp = compareOrder( other );
        if ( cmp != 0 ) {
            return cmp;
        }

        cmp = compareTime( other );
        if ( cmp != 0 ) {
            return cmp;
        }

        return cmp = compareReadCount( other );
    }

    @Override
    public String toString() {
        return "CE{" +
                "c=" + readCount +
                ", ord=" + order +
                ", val=" + value +
                ", ky=" + key +
                ", typ=" + type +
                ", t=" + time +
                '}';
    }
}


/*
 * Copyright 2013-2014 Richard M. Hightower
 * 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.
 *
 * __________                              _____          __   .__
 * \______   \ ____   ____   ____   /\    /     \ _____  |  | _|__| ____    ____
 *  |    |  _//  _ \ /  _ \ /    \  \/   /  \ /  \\__  \ |  |/ /  |/    \  / ___\
 *  |    |   (  <_> |  <_> )   |  \ /\  /    Y    \/ __ \|    <|  |   |  \/ /_/  >
 *  |______  /\____/ \____/|___|  / \/  \____|__  (____  /__|_ \__|___|  /\___  /
 *         \/                   \/              \/     \/     \/       \//_____/
 *      ____.                     ___________   _____    ______________.___.
 *     |    |____ ___  _______    \_   _____/  /  _  \  /   _____/\__  |   |
 *     |    \__  \\  \/ /\__  \    |    __)_  /  /_\  \ \_____  \  /   |   |
 * /\__|    |/ __ \\   /  / __ \_  |        \/    |    \/        \ \____   |
 * \________(____  /\_/  (____  / /_______  /\____|__  /_______  / / ______|
 *               \/           \/          \/         \/        \/  \/
 */

package org.boon.sort;

import org.boon.*;
import org.boon.core.Typ;
import org.boon.core.reflection.BeanUtils;
import org.boon.core.Conversions;
import org.boon.core.reflection.Fields;
import org.boon.core.reflection.fields.FieldAccess;

import java.text.Collator;
import java.util.*;

import static org.boon.core.Conversions.toArray;

public class Sorting {

    /**
     * Gets the logger.
     */
    private static final Logger log = Boon.configurableLogger(Sorting.class.getName());

    /** Takes a list an an array or sorts
     *
     * @param list list to sorts
     * @param sorts what you want to sore the list by
     */
    public static void sort(List list, Sort... sorts) {
        Sort.sorts(sorts).sort(list);
    }

    /**
     * Sort a list.
     * @param list the list you want to sort
     * @param sortBy what you want to sort the list by
     * @param ascending do you want ascending order
     * @param nullsFirst do you want nulls first
     */
    public static void sort( List list, String sortBy, boolean ascending, boolean nullsFirst ) {
        if ( list == null || list.size() == 0 ) {
            return;
        }

        if (sortBy.equals("this")) {

            Collections.sort(list, thisUniversalComparator(ascending, nullsFirst));
            return;
        }
        Iterator iterator = list.iterator();
        Object object = iterator.next();

        Map<String, FieldAccess> fields = null;

        if (object != null) {
          fields = BeanUtils.getFieldsFromObject( object );
        } else {
            while(iterator.hasNext()) {

                object = iterator.next();
                if (object!=null) {
                    fields = BeanUtils.getFieldsFromObject( object );
                    break;
                }
            }

        }

        if (fields!=null) {

            final FieldAccess field = fields.get( sortBy );

            if ( field != null ) {

                Collections.sort( list, Sorting.universalComparator(field, ascending, nullsFirst) );

            }
        }
    }

    /**
     * Sort collection.
     * @param collection the collection you want to sort
     * @param sortBy what you want to sort the list by
     * @param ascending do you want ascending order
     * @param nullsFirst do you want nulls first
     */
    public static <V> Collection<V>  sort( Class<V> componentType, Collection<V> collection, String sortBy, boolean ascending, boolean nullsFirst ) {

        if (collection instanceof List) {
            sort ((List) collection, sortBy, ascending, nullsFirst);
            return collection;
        } else {
            V[] array = toArray(componentType, collection);
            sort(array, sortBy, ascending, nullsFirst);
            if (collection instanceof LinkedHashSet) {
                return new LinkedHashSet<>(Lists.list(array));
            } else {
                return Lists.list(array);
            }
        }
    }

    /**
     * Sort map entries.
     * @param map  the map entries you want to sort
     * @param sortBy what you want to sort the list by
     * @param ascending do you want ascending order
     * @param nullsFirst do you want nulls first
     */
    public static <K, V> Collection<Map.Entry<K, V>>  sortEntries( Class<V> componentType, Map<K, V> map,
                                                            String sortBy, boolean ascending, boolean nullsFirst ) {

        return sort ((Class) componentType, (Collection) map.entrySet() , sortBy, ascending, nullsFirst);

    }

    /**
     * Sort map values.
     * @param map  the map entries you want to sort
     * @param sortBy what you want to sort the list by
     * @param ascending do you want ascending order
     * @param nullsFirst do you want nulls first
     */
    public static <K, V> Collection<Map.Entry<K, V>>  sortValues( Class<V> componentType, Map<K, V> map,
                                                                   String sortBy, boolean ascending, boolean nullsFirst ) {

        return sort ((Class) componentType, (Collection) map.values() , sortBy, ascending, nullsFirst);

    }

    /**
     * Sort map keys.
     * @param map  the map entries you want to sort
     * @param sortBy what you want to sort the list by
     * @param ascending do you want ascending order
     * @param nullsFirst do you want nulls first
     */
    public static <K, V> Collection<Map.Entry<K, V>>  sortKeys( Class<V> componentType, Map<K, V> map,
                                                                  String sortBy, boolean ascending, boolean nullsFirst ) {

        return sort ((Class) componentType, (Collection) map.keySet() , sortBy, ascending, nullsFirst);

    }

    /**
     * Sort collection.
     * @param iterable the iterable you want to sort
     * @param sortBy what you want to sort the list by
     * @param ascending do you want ascending order
     * @param nullsFirst do you want nulls first
     */
    public static <V> Iterable<V> sort( Class<V> componentType, Iterable<V> iterable, String sortBy, boolean ascending, boolean nullsFirst ) {

        if (iterable instanceof List) {
            sort ((List) iterable, sortBy, ascending, nullsFirst);
            return iterable;
        } else if (iterable instanceof  Collection) {
            return sort (componentType, (Collection<V>) iterable, sortBy, ascending, nullsFirst);
        } else {
            List<V> list = Lists.list(iterable);
            sort ( list, sortBy, ascending, nullsFirst);
            return list;
        }
    }

    /**
     * Sort an array.
     * @param array the list you want to sort
     * @param sortBy what you want to sort the list by
     * @param ascending do you want ascending order
     * @param nullsFirst do you want nulls first
     */
    public static <T> void  sort( T[] array, String sortBy, boolean ascending, boolean nullsFirst ) {
        if ( array == null || array.length == 0 ) {
            return;
        }

        if (sortBy.equals("this")) {

            Arrays.sort(array, thisUniversalComparator(ascending, nullsFirst));
            return;
        }

        Object object = array[0];

        Map<String, FieldAccess> fields = null;

        if (object != null) {
            fields = BeanUtils.getFieldsFromObject( object );
        } else {
            for (int index=1; index< array.length; index++) {

                object = array[index];
                if (object!=null) {
                    fields = BeanUtils.getFieldsFromObject( object );
                    break;
                }
            }

        }

        if (fields!=null) {

            final FieldAccess field = fields.get( sortBy );

            if ( field != null ) {

                Arrays.sort( array, Sorting.universalComparator(field, ascending, nullsFirst) );

            }
        }
    }

    /**
     * Sorts a list based on the natural ascending order.
     * This puts null values last.
     * @param list the list you want to sort.
     */
    public static void sort( List list ) {
        sort( list, "this", true, false);
    }

    /**
     * Sorts a array based on the natural ascending order.
     * This puts null values last.
     * @param array the list you want to sort.
     */
    public static <T> void sort( T[] array ) {
        sort( array, "this", true, false);
    }

    /**
     * Sorts a collection based on the natural ascending order.
     * This puts null values last.
     * @param collection the list you want to sort.
     */
    public static <T> Collection<T> sort( Class<T> componentType, Collection<T> collection ) {
        return sort( componentType, collection, "this", true, false);
    }

    /**
     * Sorts a iterable based on the natural ascending order.
     * This puts null values last.
     * @param iterable the list you want to sort.
     */
    public static <T> Iterable<T> sort( Class<T> componentType, Iterable<T> iterable ) {
        return sort( componentType, iterable, "this", true, false);
    }

    /**
     * Sorts a list based on the natural ascending order and puts null values first.
     * @param list the list you want to sort.
     */
    public static void sortNullsFirst( List list ) {

        sort(list, "this", true, true);
    }

    /**
     * Sorts an array based on the natural ascending order and puts null values first.
     * @param array the list you want to sort.
     */
    public static <T> void sortNullsFirst( T[] array ) {

        sort(array, "this", true, true);
    }

    /**
     * Sorts a collection based on the natural ascending order.
     * This puts null values first.
     * @param collection the collection you want to sort.
     */
    public static <T> Collection<T> sortNullsFirst( Class<T> componentType, Collection<T> collection ) {
        return sort( componentType, collection, "this", true, true);
    }

    /**
     * Sorts an iterable based on the natural ascending order.
     * This puts null values first.
     * @param iterable the list you want to sort.
     */
    public static <T> Iterable<T> sortNullsFirst( Class<T> componentType, Iterable<T> iterable ) {
        return sort( componentType, iterable, "this", true, true);
    }

    /**
     * Sorts a list based on the natural order descending order.
     * This puts null values last.
     * @param list the list you want to sort.
     */
    public static void sortDesc( List list ) {
        sort( list, "this", false, false);

    }

    /**
     * Sorts a array based on the natural order descending order.
     * This puts null values last.
     * @param array the list you want to sort.
     */
    public static <T> void sortDesc( T[] array ) {
        sort( array, "this", false, false);

    }

    /**
     * Sorts a iterable based on the natural descending order.
     * This puts null values last.
     * @param iterable the iterable you want to sort.
     */
    public static <T> Iterable<T> sortDesc( Class<T> componentType, Iterable<T> iterable ) {
        return sort( componentType, iterable, "this", false, false);
    }

    /**
     * Sorts a collection based on the natural descending order.
     * This puts null values last.
     * @param collection the collection you want to sort.
     */
    public static <T> Collection<T> sortDesc( Class<T> componentType, Collection<T> collection ) {
        return sort( componentType, collection, "this", false, false);
    }

    /**
     * Sorts a list based on the natural order descending order.
     * This puts null values first.
     * @param list the list you want to sort.
     */
    public static void sortDescNullsFirst( List list ) {
        sort( list, "this", false, true);

    }

    /**
     * Sorts a array based on the natural order descending order.
     * This puts null values first.
     * @param array the list you want to sort.
     */
    public static <T> void sortDescNullsFirst( T[] array ) {
        sort( array, "this", false, true);

    }

    /**
     * Sorts a iterable based on the natural descending order.
     * This puts null values first.
     * @param iterable the iterable you want to sort.
     */
    public static <T> Iterable<T> sortDescNullsFirst( Class<T> componentType, Iterable<T> iterable ) {
        return sort( componentType, iterable, "this", false, true);
    }

    /**
     * Sorts a collection based on the natural descending order.
     * This puts null values first.
     * @param collection the collection you want to sort.
     */
    public static <T> Collection<T> sortDescNullsFirst( Class<T> componentType, Collection<T> collection ) {
        return sort( componentType, collection, "this", false, true);
    }

    /**
     *
     * Sorts lists ascending
     * Nulls last.
     * @param list the list you want to sort
     * @param sortBy what you want to sort the list by
     */
    public static void sort( List list, String sortBy ) {
        sort(list, sortBy, true, false);
    }

    /**
     *
     * Sorts array ascending
     * Nulls last.
     * @param array the list you want to sort
     * @param sortBy what you want to sort the array by
     */
    public static <T> void sort( T[] array, String sortBy ) {
        sort( array, sortBy, true, false);

    }

    /**
     *
     * Sorts iterable ascending
     * Nulls last.
     * @param iterable the list you want to sort
     * @param sortBy what you want to sort the array by
     */
    public static <T> Iterable<T> sort( Class<T> componentType, Iterable<T> iterable, String sortBy  ) {
        return sort( componentType, iterable, sortBy, true, false);
    }

    /**
     *
     * Sorts collection ascending
     * Nulls last.
     * @param collection the list you want to sort
     * @param sortBy what you want to sort the array by
     */
    public static <T> Collection<T> sort( Class<T> componentType, Collection<T> collection, String sortBy ) {
        return sort( componentType, collection, sortBy, true, false);
    }

    /**
     *
     * Sorts lists Ascending Null first
     * Nulls first.
     *
     * @param list the list you want to sort
     * @param sortBy what you want to sort the list by
     */
    public static void sortNullsFirst( List list, String sortBy ) {
        sort( list, sortBy, true, true);
    }

    /**
     *
     * Sorts array ascending
     * Nulls first.
     * @param array the list you want to sort
     * @param sortBy what you want to sort the array by
     */
    public static <T> void sortNullsFirst( T[] array, String sortBy ) {
        sort( array, sortBy, true, true);

    }

    /**
     *
     * Sorts iterable ascending
     * Nulls first.
     * @param iterable the list you want to sort
     * @param sortBy what you want to sort the array by
     */
    public static <T> Iterable<T> sortNullsFirst( Class<T> componentType, Iterable<T> iterable, String sortBy  ) {
        return sort( componentType, iterable, sortBy, true, true);
    }

    /**
     *
     * Sorts collection ascending
     * Nulls first.
     * @param collection the list you want to sort
     * @param sortBy what you want to sort the array by
     */
    public static <T> Collection<T> sortNullsFirst( Class<T> componentType, Collection<T> collection, String sortBy ) {
        return sort( componentType, collection, sortBy, true, true);
    }

    /**
     *
     * Sorts lists Descending
     * @param list the list you want to sort
     * @param sortBy what you want to sort the list by
     */
    public static void sortDesc( List list, String sortBy ) {
        sort(list, sortBy, false, false);
    }

    /**
     *
     * Sorts array Descending
     * Nulls last.
     * @param array the list you want to sort
     * @param sortBy what you want to sort the array by
     */
    public static <T> void sortDesc( T[] array, String sortBy ) {
        sort( array, sortBy, false, false);

    }

    /**
     *
     * Sorts iterable ascending
     * Nulls last.
     * @param iterable the list you want to sort
     * @param sortBy what you want to sort the array by
     */
    public static <T> Iterable<T> sortDesc( Class<T> componentType, Iterable<T> iterable, String sortBy  ) {
        return sort( componentType, iterable, sortBy, false, false);
    }

    /**
     *
     * Sorts collection ascending
     * Nulls last.
     * @param collection the list you want to sort
     * @param sortBy what you want to sort the array by
     */
    public static <T> Collection<T> sortDesc( Class<T> componentType, Collection<T> collection, String sortBy ) {
        return sort( componentType, collection, sortBy, false, false);
    }

    /**
     *
     * Sorts lists Descending
     * @param list the list you want to sort
     * @param sortBy what you want to sort the list by
     */
    public static void sortDescNullsFirst( List list, String sortBy ) {
        sort( list, sortBy, false, true);
    }

    /**
     *
     * Sorts array Descending
     * Nulls first.
     * @param array the list you want to sort
     * @param sortBy what you want to sort the array by
     */
    public static <T> void sortDescNullsFirst( T[] array, String sortBy ) {
        sort( array, sortBy, false, true);

    }

    /**
     *
     * Sorts iterable ascending
     * Nulls first.
     * @param iterable the list you want to sort
     * @param sortBy what you want to sort the array by
     */
    public static <T> Iterable<T> sortDescNullsFirst( Class<T> componentType, Iterable<T> iterable, String sortBy  ) {
        return sort( componentType, iterable, sortBy, false, true);
    }

    /**
     *
     * Sorts collection ascending
     * Nulls first.
     * @param collection the list you want to sort
     * @param sortBy what you want to sort the array by
     */
    public static <T> Collection<T> sortDescNullsFirst( Class<T> componentType, Collection<T> collection, String sortBy ) {
        return sort( componentType, collection, sortBy, false, true);
    }

    /**
     * This creates the universal comparator object which is used by the sort work horse.
     *
     * @param field The field we are sorting on.
     * @param ascending if this should be ascending or descending.
     * @return
     */
    public static Comparator universalComparator( final FieldAccess field, final boolean ascending,
                                                  final boolean nullsFirst) {
        return new Comparator() {
            @Override
            public int compare( Object o1, Object o2 ) {
                Object value1 = null;
                Object value2 = null;

                if ( ascending ) {
                    value1 = field.getValue( o1 );
                    value2 = field.getValue( o2 );
                } else {
                    value1 = field.getValue( o2 );
                    value2 = field.getValue( o1 );
                }
                return Sorting.compare(value1, value2, nullsFirst);
            }
        };
    }

    /**
     * This creates the universal comparator object used for "this".
     *
     * @param ascending if this should be ascending or descending.
     * @return
     */
    public static Comparator thisUniversalComparator( final boolean ascending,
                                                  final boolean nullsFirst) {
        return new Comparator() {
            @Override
            public int compare( Object o1, Object o2 ) {
                Object value1;
                Object value2;

                if ( ascending ) {
                    value1 =  ( o1 );
                    value2 =  ( o2 );
                } else {
                    value1 =  ( o2 );
                    value2 =  ( o1 );
                }

                return Sorting.compare(value1, value2, nullsFirst);
            }
        };
    }

    /**
     * This compares two values.
     * @param value1 value1
     * @param value2 value2
     * @return
     */
    public static int compare( Object value1, Object value2 ) {
        return compare(value1, value2, false);
    }

    /**
     * This compares two values.
     * If the objects are strings (CharSequence) they are always compared lexicographically.
     * If the objects are comparable they are always compared using a natural order.
     *
     * @param value1 value1
     * @param value2 value2
     * @param nullsLast put nulls last
     *
     * @return
     */
    public static int compare( Object value1, Object value2, boolean nullsLast ) {

        if ( value1 == null && value2 == null ) {
            return 0;
        } else if ( value1 == null && value2 != null ) {
            return nullsLast ? -1 : 1;
        } else if ( value1 != null && value2 == null ) {
            return nullsLast ? 1 : -1;
        }

        /** Objects are string like so compare using collator. */
        if ( value1 instanceof CharSequence ) {
            String str1 = Conversions.toString( value1 );
            String str2 = Conversions.toString( value2 );
            Collator collator = Collator.getInstance();
            return collator.compare( str1, str2 );

        /** Objects are comparable, yeah! */
        } else if ( Typ.isComparable( value1 ) && value1.getClass() == value2.getClass()) {
            Comparable c1 = Conversions.comparable(value1);
            Comparable c2 = Conversions.comparable(value2);
            return c1.compareTo( c2 );
        } else if (  value1 instanceof Integer && value2 instanceof Integer ) {
            Comparable c1 = Conversions.comparable(value1);
            Comparable c2 = Conversions.comparable(value2);
            return c1.compareTo( c2 );
        }
        else if (  value1 instanceof Double && value2 instanceof Double ) {
            Comparable c1 = Conversions.comparable(value1);
            Comparable c2 = Conversions.comparable(value2);
            return c1.compareTo( c2 );
        }
        else if (  value1 instanceof Long && value2 instanceof Long ) {
            Comparable c1 = Conversions.comparable(value1);
            Comparable c2 = Conversions.comparable(value2);
            return c1.compareTo( c2 );
        }
        else if (  value1 instanceof Number && value2 instanceof Number ) {
            Double c1 = Conversions.toDouble(value1);
            Double c2 = Conversions.toDouble(value2);
            return c1.compareTo( c2 );
        }
         else {
            /** Object are neither String like or comparable.
             * Ours it not to reason why, ours it to do or die.
             * Find the first sortable field and sort by that.
             * */
            String name = Fields.getSortableField( value1 );
            String sv1 = ( String ) BeanUtils.getPropByPath( value1, name );
            String sv2 = ( String ) BeanUtils.getPropByPath( value2, name );
            return Sorting.compare(sv1, sv2);

        }

    }

}


/*
 * Copyright 2013-2014 Richard M. Hightower
 * 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.
 *
 * __________                              _____          __   .__
 * \______   \ ____   ____   ____   /\    /     \ _____  |  | _|__| ____    ____
 *  |    |  _//  _ \ /  _ \ /    \  \/   /  \ /  \\__  \ |  |/ /  |/    \  / ___\
 *  |    |   (  <_> |  <_> )   |  \ /\  /    Y    \/ __ \|    <|  |   |  \/ /_/  >
 *  |______  /\____/ \____/|___|  / \/  \____|__  (____  /__|_ \__|___|  /\___  /
 *         \/                   \/              \/     \/     \/       \//_____/
 *      ____.                     ___________   _____    ______________.___.
 *     |    |____ ___  _______    \_   _____/  /  _  \  /   _____/\__  |   |
 *     |    \__  \\  \/ /\__  \    |    __)_  /  /_\  \ \_____  \  /   |   |
 * /\__|    |/ __ \\   /  / __ \_  |        \/    |    \/        \ \____   |
 * \________(____  /\_/  (____  / /_______  /\____|__  /_______  / / ______|
 *               \/           \/          \/         \/        \/  \/
 */

package org.boon.cache;

import org.boon.collections.SortableConcurrentList;
import org.boon.core.timer.TimeKeeper;
import org.boon.core.timer.TimeKeeperBasic;

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * Fast concurrent read cache with many options.
 *
 * Writes are slow (single threaded lock)
 * Reads are very fast no lock.
 * Compaction (if you are over the limit) is slow.
 *
 *
 * @param <KEY> key
 * @param <VALUE> value
 */
public class FastConcurrentReadLruLfuFifoCache<KEY, VALUE> implements Cache<KEY, VALUE> {

    /**
     * Map to hold cache items.
     */
    private final ConcurrentHashMap<KEY, CacheEntry<KEY, VALUE>> map = new ConcurrentHashMap<>();

    /**
     * A list that can be sorted easily. It is concurrent.
     */
    private final SortableConcurrentList<CacheEntry<KEY, VALUE>> list;

    /**
     * Eviction size.
     */
    private final int evictSize;

    /**
     * Count which determines order.
     */
    private final AtomicInteger count = new AtomicInteger();

    /**
     * CacheType.
     */
    private final CacheType type;

    /**
     * How do we determine time of entry.
     */
    private final TimeKeeper timeKeeper;

    /**
     * New cache LFU is the default.
     * @param evictSize
     */
    public FastConcurrentReadLruLfuFifoCache( int evictSize ) {
        this.evictSize = ( int ) ( evictSize + ( evictSize * 0.20f ) );
        list = new SortableConcurrentList<>();
        this.type = CacheType.LFU;
        timeKeeper = new TimeKeeperBasic();

    }

    /**
     * New cache
     * @param evictSize eviction size
     * @param tradeoffs tradeoffs
     * @param type cache type.
     */
    public FastConcurrentReadLruLfuFifoCache( int evictSize, Tradeoffs tradeoffs, CacheType type ) {
        this.evictSize = ( int ) ( evictSize + ( evictSize * 0.20f ) );

        this.type = type;

        if ( tradeoffs == Tradeoffs.FAST_REMOVE ) {
            list = new SortableConcurrentList<>( new LinkedList<CacheEntry<KEY, VALUE>>() );
        } else if ( tradeoffs == Tradeoffs.FAST_SORT ) {
            list = new SortableConcurrentList<>( new ArrayList<CacheEntry<KEY, VALUE>>() );
        } else {
            list = new SortableConcurrentList<>();
        }

        timeKeeper = new TimeKeeperBasic();

    }

    /**
     * Just for testing
     *
     * @param evictSize
     * @param type
     */
    FastConcurrentReadLruLfuFifoCache( boolean test, int evictSize, CacheType type ) {
        this.evictSize = ( int ) ( evictSize + ( evictSize * 0.20f ) );
        list = new SortableConcurrentList<>();
        this.type = type;

        timeKeeper = new TimeKeeper() {
            int i;

            @Override
            public long time() {
                return System.currentTimeMillis() + i++;
            }
        };

    }

    /** Get the value from the cache. It does not touch the lock so
     * reads are fast.
     * This does not touch the order list so it is fast.
     * @param key the key
     * @return value
     */
    public VALUE get( KEY key ) {
        CacheEntry<KEY, VALUE> cacheEntry = map.get( key );
        if ( cacheEntry != null ) {
            cacheEntry.readCount.incrementAndGet();
            return cacheEntry.value;
        } else {
            return null;
        }

    }

    /**
     * Used for testing as it gets the value without updating stats
     * @param key key
     * @return value
     */
    public VALUE getSilent( KEY key ) {
        CacheEntry<KEY, VALUE> cacheEntry = map.get( key );
        if ( cacheEntry != null ) {
            return cacheEntry.value;
        } else {
            return null;
        }

    }

    /**
     * Remove the key. This touches the lock.
     * So removes are **not** fast.
     * @param key key
     */
    @Override
    public void remove( KEY key ) {
        CacheEntry<KEY, VALUE> entry = map.remove( key );
        if ( entry != null ) {
            list.remove( entry );
        }
    }

    /** Put the value. This touches the lock so puts are **not** fast.
     *
     * @param key the key key
     * @param value the value value
     */
    public void put( KEY key, VALUE value ) {
        CacheEntry<KEY, VALUE> entry = map.get( key );

        if ( entry == null ) {
            entry = new CacheEntry<>( key, value, order(), type, time() );
            list.add( entry );
            map.put( key, entry );
        } else {
            entry.readCount.incrementAndGet();
            entry.value = value;
        }
        evictIfNeeded();
    }

    /** Gets the time. */
    private long time() {
        return this.timeKeeper.time();
    }

    /** Avoid overflow. */
    private final int order() {
        int order = count.incrementAndGet();
        if ( order > Integer.MAX_VALUE - 100 ) {
            count.set( 0 );
        }
        return order;
    }

    /** Evict if we are over the size limit.*/
    private final void evictIfNeeded() {
        if ( list.size() > evictSize ) {

            final List<CacheEntry<KEY, VALUE>> killList = list.sortAndReturnPurgeList( 0.1f );

            for ( CacheEntry<KEY, VALUE> cacheEntry : killList ) {
                map.remove( cacheEntry.key );
            }
        }

    }

    public String toString() {
        return map.toString();
    }

    /**
     * How many items do we have in the cache?
     * @return size
     */
    public int size() {
        return this.map.size();
    }
}


/*
 * Copyright 2013-2014 Richard M. Hightower
 * 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.
 *
 * __________                              _____          __   .__
 * \______   \ ____   ____   ____   /\    /     \ _____  |  | _|__| ____    ____
 *  |    |  _//  _ \ /  _ \ /    \  \/   /  \ /  \\__  \ |  |/ /  |/    \  / ___\
 *  |    |   (  <_> |  <_> )   |  \ /\  /    Y    \/ __ \|    <|  |   |  \/ /_/  >
 *  |______  /\____/ \____/|___|  / \/  \____|__  (____  /__|_ \__|___|  /\___  /
 *         \/                   \/              \/     \/     \/       \//_____/
 *      ____.                     ___________   _____    ______________.___.
 *     |    |____ ___  _______    \_   _____/  /  _  \  /   _____/\__  |   |
 *     |    \__  \\  \/ /\__  \    |    __)_  /  /_\  \ \_____  \  /   |   |
 * /\__|    |/ __ \\   /  / __ \_  |        \/    |    \/        \ \____   |
 * \________(____  /\_/  (____  / /_______  /\____|__  /_______  / / ______|
 *               \/           \/          \/         \/        \/  \/
 */

package org.boon.validation.validators;

import org.boon.messages.MessageSpecification;
import org.boon.validation.ValidatorMessage;
import org.boon.validation.ValidatorMessageHolder;

/**
 * LongRangeValidator works with all integer ranges.
 */
public class LongRangeValidator extends AbstractRangeValidator {

    /**
     * The min value.
     */
    private Long min;
    /**
     * The max value.
     */
    private Long max;
    /* What type are we working with: Integer, Long, etc. */
    private Class<?> type;
    /* The underMin message used if the value is under the min. */
    private MessageSpecification underMin;
    /* The overMax message used if the value is over the max. */
    private MessageSpecification overMax;

    /**
     * Perform the actual validation.
     *
     * @param fieldValue the value to validate
     * @param fieldLabel the logical name of the value used for generating error messages
     */
    @SuppressWarnings ( "unchecked" )
    public ValidatorMessageHolder validate( Object fieldValue, String fieldLabel ) {
        ValidatorMessage validatorMessage = new ValidatorMessage();
        if ( fieldValue == null ) {
            return validatorMessage;
        }

        dynamicallyInitIfNeeded( fieldValue );

        if ( !super.isValueGreaterThanMin( ( Comparable ) fieldValue ) ) {
            populateMessage( underMin, validatorMessage, fieldLabel, min );
        } else if ( !super.isValueLessThanMax( ( Comparable ) fieldValue ) ) {
            populateMessage( overMax, validatorMessage, fieldLabel, max );
        }

        return validatorMessage;

    }

    /* Initialize this instance. */
    public void init() {
        /* If the underMin message was not injected, create a default. */
        if ( underMin == null ) {
            underMin = new MessageSpecification();
            underMin.setDetailMessage( "{validator.range.underMin.detail}" );
            underMin.setSummaryMessage( "{validator.range.underMin.summary}" );
        }
        /* If the overMax message was not injected, create a default. */
        if ( overMax == null ) {
            overMax = new MessageSpecification();
            overMax.setDetailMessage( "{validator.range.overMax.detail}" );
            overMax.setSummaryMessage( "{validator.range.overMax.summary" );
        }
        /* If the type was not injected, stop initialization. */
        if ( type == null ) {
            return;
        }
        /* Initialize based on type for all Integer value
         * so that LongRangeValidator can be used
    	 * for int, short, byte, and long. */
        if ( !isInitialized() ) {
            if ( type.equals( Integer.class ) ) {
                init( new Integer( min.intValue() ), new Integer( max.intValue() ) );
            } else if ( type.equals( Byte.class ) ) {
                init( new Byte( min.byteValue() ), new Byte( max.byteValue() ) );
            } else if ( type.equals( Short.class ) ) {
                init( new Short( min.byteValue() ), new Short( max.byteValue() ) );
            } else {
                init( min, max );
            }
        }
    }

    /**
     * If the type was not initialized, we can still figure it out at runtime.
     *
     * @param value
     */
    private void dynamicallyInitIfNeeded( Object value ) {
        /* Check to see if this class was already initialized,
         * if not, initialize it based on the type of the value.
		 */
        if ( !isInitialized() ) {
            if ( value instanceof Integer ) {
                init( new Integer( min.intValue() ), new Integer( max.intValue() ) );
            } else if ( value instanceof Byte ) {
                init( new Byte( min.byteValue() ), new Byte( max.byteValue() ) );
            } else if ( value instanceof Short ) {
                init( new Short( min.shortValue() ), new Short( max.shortValue() ) );
            } else {
                init( min, max );
            }
        }
    }

    public void setMax( Long max ) {
        this.max = max;
    }

    public void setMin( Long min ) {
        this.min = min;
    }

    public void setType( Class<?> type ) {
        this.type = type;
    }

    protected void setOverMax( MessageSpecification overMax ) {
        this.overMax = overMax;
    }

    protected void setUnderMin( MessageSpecification underMin ) {
        this.underMin = underMin;
    }

}