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

import org.boon.*;
import org.boon.core.reflection.*;
import org.boon.primitive.Arry;

import java.lang.reflect.Array;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.text.DateFormat;
import java.text.ParseException;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Logger;

import static org.boon.Boon.sputs;
import static org.boon.Exceptions.die;
import static org.boon.Exceptions.handle;
import static org.boon.core.Typ.getComponentType;
import static org.boon.core.Typ.isArray;

public class Conversions {

    private static final Logger log = Logger.getLogger(Conversions.class.getName());

    public static BigDecimal toBigDecimal(Object obj) {
        if (obj instanceof BigDecimal) {
            return (BigDecimal) obj;
        }

        if (obj instanceof Value) {
            return ((Value) obj).bigDecimalValue();
        } else if (obj instanceof String) {
            return new BigDecimal((String) obj);
        } else if (obj instanceof Number) {
            double val = ((Number) obj).doubleValue();
            return BigDecimal.valueOf(val);
        }

        return null;
    }

    public static BigInteger toBigInteger(Object obj) {
        if (obj instanceof BigInteger) {
            return (BigInteger) obj;
        }

        if (obj instanceof Value) {
            return ((Value) obj).bigIntegerValue();
        } else if (obj instanceof String) {
            return new BigInteger((String) obj);
        } else if (obj instanceof Number) {
            long val = ((Number) obj).longValue();
            return BigInteger.valueOf(val);
        }

        return null;

    }

    public static int toInt(Object obj) {
        return toInt(obj, Integer.MIN_VALUE);
    }

    public static int toInt(Object obj, int defaultValue) {
        if (obj.getClass() == int.class) {
            return int.class.cast(obj);
        }
        if (obj instanceof Number) {
            return ((Number) obj).intValue();
        } else if (obj instanceof Boolean || obj.getClass() == Boolean.class) {
            boolean value = toBoolean(obj);
            return value ? 1 : 0;
        } else if (obj instanceof CharSequence) {
            try {
                return StringScanner.parseInt(Str.toString(obj));
            } catch (Exception ex) {
                return defaultValue;
            }
        }
        return defaultValue;

    }

    public static byte toByte(Object obj) {
        return toByte(obj, Byte.MIN_VALUE);
    }

    public static byte toByte(Object obj, byte defaultByte) {
        if (obj.getClass() == byte.class) {
            return byte.class.cast(obj);
        } else if (obj instanceof Number) {
            return ((Number) obj).byteValue();
        } else {
            return (byte) toInt(obj, defaultByte);
        }
    }

    public static short toShort(Object obj) {
        return toShort(obj, Short.MIN_VALUE);
    }

    public static short toShort(Object obj, final short shortDefault) {

        if (obj.getClass() == short.class) {
            return short.class.cast(obj);
        } else if (obj instanceof Number) {
            return ((Number) obj).shortValue();
        } else {
            return (short) toInt(obj, shortDefault);
        }
    }

    public static char toChar(Object obj) {
        return toChar(obj, (char) 0);
    }

    public static char toChar(Object obj, final char defaultChar) {
        if (obj.getClass() == char.class) {
            return char.class.cast(obj);
        } else if (obj instanceof Character) {
            return ((Character) obj).charValue();
        } else if (obj instanceof CharSequence) {
            return obj.toString().charAt(0);
        } else if (obj instanceof Number) {
            return (char) toInt(obj);
        } else if (obj instanceof Boolean || obj.getClass() == Boolean.class) {
            boolean value = toBoolean(obj);
            return value ? 'T' : 'F';
        } else if (obj.getClass().isPrimitive()) {
            return (char) toInt(obj);
        } else {
            String str = toString(obj);
            if (str.length() > 0) {
                return str.charAt(0);
            } else {
                return defaultChar;
            }
        }
    }

    public static long toLong(Object obj) {
        return toLong(obj, Long.MIN_VALUE);
    }

    public static long toLongOrDie(Object obj) {
        long l = toLong(obj, Long.MIN_VALUE);
        if (l == Long.MIN_VALUE) {
            die("Cannot convert", obj, "into long value", obj);
        }
        return l;
    }

    public static long toLong(Object obj, final long longDefault) {

        if (obj instanceof Long) {
            return (Long) obj;
        }

        if (obj instanceof Number) {
            return ((Number) obj).longValue();
        } else if (obj instanceof CharSequence) {
            String str = Str.toString(obj);
            if (Dates.isJsonDate(str)) {
                return Dates.fromJsonDate(str).getTime();
            }

            try {
                return StringScanner.parseLong(str);
            } catch (Exception ex) {
                return longDefault;
            }
        } else if (obj instanceof Date) {
            return ((Date) obj).getTime();
        } else {
            return toInt(obj);
        }

    }

    public static Currency toCurrency(Object obj) {
        if (obj instanceof Currency) {
            return (Currency) obj;
        }

        if (obj instanceof String) {
            String str = Str.toString(obj);
            return Currency.getInstance(str);
        }

        return null;
    }

    /**
     * Converts the value to boolean.
     *
     * @param value value
     * @return value converted to boolean
     */
    public static boolean toBoolean(Object value) {
        return toBoolean(value, false);
    }

    /**
     * Converts the value to boolean, and if it is null, it uses the default value passed.
     *
     * @param obj          value to convert to boolean
     * @param defaultValue default value
     * @return obj converted to boolean
     */
    public static boolean toBoolean(Object obj, boolean defaultValue) {

        if (obj == null) {
            return defaultValue;
        }

        if (obj instanceof Boolean) {
            return ((Boolean) obj).booleanValue();
        } else if (obj instanceof Number || obj.getClass().isPrimitive()) {
            int value = toInt(obj);
            return value != 0 ? true : false;
        } else if (obj instanceof String || obj instanceof CharSequence) {
            String str = Conversions.toString(obj);
            if (str.length() == 0) {
                return false;
            }
            if (str.equals("false")) {
                return false;
            } else {
                return true;
            }
        } else if (Boon.isArray(obj)) {
            return Boon.len(obj) > 0;
        } else if (obj instanceof Collection) {

            if (len(obj) > 0) {
                List list = Lists.list((Collection) obj);
                while (list.remove(null)) {

                }
                return Lists.len(list) > 0;
            }else {
                return false;
            }
        } else {
            return toBoolean(Conversions.toString(obj));
        }
    }

    public static boolean toBooleanOrDie(Object obj) {

        if (obj == null) {
            die("Can't convert boolean from a null");
        }

        if (obj instanceof Boolean) {
            return ((Boolean) obj).booleanValue();
        } else if (obj instanceof String || obj instanceof CharSequence) {
            String str = Conversions.toString(obj);
            if (str.length() == 0) {
                return false;
            }
            if (str.equals("false")) {
                return false;
            } else if (str.equals("true")) {
                return true;
            }
            die("Can't convert string", obj, "to boolean ");
            return false;
        } else {
            die("Can't convert", obj, "to boolean ");
            return false;

        }
    }

    public static double toDouble(Object obj) {

        try {
            if (obj instanceof Double) {
                return (Double) obj;
            } else if (obj instanceof Number) {
                return ((Number) obj).doubleValue();
            } else if (obj instanceof CharSequence) {
                try {
                    return Double.parseDouble(((CharSequence) obj).toString());
                } catch (Exception ex) {
                    die(String.format("Unable to convert %s to a double", obj.getClass()));
                    return Double.MIN_VALUE;
                }
            } else {
            }
        } catch (Exception ex) {
            log.warning(String.format(
                    "unable to convert to double and there was an exception %s",
                    ex.getMessage()));
        }

        die(String.format("Unable to convert %s to a double", obj.getClass()));
        return Double.MIN_VALUE; // die throws an exception

    }

    public static float toFloat(Object obj) {
        if (obj.getClass() == float.class) {
            return (Float) obj;
        }

        try {
            if (obj instanceof Float) {
                return (Float) obj;
            } else if (obj instanceof Number) {
                return ((Number) obj).floatValue();
            } else if (obj instanceof CharSequence) {
                try {
                    return Float.parseFloat(Str.toString(obj));
                } catch (Exception ex) {
                    die(String.format("Unable to convert %s to a float", obj.getClass()));
                    return Float.MIN_VALUE;
                }
            } else {
            }
        } catch (Exception ex) {

            log.warning(String.format(
                    "unable to convert to float and there was an exception %s",
                    ex.getMessage()));
        }

        die(String.format("Unable to convert %s to a float", obj.getClass()));
        return Float.MIN_VALUE;

    }

    public static <T> T coerce(Class<T> clz, Object value) {
        if (value != null) {
            if (clz == value.getClass()) {
                return (T) value;
            }
        }

        return coerce(TypeType.getType(clz), clz, value);
    }

    public static <T> T createFromArg(Class<T> clz, Object value) {
        if (value == null) {
            return null;
        }
        ClassMeta meta = ClassMeta.classMeta(clz);
        List<ConstructorAccess> constructors = meta.oneArgumentConstructors();

        if (constructors.size() == 0) {
            return null;
        } else if (constructors.size() == 1) {
            ConstructorAccess constructorAccess = constructors.get(0);
            Class<?> arg1Type = constructorAccess.parameterTypes()[0];
            if (arg1Type.isInstance(value)) {
                return (T) constructorAccess.create(value);
            } else {
                return (T) constructorAccess.create(coerce(arg1Type, value));
            }
        } else {
            for (ConstructorAccess c : constructors) {
                Class<?> arg1Type = c.parameterTypes()[0];
                if (arg1Type.isInstance(value)) {
                    return (T) c.create(value);
                }
            }

            for (ConstructorAccess c : constructors) {
                Class<?> arg1Type = c.parameterTypes()[0];
                if (arg1Type.isAssignableFrom(value.getClass())) {
                    return (T) c.create(value);
                }
            }
        }
        return null;
    }

    public static <T> T coerce(TypeType coerceTo, Class<T> clz, Object value) {
        if (value == null) {
            if (coerceTo != TypeType.INSTANCE && !clz.isPrimitive()) {

                return null;
            } else if (clz.isPrimitive()) {
                if (clz == boolean.class) {
                    return (T) (Boolean) false;
                }
                return (T) (Number) 0;
            }
        }

        switch (coerceTo) {
            case STRING:
            case CHAR_SEQUENCE:
                return (T) value.toString();

            case NUMBER:
                if (value instanceof Number) {
                    return (T)value;
                } else {
                    Double d = toDouble(value);
                    return (T) d;
                }

            case INT:
            case INTEGER_WRAPPER:
                Integer i = toInt(value);
                return (T) i;

            case SHORT:
            case SHORT_WRAPPER:
                Short s = toShort(value);
                return (T) s;

            case BYTE:
            case BYTE_WRAPPER:
                Byte by = toByte(value);
                return (T) by;

            case CHAR:
            case CHAR_WRAPPER:
                Character ch = toChar(value);
                return (T) ch;

            case LONG:
            case LONG_WRAPPER:
                Long l = toLong(value);
                return (T) l;

            case DOUBLE:
            case DOUBLE_WRAPPER:
                Double d = toDouble(value);
                return (T) d;

            case FLOAT:
            case FLOAT_WRAPPER:
                Float f = toFloat(value);
                return (T) f;

            case DATE:
                return (T) toDate(value);

            case BIG_DECIMAL:
                return (T) toBigDecimal(value);

            case BIG_INT:
                return (T) toBigInteger(value);

            case CALENDAR:
                return (T) toCalendar(toDate(value));

            case BOOLEAN:
            case BOOLEAN_WRAPPER:
                return (T) (Boolean) toBoolean(value);

            case MAP:
                return (T) toMap(value);

            case ARRAY:
            case ARRAY_INT:
            case ARRAY_BYTE:
            case ARRAY_SHORT:
            case ARRAY_FLOAT:
            case ARRAY_DOUBLE:
            case ARRAY_LONG:
            case ARRAY_STRING:
            case ARRAY_OBJECT:
                return toPrimitiveArrayIfPossible(clz, value);

            case LIST:
            case SET:
            case COLLECTION:
                return toCollection(clz, value);

            case INSTANCE:

                if (value instanceof Value) {
                    value = ((Value)value).toValue();
                }
                if (value instanceof Map) {
                    return MapObjectConversion.fromMap((Map<String, Object>) value, clz);
                } else if (value instanceof List) {
                    return MapObjectConversion.fromList((List<Object>) value, clz);
                } else if (clz.isInstance(value)) {
                    return (T) value;
                } else {
                    return createFromArg(clz, value);
                }

            case ENUM:
                return (T) toEnum((Class<? extends Enum>) clz, value);

            case PATH:
                return (T) IO.path(value.toString());

            case CLASS:
                return (T) toClass(value);

            case TIME_ZONE:
                return (T) toTimeZone( value);

            case UUID:
                return (T) toUUID(value);

            case CURRENCY:
                return (T) toCurrency(value);

            case OBJECT:
                return (T) value;

            case HANDLER:
                return (T) value;

            default:
                return createFromArg(clz, value);
        }
    }

    private static UUID toUUID(Object value) {
        return UUID.fromString(value.toString());
    }

    public static <T> T coerceWithFlag(Class<T> clz, boolean [] flag, Object value) {

        return coerceWithFlag(TypeType.getType(clz), clz, flag, value);
    }

    public static <T> T coerceWithFlag(TypeType coerceTo, Class<T> clz, boolean [] flag, Object value) {

        flag[0] = true;
        if (value == null) {
            return null;
        }

        if (clz.isInstance(value)) {
            return (T) value;
        }

        switch (coerceTo) {
            case STRING:
            case CHAR_SEQUENCE:
                return (T) value.toString();

            case INT:
            case INTEGER_WRAPPER:
                Integer i = toInt(value);
                if (i == Integer.MIN_VALUE) {
                    flag[0] = false;
                }
                return (T) i;

            case SHORT:
            case SHORT_WRAPPER:
                Short s = toShort(value);
                if (s == Short.MIN_VALUE) {
                    flag[0] = false;
                }
                return (T) s;

            case BYTE:
            case BYTE_WRAPPER:
                Byte by = toByte(value);
                if (by == Byte.MIN_VALUE) {
                    flag[0] = false;
                }
                return (T) by;

            case CHAR:
            case CHAR_WRAPPER:
                Character ch = toChar(value);
                if (ch == (char) 0) {
                    flag[0] = false;
                }
                return (T) ch;

            case LONG:
            case LONG_WRAPPER:
                Long l = toLong(value);
                if (l == Long.MIN_VALUE) {
                    flag[0] = false;
                }

                return (T) l;

            case DOUBLE:
            case DOUBLE_WRAPPER:
                Double d = toDouble(value);
                if (d == Double.MIN_VALUE) {
                    flag[0] = false;
                }

                return (T) d;

            case FLOAT:
            case FLOAT_WRAPPER:
                Float f = toFloat(value);
                if (f == Float.MIN_VALUE) {
                    flag[0] = false;
                }
                return (T) f;

            case DATE:
                return (T) toDate(value);

            case BIG_DECIMAL:
                return (T) toBigDecimal(value);

            case BIG_INT:
                return (T) toBigInteger(value);

            case CALENDAR:
                return (T) toCalendar(toDate(value));

            case BOOLEAN:
            case BOOLEAN_WRAPPER:
                return (T) (Boolean) toBooleanOrDie(value);

            case MAP:
                return (T) toMap(value);

            case ARRAY:
            case ARRAY_INT:
            case ARRAY_BYTE:
            case ARRAY_SHORT:
            case ARRAY_FLOAT:
            case ARRAY_DOUBLE:
            case ARRAY_LONG:
            case ARRAY_STRING:
            case ARRAY_OBJECT:
                return toPrimitiveArrayIfPossible(clz, value);

            case COLLECTION:
                return toCollection(clz, value);

            case INSTANCE:
                if (value instanceof Map) {
                    return MapObjectConversion.fromMap((Map<String, Object>) value, clz);
                } else if (value instanceof List) {
                    return MapObjectConversion.fromList((List<Object>) value, clz);
                } else if (clz.isInstance(value)) {
                    return (T) value;
                } else {
                    ClassMeta meta = ClassMeta.classMeta(clz);
                    List<ConstructorAccess> constructors = meta.oneArgumentConstructors();

                    if (constructors.size() == 0) {
                        return null;
                    } else if (constructors.size() == 1) {
                        ConstructorAccess constructorAccess = constructors.get(0);
                        Class<?> arg1Type = constructorAccess.parameterTypes()[0];
                        if (arg1Type.isInstance(value)) {
                            return (T) constructorAccess.create(value);
                        } else {
                            return (T) constructorAccess.create(coerce(arg1Type, value));
                        }
                    } else {
                        for (ConstructorAccess c : constructors) {
                            Class<?> arg1Type = c.parameterTypes()[0];
                            if (arg1Type.isInstance(value)) {
                                return (T) c.create(value);
                            }
                        }

                        for (ConstructorAccess c : constructors) {
                            Class<?> arg1Type = c.parameterTypes()[0];
                            if (arg1Type.isAssignableFrom(value.getClass())) {
                                return (T) c.create(value);
                            }
                        }

                        flag[0] = false;
                        break;

                    }
                }

            case ENUM:
                return (T) toEnum((Class<? extends Enum>) clz, value);

            case CLASS:
                return (T) toClass(value);

            case TIME_ZONE:
                return (T) toTimeZone(flag, value);

            case UUID:
                return (T) toUUID(flag, value);

            case CURRENCY:
                return (T) toCurrency(value);

            case OBJECT:
                return (T) value;

            default:
                flag[0] = false;
                break;
        }
        return null;
    }

    private static UUID toUUID(boolean[] flag, Object value) {
        return toUUID(value);
    }

    public static TimeZone toTimeZone(boolean[] flag, Object value) {
        String id = value.toString();
        return TimeZone.getTimeZone(id);
    }

    public static TimeZone toTimeZone(Object value) {
        String id = value.toString();
        return TimeZone.getTimeZone(id);
    }

    @SuppressWarnings("unchecked")
    public static <T> T coerceClassic(Class<T> clz, Object value) {

        if (value == null) {
            return null;
        }

        if (clz == value.getClass()) {
            return (T) value;
        }

        if (clz == Typ.string || clz == Typ.chars) {
            return (T) value.toString();
        } else if (clz == Typ.integer || clz == Typ.intgr) {
            Integer i = toInt(value);
            return (T) i;
        } else if (clz == Typ.longWrapper || clz == Typ.lng) {
            Long l = toLong(value);
            return (T) l;
        } else if (clz == Typ.doubleWrapper || clz == Typ.dbl) {
            Double i = toDouble(value);
            return (T) i;
        } else if (clz == Typ.date) {
            return (T) toDate(value);
        } else if (clz == Typ.bigInteger) {
            return (T) toBigInteger(value);
        } else if (clz == Typ.bigDecimal) {
            return (T) toBigDecimal(value);
        } else if (clz == Typ.calendar) {
            return (T) toCalendar(toDate(value));
        } else if (clz == Typ.floatWrapper || clz == Typ.flt) {
            Float i = toFloat(value);
            return (T) i;
        } else if (clz == Typ.stringArray) {
            die("Need to fix this");
            return null;
        } else if (clz == Typ.bool || clz == Typ.bln) {
            Boolean b = toBoolean(value);
            return (T) b;
        } else if (Typ.isMap(clz)) {
            return (T) toMap(value);
        } else if (clz.isArray()) {
            return toPrimitiveArrayIfPossible(clz, value);
        } else if (Typ.isCollection(clz)) {
            return toCollection(clz, value);
        } else if (clz != null && clz.getPackage() != null && !clz.getPackage().getName().startsWith("java")
                && Typ.isMap(value.getClass()) && Typ.doesMapHaveKeyTypeString(value)) {
            return (T) MapObjectConversion.fromMap((Map<String, Object>) value);
        } else if (clz.isEnum()) {
            return (T) toEnum((Class<? extends Enum>) clz, value);

        } else {
            return null;
        }
    }

    public static <T extends Enum> T toEnumOld(Class<T> cls, String value) {
        try {
            return (T) Enum.valueOf(cls, value);
        } catch (Exception ex) {
            return (T) Enum.valueOf(cls, value.toUpperCase().replace('-', '_'));
        }
    }

    public static <T extends Enum> T toEnum(Class<T> cls, String value) {

        return toEnum(cls, value, null);
    }

    public static <T extends Enum> T toEnum(Class<T> cls, String value, Enum defaultEnum) {

        T[] enumConstants = cls.getEnumConstants();
        for (T e : enumConstants) {
            if (e.name().equals(value)) {
                return e;
            }
        }

        value = value.toUpperCase().replace('-', '_');
        for (T e : enumConstants) {
            if (e.name().equals(value)) {
                return e;
            }
        }

        value = Str.underBarCase(value);
        for (T e : enumConstants) {
            if (e.name().equals(value)) {
                return e;
            }
        }

        return (T) defaultEnum;
    }

    public static <T extends Enum> T toEnum(Class<T> cls, int value) {

        T[] enumConstants = cls.getEnumConstants();
        for (T e : enumConstants) {
            if (e.ordinal() == value) {
                return e;
            }
        }
        return null;
    }

    public static <T extends Enum> T toEnumOrDie(Class<T> cls, int value) {

        T[] enumConstants = cls.getEnumConstants();
        for (T e : enumConstants) {
            if (e.ordinal() == value) {
                return e;
            }
        }
        die("Can't convert ordinal value " + value + " into enum of type " + cls);
        return null;
    }

    public static <T extends Enum> T toEnum(Class<T> cls, Object value) {

        if (value instanceof Value) {
            return ((Value) value).toEnum(cls);
        } else if (value instanceof CharSequence) {
            return toEnum(cls, value.toString());
        } else if (value instanceof Number || value.getClass().isPrimitive()) {

            int i = toInt(value);
            return toEnum(cls, i);
        } else {

            if (value instanceof Collection) {
               return  toEnum(cls, ((Collection) value).iterator().next());
            }
            die( "Can't convert  value " + value + " into enum of type " + cls );
            return null;
        }
    }

    @SuppressWarnings("unchecked")
    public static <T> T toPrimitiveArrayIfPossible(Class<T> clz, Object value) {
        if (clz == Typ.intArray) {
            return (T) iarray(value);
        } else if (clz == Typ.byteArray) {
            return (T) barray(value);
        } else if (clz == Typ.charArray) {
            return (T) carray(value);
        } else if (clz == Typ.shortArray) {
            return (T) sarray(value);
        } else if (clz == Typ.longArray) {
            return (T) larray(value);
        } else if (clz == Typ.floatArray) {
            return (T) farray(value);
        } else if (clz == Typ.doubleArray) {
            return (T) darray(value);
        } else if (value.getClass() == clz) {
            return (T) value;
        } else {
            int index = 0;
            Object newInstance = Array.newInstance(clz.getComponentType(), Boon.len(value));
            Iterator<Object> iterator = iterator(Typ.object, value);
            while (iterator.hasNext()) {

                Object item = iterator.next();

                if (clz.getComponentType().isAssignableFrom(item.getClass())) {

                    BeanUtils.idx(newInstance, index, item);

                } else {

                    item = coerce(clz.getComponentType(), item);

                    BeanUtils.idx(newInstance, index, item);
                }
                index++;
            }
            return (T) newInstance;
        }
    }

    public static double[] darray(Object value) {
        //You could handleUnexpectedException shorts, bytes, longs and chars more efficiently
        if (value.getClass() == Typ.doubleArray) {
            return (double[]) value;
        }
        double[] values = new double[Boon.len(value)];
        int index = 0;
        Iterator<Object> iterator = iterator(Object.class, value);
        while (iterator.hasNext()) {
            values[index] = toFloat(iterator.next());
            index++;
        }
        return values;
    }

    public static float[] farray(Object value) {
        //You could handleUnexpectedException shorts, bytes, longs and chars more efficiently
        if (value.getClass() == Typ.floatArray) {
            return (float[]) value;
        }
        float[] values = new float[Boon.len(value)];
        int index = 0;
        Iterator<Object> iterator = iterator(Object.class, value);
        while (iterator.hasNext()) {
            values[index] = toFloat(iterator.next());
            index++;
        }
        return values;
    }

    public static long[] larray(Object value) {
        //You could handleUnexpectedException shorts, bytes, longs and chars more efficiently
        if (value.getClass() == Typ.shortArray) {
            return (long[]) value;
        }
        long[] values = new long[Boon.len(value)];
        int index = 0;
        Iterator<Object> iterator = iterator(Object.class, value);
        while (iterator.hasNext()) {
            values[index] = toLong(iterator.next());
            index++;
        }
        return values;
    }

    public static short[] sarray(Object value) {
        //You could handleUnexpectedException shorts, bytes, longs and chars more efficiently
        if (value.getClass() == Typ.shortArray) {
            return (short[]) value;
        }
        short[] values = new short[Boon.len(value)];
        int index = 0;
        Iterator<Object> iterator = iterator(Object.class, value);
        while (iterator.hasNext()) {
            values[index] = toShort(iterator.next());
            index++;
        }
        return values;
    }

    public static int[] iarray(Object value) {
        //You could handleUnexpectedException shorts, bytes, longs and chars more efficiently
        if (value.getClass() == Typ.intArray) {
            return (int[]) value;
        }
        int[] values = new int[Boon.len(value)];
        int index = 0;
        Iterator<Object> iterator = iterator(Object.class, value);
        while (iterator.hasNext()) {
            values[index] = toInt(iterator.next());
            index++;
        }
        return values;
    }

    public static byte[] barray(Object value) {
        //You could handleUnexpectedException shorts, ints, longs and chars more efficiently
        if (value.getClass() == Typ.byteArray) {
            return (byte[]) value;
        }
        byte[] values = new byte[Boon.len(value)];
        int index = 0;
        Iterator<Object> iterator = iterator(Object.class, value);
        while (iterator.hasNext()) {
            values[index] = toByte(iterator.next());
            index++;
        }
        return values;
    }

    public static char[] carray(Object value) {
        //You could handleUnexpectedException shorts, ints, longs and chars more efficiently
        if (value.getClass() == Typ.charArray) {
            return (char[]) value;
        }
        char[] values = new char[Boon.len(value)];
        int index = 0;
        Iterator<Object> iterator = iterator(Typ.object, value);
        while (iterator.hasNext()) {
            values[index] = toChar(iterator.next());
            index++;
        }
        return values;
    }

    @SuppressWarnings("unchecked")
    public static Iterator iterator(final Object value) {
        return iterator(null, value);
    }

    public static <T> Iterator<T> iterator(Class<T> class1, final Object value) {

        if (value == null) {
            return Collections.EMPTY_LIST.iterator();
        }

        if (Boon.isArray(value)) {
            final int length = Arry.len(value);

            return new Iterator<T>() {
                int i = 0;

                @Override
                public boolean hasNext() {
                    return i < length;
                }

                @Override
                public T next() {
                    T next = (T) BeanUtils.idx(value, i);
                    i++;
                    return next;
                }

                @Override
                public void remove() {
                }
            };
        } else if (Typ.isCollection(value.getClass())) {
            return ((Collection<T>) value).iterator();
        } else {
            return (Iterator<T>) Collections.singleton(value).iterator();
        }
    }

    @SuppressWarnings("unchecked")
    public static <T> T toCollection(Class<T> clz, Object value) {
        if (Typ.isList(clz)) {
            return (T) toList(value);
        } else if (Typ.isSortedSet(clz)) {
            return (T) toSortedSet(value);
        } else if (Typ.isSet(clz)) {
            return (T) toSet(value);
        } else {
            return (T) toList(value);
        }
    }

    @SuppressWarnings({"rawtypes", "unchecked"})
    public static List toList(Object value) {
        if (value instanceof List) {
            return (List) value;
        } else if (value instanceof Collection) {
            return new ArrayList((Collection) value);
        } else if (value == null) {
            return new ArrayList();
        } else if (value instanceof Map) {
            return new ArrayList(((Map)value).entrySet());
        }
        else {
            ArrayList list = new ArrayList(Boon.len(value));
            Iterator<Object> iterator = iterator(Typ.object, value);
            while (iterator.hasNext()) {
                list.add(iterator.next());
            }
            return list;
        }
    }

    public static Collection toCollection(Object value) {
        if (value instanceof Collection) {
            return (Collection) value;
        } else if (value == null) {
            return new ArrayList();
        } else if (value instanceof Map) {
            return ((Map)value).entrySet();
        }
        else {
            ArrayList list = new ArrayList(Boon.len(value));
            Iterator<Object> iterator = iterator(Typ.object, value);
            while (iterator.hasNext()) {
                list.add(iterator.next());
            }
            return list;
        }
    }

    @SuppressWarnings({"rawtypes", "unchecked"})
    public static Set toSet(Object value) {
        if (value instanceof Set) {
            return (Set) value;
        } else if (value instanceof Collection) {
            return new HashSet((Collection) value);
        } else {
            HashSet set = new HashSet(Boon.len(value));
            Iterator<Object> iterator = iterator(Typ.object, value);
            while (iterator.hasNext()) {
                set.add(iterator.next());
            }
            return set;
        }
    }

    @SuppressWarnings({"rawtypes", "unchecked"})
    public static SortedSet toSortedSet(Object value) {
        if (value instanceof Set) {
            return (SortedSet) value;
        } else if (value instanceof Collection) {
            return new TreeSet((Collection) value);
        } else {
            TreeSet set = new TreeSet();
            Iterator<Object> iterator = iterator(Typ.object, value);
            while (iterator.hasNext()) {
                set.add(iterator.next());
            }
            return set;
        }
    }

    public static Map<String, Object> toMap(Object value) {
        return MapObjectConversion.toMap(value);
    }

    public static Number toWrapper(long l) {
        if (l >= Integer.MIN_VALUE && l <= Integer.MAX_VALUE) {
            return toWrapper((int) l);
        } else {
            return Long.valueOf(l);
        }
    }

    public static Number toWrapper(int i) {
        if (i >= Byte.MIN_VALUE && i <= Byte.MAX_VALUE) {
            return Byte.valueOf((byte) i);
        } else if (i >= Short.MIN_VALUE && i <= Short.MAX_VALUE) {
            return Short.valueOf((short) i);
        } else {
            return Integer.valueOf(i);
        }
    }

    public static Object wrapAsObject(boolean i) {
        return Boolean.valueOf(i);
    }

    public static Object wrapAsObject(byte i) {
        return Byte.valueOf(i);
    }

    public static Object wrapAsObject(short i) {
        return Short.valueOf(i);
    }

    public static Object wrapAsObject(int i) {
        return Integer.valueOf(i);
    }

    public static Object wrapAsObject(long i) {
        return Long.valueOf(i);
    }

    public static Object wrapAsObject(double i) {
        return Double.valueOf(i);
    }

    public static Object wrapAsObject(float i) {
        return Float.valueOf(i);
    }

    public static Object toArrayGuessType(Collection<?> value) {
        Class<?> componentType = Reflection.getComponentType(value);
        Object array = Array.newInstance(componentType, value.size());
        @SuppressWarnings("unchecked")
        Iterator<Object> iterator = (Iterator<Object>) value.iterator();
        int index = 0;
        while (iterator.hasNext()) {
            BeanUtils.idx(array, index, iterator.next());
            index++;
        }
        return array;
    }

//    public static Object toArray( Class<?> componentType, Collection<?> value ) {
//        Object array = Array.newInstance( componentType, value.size() );
//        @SuppressWarnings ( "unchecked" )
//        Iterator<Object> iterator = ( Iterator<Object> ) value.iterator();
//        int index = 0;
//        while ( iterator.hasNext() ) {
//            BeanUtils.idx ( array, index, iterator.next () );
//            index++;
//        }
//        return array;
//    }

    public static <T> T[] toArray(Class<T> componentType, Collection<T> collection) {
        T[] array = (T[]) Array.newInstance(componentType, collection.size());

        if (componentType.isAssignableFrom(getComponentType(collection))) {
            return collection.toArray(array);
        } else {

            int index = 0;
            for (Object o : collection) {
                array[index] = Conversions.coerce(componentType, o);
                index++;
            }
            return array;
        }
    }

//    public static <V> V[] array( Class<V> type, final Collection<V> array ) {
//        return ( V[] ) Conversions.toArray( type, array );
//    }

    public static <V> V[] array(Class<V> type, final Collection<V> array) {
        return Conversions.toArray(type, array);
    }

    public static Date toDate(Object object) {

        if (object instanceof Date) {
            return (Date) object;
        } else if (object instanceof Value) {
            return ((Value) object).dateValue();
        } else if (object instanceof Calendar) {
            return ((Calendar) object).getTime();
        } else if (object instanceof Long) {
            return new Date((long) object);
        } else if (object instanceof String) {
            String val = (String) object;
            char[] chars = FastStringUtils.toCharArray(val);
            if (Dates.isISO8601QuickCheck(chars)) {
                return Dates.fromISO8601DateLoose(chars);
            } else {
                return toDateUS(val);
            }
        }
        return null;
    }

    public static Calendar toCalendar(Date date) {

        final Calendar calendar = Calendar.getInstance();
        calendar.setTime(date);
        return calendar;

    }

    public static Date toDate(Calendar c) {
        return c.getTime();

    }

    public static Date toDate(long value) {
        return new Date(value);
    }

    public static Date toDate(Long value) {
        return new Date(value);
    }

    public static Date toDate(String value) {
        try {
            return toDateUS(value);
        } catch (Exception ex) {
            try {
                return DateFormat.getDateInstance(DateFormat.SHORT).parse(value);
            } catch (ParseException e) {
                die("Unable to parse date");
                return null;
            }

        }
    }

    public static Date toDateUS(String string) {

        String[] split = StringScanner.splitByChars(string, new char[]{'.', '\\', '/', ':'});

        if (split.length == 3) {
            return Dates.getUSDate(toInt(split[0]), toInt(split[1]), toInt(split[2]));
        } else if (split.length >= 6) {
            return Dates.getUSDate(toInt(split[0]), toInt(split[1]), toInt(split[2]),
                    toInt(split[3]), toInt(split[4]), toInt(split[5])
            );
        } else {
            die(String.format("Not able to parse %s into a US date", string));
            return null;
        }

    }

    public static Date toEuroDate(String string) {

        String[] split = StringScanner.splitByChars(string, new char[]{'.', '\\', '/', ':'});

        if (split.length == 3) {
            return Dates.getEuroDate(toInt(split[0]), toInt(split[1]), toInt(split[2]));
        } else if (split.length >= 6) {
            return Dates.getEuroDate(toInt(split[0]), toInt(split[1]), toInt(split[2]),
                    toInt(split[3]), toInt(split[4]), toInt(split[5])
            );
        } else {
            die(String.format("Not able to parse %s into a Euro date", string));
            return null;
        }

    }

    public static Collection<Object> createCollection(Class<?> type, int size) {

        if (type == List.class) {
            return new ArrayList<>(size);
        } else if (type == SortedSet.class) {
            return new TreeSet<>();
        } else if (type == Set.class) {
            return new LinkedHashSet<>(size);
        } else if (Typ.isList(type)) {
            return new ArrayList<>();
        } else if (Typ.isSortedSet(type)) {
            return new TreeSet<>();
        } else if (Typ.isSet(type)) {
            return new LinkedHashSet<>(size);
        } else {
            return new ArrayList(size);
        }

    }

    public static Map<?, ?> createMap(Class<?> type, int size) {

        if (type == HashMap.class) {
            return new HashMap<>(size);
        } else if (type == TreeMap.class) {
            return new TreeMap<>();
        } else if (type == SortedMap.class) {
            return new TreeMap<>();
        } else if (type == ConcurrentHashMap.class) {
            return new ConcurrentHashMap<>();
        } else {
            return new HashMap(size);
        }

    }

    public static <TO, FROM> List<TO> mapFilterNulls(Function<FROM, TO> converter,
                                                     Collection<FROM> fromCollection) {

        ArrayList<TO> toList = new ArrayList<>(fromCollection.size());

        for (FROM from : fromCollection) {
            TO converted = converter.apply(from);
            if (converted != null) {
                toList.add(converted);
            }
        }

        return toList;
    }

    public static Object unifyListOrArray(Object o) {
        return unifyListOrArray(o, null);
    }

    /**
     * This flattens a list.
     * @param o object that might be a list
     * @param list list to add o to or all of o's items to.
     * @return an object or a list
     */
    public static Object unifyListOrArray(Object o, List list) {

        if (o==null) {
            return null;
        }

        boolean isArray = o.getClass().isArray();

        if (list == null && !isArray && !(o instanceof Iterable)) {
            return o;
        }

        if (list == null) {
            list = new LinkedList();
        }

        if (isArray) {
            int length = Array.getLength( o );

            for (int index = 0; index < length; index++) {

                Object o1 = Array.get(o, index);
                if (o1 instanceof Iterable || o.getClass().isArray()) {
                    unifyListOrArray(o1, list);
                } else {
                    list.add(o1);
                }
            }
        } else if (o instanceof Collection) {

            Collection i = ((Collection) o);

            for (Object item : i) {

                if (item instanceof Iterable || o.getClass().isArray()) {
                    unifyListOrArray(item, list);
                } else {
                    list.add(item);
                }

            }

        } else {

            list.add(o);
        }

        return list;

    }

    public static Object unifyList(List list) {
        return unifyListOrArray(list, null);
    }

    /**
     * This flattens a list.
     * @param o object that might be a list
     * @param list list to add o to or all of o's items to.
     * @return an object or a list
     */
    public static Object unifyList(Object o, List list) {

        if (o==null) {
            return null;
        }

        if (list == null) {
            list = new ArrayList();
        }

        if (o instanceof Iterable) {
            Iterable i = ((Iterable) o);

            for (Object item : i) {

               unifyListOrArray(item, list);

            }

        } else {
            list.add(o);
        }

        return list;

    }

    /**
     * Cast an object to a comparable object.
     *
     * @param comparable
     * @return
     */
    public static Comparable comparable(Object comparable) {
        return (Comparable) comparable;
    }

    public Number coerceNumber(Object inputArgument, Class<?> paraType) {
        Number number = (Number) inputArgument;
        if (paraType == int.class || paraType == Integer.class) {
            return number.intValue();
        } else if (paraType == double.class || paraType == Double.class) {
            return number.doubleValue();
        } else if (paraType == float.class || paraType == Float.class) {
            return number.floatValue();
        } else if (paraType == short.class || paraType == Short.class) {
            return number.shortValue();
        } else if (paraType == byte.class || paraType == Byte.class) {
            return number.byteValue();
        }
        return null;
    }

    public static int lengthOf(Object obj) {
        return len(obj);
    }

    public static int len(Object obj) {
        if (isArray(obj)) {
            return Arry.len(obj);
        } else if (obj instanceof CharSequence) {
            return ((CharSequence) obj).length();
        } else if (obj instanceof Collection) {
            return ((Collection<?>) obj).size();
        } else if (obj instanceof Map) {
            return ((Map<?, ?>) obj).size();
        } else if (obj == null) {
            return 0;
        } else {
            return 1;
        }

    }

    public static Class<?> toClass(Object value) {

        if (value instanceof Class || value == null) {
            return (Class<?>) value;
        }
        return toClass(value.toString());
    }

    public static Class<?> toClass(String str) {

        try {
            return Class.forName(str);
        } catch (ClassNotFoundException ex) {
            return (Class<?>) handle(Object.class, ex);
        }
    }

    public static String toString(Object obj, String defaultValue) {

        return (obj == null) ? defaultValue : obj.toString();

    }

    public static String toString(Object obj) {
        return (obj == null) ? "" : obj.toString();

    }
}


package org.boon.slumberdb.stores.log;

import org.boon.slumberdb.service.config.DataStoreConfig;
import org.boon.IO;
import org.boon.core.Sys;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;

import static org.boon.Boon.*;

/**
 * Created by Richard on 6/27/14.
 */
public class CollectorManager implements TimeAware {

    /**
     * Force flush if outputDataQueue is empty after this many mili-seconds.
     */
    private final static long FORCE_FLUSH_AFTER_THIS_MANY_MILI_SECONDS
            = Sys.sysProp(
            "NFL.USER_DATA_TRACKER_SERVER.FORCE_FLUSH_AFTER_THIS_MANY_MILI_SECONDS", 40);

    private final static boolean RECYCLE_BUFFER
            = Sys.sysProp(
            "NFL.USER_DATA_TRACKER_SERVER.RECYCLE_BUFFER", true);
    /**
     * Periodic force flush. We can turn off periodic flushing and allow the OS
     * to decide best time to sync to disk for speed.
     * (Not much difference in speed on OSX).
     */
    private final static boolean PERIODIC_FORCE_FLUSH
            = Sys.sysProp(
            "NFL.USER_DATA_TRACKER_SERVER.PERIODIC_FORCE_FLUSH", true);
    /**
     * Determines if we should see if the writer is busy before batching up a lot of
     * data. Turning this off helps with throughput at the expense of data safety.
     */
    private final static boolean TRANSFER_QUEUE_WRITER_WAITING_CHECK
            = Sys.sysProp(
            "NFL.USER_DATA_TRACKER_SERVER.TRANSFER_QUEUE_WRITER_WAITING_CHECK", true);
    /**
     * How often should we report status?
     */
    protected static int MONITOR_INTERVAL_SECONDS
            = Sys.sysProp(
            "NFL.USER_DATA_TRACKER_SERVER.MONITOR_INTERVAL_SECONDS", 600);
    /**
     * Byte Buffers that are done are put back on this outputDataQueue.
     */
    private final TransferQueue<ByteBuffer> recycleChannel = new LinkedTransferQueue<>();
    /**
     * Byte buffers that have been received from HTTP post or Event Bus, but not written to disk.
     */
    private final TransferQueue<ByteBuffer> inputChannel = new LinkedTransferQueue<>();
    /**
     * BatchFileWriter is used to write out batches of data at once.
     */
    private final BatchFileWriter writer = new BatchFileWriter();
    int monitorCount = 0;
    /**
     * Main thread scheduler.
     */
    private ScheduledExecutorService scheduledExecutorService;
    /**
     * How many times have we done a flush.
     */
    private AtomicLong numberOfFlushesTotal = new AtomicLong();
    /**
     * current time, which we get every 20 mili-seconds.
     */
    private AtomicLong time = new AtomicLong();
    /**
     * Request main thread to stop.
     */
    private AtomicBoolean stop = new AtomicBoolean();
    /**
     * Holds the writerFuture for shutdown.
     */
    private ScheduledFuture<?> writerFuture;
    /**
     * The last time we forced a sync to disk.
     */
    private long lastFlushTime = 0;
    /**
     * Request main thread to stop.
     */
    private AtomicBoolean recoverMode = new AtomicBoolean();
    /**
     * Health monitor Future.
     */
    private ScheduledFuture<?> monitorFuture;
    /**
     * 20 ms timer to reduce expensive calls to System.nanoSecond.
     */
    private ScheduledFuture<?> tickTock;
    private DataStoreConfig config;

    //Create with only six threads max.
    public CollectorManager() {

        scheduledExecutorService = Executors.newScheduledThreadPool(6);
    }

    public CollectorManager(ScheduledExecutorService service) {
        scheduledExecutorService = service;
    }

    /**
     * This gets called by the http post handler or event bus handler.
     */
    public final void offer(ByteBuffer batch) {
        batch.flip();

//        try {
//
//                inputChannel.transfer(batch);
//        } catch (InterruptedException ie) {
//            Thread.interrupted();
        inputChannel.offer(batch);
//        }

    }

    /**
     * This gets called from the http post handler  or event bus handler.
     *
     * @param size the size of the buffer that you would like.
     * @return
     */
    public final ByteBuffer allocateBuffer(int size) {

        if (RECYCLE_BUFFER) {
            ByteBuffer spentBuffer = recycleChannel.poll();
            if (spentBuffer == null) {
                spentBuffer = ByteBuffer.allocateDirect(size);
            }
            spentBuffer.clear();
            return spentBuffer;
        } else {
            return ByteBuffer.allocateDirect(size);
        }
    }

    /**
     * This checks to see if the output outputDataQueue is waiting.
     * We don't want the output outputDataQueue to wait, but we also
     * don't want it to thread sync too much either.
     */
    public final boolean isWriterWaiting() {
        // This call causes us to lose about 5% write throughput
        // it has the advantage of reducing loss of buffered input data
        // in the very rare occurrence of an outage.
        return TRANSFER_QUEUE_WRITER_WAITING_CHECK &&
                inputChannel.hasWaitingConsumer();
    }

    /**
     * This is the main processing loop for the batch writer processing.
     */
    private void processWrites() {

        while (true) {
            try {

                manageInputWriterChannel();

            } catch (InterruptedException e) {

                if (determineIfWeShouldExit()) {
                    break;
                }
            }

        }

    }

    /**
     * See if it is time to stop
     * We have been interrupted. Should we ignore it or break out of the loop.
     *
     * @return
     */
    private boolean determineIfWeShouldExit() {
        boolean shouldStop = stop.get();
        if (!shouldStop) {
            Thread.interrupted();
        } else {
            System.out.println("Exiting processing loop as requested");
            return true;
        }
        return false;
    }

    /**
     * Queue and batch writer main logic.
     * This is where the magic happens.
     *
     * @throws InterruptedException
     */
    private void manageInputWriterChannel() throws InterruptedException {

        try {

            ByteBuffer dataToWriteToFile;
            dataToWriteToFile = inputChannel.poll();  //no wait

            //If it is null, it means the inputChannel is empty and we need to flush.
            if (dataToWriteToFile == null) {
                queueEmptyMaybeFlush();
                dataToWriteToFile = inputChannel.poll();
            }

            //If it is still null, this means that we need to wait
            //for more items to show up in the inputChannel.
            if (dataToWriteToFile == null) {
                dataToWriteToFile = waitForNextDataToWrite();
            }

            //We have to check for null again because we could have been interrupted.
            if (dataToWriteToFile != null) {
                //Write it
                writer.nextBufferToWrite(dataToWriteToFile);
                //Then give it back
                if (RECYCLE_BUFFER) {
                    recycleChannel.offer(dataToWriteToFile);
                }

            }

        } catch (InterruptedException ex) {
            throw ex;
        } catch (Exception ex) {
            ex.printStackTrace();
            ex.printStackTrace(System.err);
        }

    }

    /**
     * If we detect that the in-coming transfer outputDataQueue channel is empty
     * then it could be an excellent time to sync to disk.
     */
    private void queueEmptyMaybeFlush() {
        if (PERIODIC_FORCE_FLUSH) {
            long currentTime = time.get();
            /* Try not to flush more than once every x times per mili-seconds time period. */
            if ((currentTime - lastFlushTime) > FORCE_FLUSH_AFTER_THIS_MANY_MILI_SECONDS) {

                /* If the writer had things to flush, and we flushed then
                increment the number of flushes.
                 */
                if (writer.syncToDisk()) { //could take 100 ms to 1 second
                    this.numberOfFlushesTotal.incrementAndGet();
                }
                /* We update the flush time no matter what. */
                lastFlushTime = time.get();
            }
        }
    }

    /**
     * If we don't have any data, and we have flushed,
     * then we can wait on the outputDataQueue. There is no sense spin-locking.
     * The poll(time, timeunit) call will block until there is something to do
     * or until the timeout.
     *
     * @return the next byte buffer.
     * @throws InterruptedException
     */
    private ByteBuffer waitForNextDataToWrite() throws InterruptedException {
        ByteBuffer dataToWriteToFile;

        dataToWriteToFile =
                inputChannel.poll(FORCE_FLUSH_AFTER_THIS_MANY_MILI_SECONDS,
                        TimeUnit.MILLISECONDS);

        return dataToWriteToFile;
    }

    /**
     * Start up the health monitor.
     */
    private void startMonitor() {

        final ScheduledExecutorService monitor = Executors.newScheduledThreadPool(2,
                new ThreadFactory() {
                    @Override
                    public Thread newThread(Runnable runnable) {
                        Thread thread = new Thread(runnable);
                        thread.setPriority(Thread.NORM_PRIORITY + 1);
                        return thread;
                    }
                }
        );

        monitorFuture = monitor.scheduleAtFixedRate(new Runnable() {
            @Override
            public void run() {
                monitor();
            }
        }, MONITOR_INTERVAL_SECONDS, MONITOR_INTERVAL_SECONDS, TimeUnit.SECONDS);

        Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
            @Override
            public void run() {
                System.err.println("shutting down....");
                monitor();
            }
        }));

    }

    private void monitor() {

        if (recoverMode.get()) {
            check("outputDir", this.writer.outputDir());
        }

        //monitor runtime state... TBD
        //Health check of system.
        putl("Monitor:",
                sputs("    total bytes transferred:          ", String.format("%,d", this.writer.totalBytesTransferred())),
                sputs("    avg buffer size :       ", String.format("%,d", this.writer.averageBufferSize())),
                sputs("    numberOfFlushesTotal:             ", this.numberOfFlushesTotal.get()),
                sputs("    input inputChannel size:          ", this.inputChannel.size()),
                sputs("    recycle inputChannel size:        ", this.recycleChannel.size()),
                sputs("    bytes transferred for log:       ", String.format("%,d", this.writer.bytesTransferred())),
                sputs("    current log                      ", this.writer.fileName())
        );

        monitorCount++;

        if (monitorCount % 5 == 0) {
            String outputDir = this.writer.outputDir();

            check("output directory", outputDir);

        }

    }

    private void check(String description, String fileName) {

        try {
            Path path = IO.path(fileName);

            if (!Files.isWritable(path) || !Files.exists(path)) {
                writer.setError();
                Exception ex = new IOException();
                ex.fillInStackTrace();
                ex.printStackTrace(System.err);
                puts("Unable to write to ", fileName, "which is the", description);
                this.writer.diagnose();
                System.out.flush();
                System.err.flush();

                recoverMode.set(true);

            } else {
                recoverMode.set(false);
            }
        } catch (Exception ex) {
            ex.printStackTrace(System.err);
        }
    }

    public void stop() {

        stop.set(true);
        writerFuture.cancel(true);
        monitorFuture.cancel(true);
        tickTock.cancel(true);

    }

    /**
     * Starts up the batch writer.
     */
    public void start(final TimeAware receiver) {

        //This starts itself up again every 1/2 second if something really bad
        //happens like disk full. As soon as the problem gets corrected
        //then things start working again...happy day.    Only
        // one is running per instance of CollectionManagerImpl.
        writerFuture =
                scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
                    @Override
                    public void run() {
                        processWrites();
                    }
                }, 0, 500, TimeUnit.MILLISECONDS);

        startMonitor();

        tickTock =
                this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
                    @Override
                    public void run() {

                        long time = System.nanoTime() / 1_000_000;
                        if (receiver != null) {
                            receiver.tick(time);
                        }
                        tick(time);

                    }
                }, 0, 20, TimeUnit.MILLISECONDS);

    }

    /**
     * nano time cost 100 nano seconds to call.
     * System.currentTimeMilis is not accurate (day light saving time shift)
     * We want the speed of System.currentTimeMilis and the accuracy of
     * System.nanoTime w/o the overhead so we call nano time
     * every 20 miliseconds. and store the results in an atomic.
     */
    @Override
    public final void tick(long time) {
        this.time.set(time);
        this.writer.tick(time);
    }

    public void init(DataStoreConfig config) {
        this.config = config;
        writer.init(config);
    }
}


/*
 * 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.value;

import org.boon.core.Sys;
import org.boon.core.Value;
import org.boon.primitive.Arry;

import java.util.*;

import static org.boon.Exceptions.die;

/**
 * This class is important to the performance of the parser.
 * It stores Value objects in a map where they are evaluated lazily.
 * This is great for JSONPath types of application, and Object Serialization but not for maps that are going to be stored in a cache.
 *
 * This is because the Value construct is a type of index overlay that merely tracks where the token is located in the buffer,
 * and what if any thing we noted about it (like can be converted to a decimal number, etc.).
 *
 * To mitigate memory leaks this class along with CharSequenceValue implement two constructs, namely,
 * chop,  and lazyChop.
 *
 * A chop is when we convert backing buffer of a Value object into a smaller buffer.
 * A lazyChop is when we do a chop but only when a get operation is called.
 *
 * The lazyChop is performed on the tree that is touched by the JSONPath expression or its ilk.
 *
 * The chop operation can be done during parsing or lazily by storing the values in this construct.
 *
 */
public class LazyValueMap extends AbstractMap<String, Object> implements ValueMap <String, Object> {

    /** holds the map that gets lazily created on first access. */
    private Map<String, Object> map = null;
    /** holds the list of items that we are managing. */
    private  Entry<String, Value>[] items;
    /** Holds the current number mapping managed by this map. */
    private int len = 0;
    /** Holds whether or not we ae in lazy chop mode or not. */
    private final boolean lazyChop;

    /** Keep track if this map has already been chopped so we don't waste time trying to chop it again. */
    boolean mapChopped = false;

    public LazyValueMap( boolean lazyChop ) {

        this.items = new Entry[ 5 ];
        this.lazyChop = lazyChop;
    }

    public LazyValueMap( boolean lazyChop, int initialSize ) {
        this.items = new Entry[ initialSize ];
        this.lazyChop = lazyChop;
    }

    /** Adds a new MapItemValue to the mapping.
     *
     * @param miv miv we are adding.
     */
    public final void add( MapItemValue miv ) {
        if ( len >= items.length ) {
            items = Arry.grow(items);
        }
        items[ len ] = miv;
        len++;

    }

    /** Gets the item by key from the mapping.
     *
     * @param key to lookupWithDefault
     * @return
     */
    @Override
    public final Object get( Object key ) {

        Object object=null;

        /* if the map is null, then we create it. */
        if ( map == null ) {
                buildMap ();
        }
        object = map.get ( key );

        lazyChopIfNeeded ( object );

        return object;
    }

    /** If in lazy chop mode, and the object is a Lazy Value Map or a ValueList
     * then we force a chop operation for each of its items. */
    private void lazyChopIfNeeded( Object object ) {
        if ( lazyChop ) {
            if ( object instanceof LazyValueMap ) {
                LazyValueMap m = ( LazyValueMap ) object;
                m.chopMap();
            } else if ( object instanceof ValueList ) {
                ValueList list = ( ValueList ) object;
                list.chopList();
            }
        }

    }

    /** Chop this map.
     *
     */
    public final void chopMap() {
        /* if it has been chopped then you have to return. */
        if ( mapChopped ) {
            return;
        }
        mapChopped = true;

        /* If the internal map was not create yet, don't. We can chop the value w/o creating the internal map.*/
        if ( this.map == null ) {
            for ( int index = 0; index < len; index++ ) {
                MapItemValue entry = ( MapItemValue ) items [index] ;

                Value value = entry.getValue();
                if ( value == null ) continue;
                if ( value.isContainer() ) {
                    chopContainer( value );
                } else {
                    value.chop();
                }
            }
        } else {
            /* Iterate through the map and do the same thing. Make sure children and children of children are chopped.  */
            for (Map.Entry <String, Object> entry : map.entrySet ()) {

                Object object = entry.getValue();
                if ( object instanceof  Value ) {
                    Value value = (Value) object;
                    if ( value.isContainer() ) {
                        chopContainer( value );
                    } else {
                        value.chop();
                    }
                } else if (object instanceof LazyValueMap) {
                    LazyValueMap m = ( LazyValueMap ) object;
                    m.chopMap();
                } else if ( object instanceof ValueList ) {
                    ValueList list = ( ValueList ) object;
                    list.chopList();
                }
            }
        }

    }

    /* We need to chop up this child container. */
    private void chopContainer( Value value ) {
        Object obj = value.toValue();
        if ( obj instanceof LazyValueMap ) {
            LazyValueMap map = ( LazyValueMap ) obj;
            map.chopMap();
        } else if ( obj instanceof ValueList ) {
            ValueList list = ( ValueList ) obj;
            list.chopList();
        }
    }

    @Override
    public Object put( String key, Object value ) {

        if ( map == null ) {
            buildMap();
        }

        return map.put(key, value);

    }

    @Override
    public Set<Entry<String, Object>> entrySet() {
        if ( map == null ) {
            buildMap();
        }
        return map.entrySet();
    }

    final static boolean althashingThreshold = System.getProperty("jdk.map.althashing.threshold") != null;

    private final void buildMap() {

        /** added to avoid hash collision attack. */
        if (Sys.is1_7OrLater() && althashingThreshold) {
            map = new HashMap<>( items.length );
        } else {
            map = new TreeMap<>();
        }

        for ( Entry<String, Value> miv : items ) {
            if ( miv == null ) {
                break;
            }
            map.put( miv.getKey(), miv.getValue().toValue() );
        }

        len = 0;
        items = null;
    }

    public Collection<Object> values() {
        if ( map == null ) buildMap();
        return map.values();
    }

    public int size() {
       if (map == null) buildMap();
       return map.size();
    }

    public String toString() {

        if (map == null) buildMap();
        return map.toString();

    }

    public int len() {
        return len;
    }

    public boolean hydrated() {
        return map!=null;
    }

    public Entry<String, Value>[] items() {
        return items;
    }

}


/*
 * 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.Boon;
import org.boon.validation.ValidatorMessage;
import org.boon.validation.ValidatorMessageHolder;

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

    /**
     * The min value.
     */
    private int min = 0;
    /**
     * The max value.
     */
    private int max = Integer.MAX_VALUE;

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

        int len = Boon.len( fieldValue );

        if ( !( len >= min && len <= max ) ) {
            populateMessage( validatorMessage, fieldLabel, min, max );
        }

        return validatorMessage;

    }

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

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

}


/*
 * 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.datarepo.impl;

import org.boon.Sets;
import org.boon.core.Conversions;
import org.boon.core.reflection.fields.FieldAccess;
import org.boon.criteria.ObjectFilter;
import org.boon.criteria.Criterion;
import org.boon.criteria.internal.*;
import org.boon.datarepo.Filter;
import org.boon.datarepo.LookupIndex;
import org.boon.datarepo.ResultSet;
import org.boon.datarepo.SearchableCollection;
import org.boon.datarepo.spi.FilterComposer;
import org.boon.datarepo.spi.ResultSetInternal;
import org.boon.datarepo.spi.SearchIndex;

import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;

import static org.boon.criteria.ObjectFilter.instanceOf;
import static org.boon.criteria.ObjectFilter.not;

/**
 * This class should be renamed mother of all beasts.
 * This class is the reason I have no hair.
 * It implements the first cut of a decent criteria plan.
 *
 * @author Rick Hightower
 */
public class FilterDefault implements Filter, FilterComposer {

    private Set<Operator> indexedOperators =
            Sets.set( Operator.BETWEEN, Operator.EQUAL, Operator.STARTS_WITH,
                    Operator.GREATER_THAN, Operator.GREATER_THAN_EQUAL,
                    Operator.LESS_THAN, Operator.LESS_THAN_EQUAL );

    private Map<String, FieldAccess> fields;
    private SearchableCollection searchableCollection;
    private Map<String, SearchIndex> searchIndexMap;
    private Map<String, LookupIndex> lookupIndexMap;

    /**
     * Seems innocent enough. Give me some criteria expressions,
     * and i will give you a nice results set.
     *
     * @param expressions listStream of expressions
     * @return result set
     * @see ResultSet
     * @see org.boon.criteria.internal.Criteria
     */
    @Override
    public ResultSet filter( Criteria... expressions ) {
        try {
            return mainQueryPlan( expressions );
        } finally {
        }
    }

    /**
     * This is the main criteria plan in case the name was not
     * obvious.
     *
     * @param expressions
     * @return
     * @author Rick Hightower
     */
    private ResultSet mainQueryPlan( Criteria[] expressions ) {

        ResultSetInternal results = new ResultSetImpl( this.fields );

        if (expressions == null || expressions.length == 0) {
            results.addResults ( searchableCollection.all() );
        }

        /* I am sure this looked easy to read when I wrote it.
         * If there is only one expression and first expression is a group then
         * the group is that first expression otherwise wrap
         * all of the expressions in an and clause. */
        Group group = expressions.length ==
                1 && expressions[ 0 ] instanceof Group
                ? ( Group ) expressions[ 0 ] : ObjectFilter.and( expressions );

        /**
         * Run the filter on the group.
         */
        doFilterGroup( group, results );

        return results;
    }

    private void orPlanWithIndex( Criterion criterion, ResultSetInternal results ) {

        Operator operator = criterion.getOperator();
        if ( operator == Operator.EQUAL && lookupIndexMap.get( criterion.getName() ) != null ) {
            doFilterWithIndex( criterion, fields, results );
        } else if ( this.isIndexed( criterion.getName() ) && Sets.in( operator, indexedOperators ) ) {
            doFilterWithIndex( criterion, fields, results );
        } else {
            List list = QueryFactory.filter( this.searchableCollection.all(), criterion );
            results.addResults( list );
        }

    }

    @Override
    public void invalidate() {

    }

    /**
     * Run the filter on the group.
     *
     * @param group   here is the group
     * @param results here are the results
     */
    private void doFilterGroup( Group group, ResultSetInternal results ) {
        /* The group was n or group so handle it that way. */
        if ( group.getGrouping() == Grouping.OR ) {
            /* nice short method name, or. */
            or( group.getExpressions(), fields, results );
        } else {
            /* create a result internal (why?), wrap the fields in the result set
            internal, and pass that to the and method.
             */
            ResultSetInternal resultsForAnd = new ResultSetImpl( fields );
            and( group.getExpressions(), fields, resultsForAnd );
            results.addResults( resultsForAnd.asList() );
        }
    }

    private void or( List<Criteria> expressions,
                     Map<String, FieldAccess> fields, ResultSetInternal results ) {

        for ( Criteria expression : expressions ) {
            if ( expression instanceof Criterion ) {
                orPlanWithIndex( ( Criterion ) expression, results );
            } else if ( expression instanceof Group ) {
                doFilterGroup( ( Group ) expression, results );
            }
        }
    }

    private void and( List<Criteria> expressions, Map<String, FieldAccess> fields, ResultSetInternal resultSet ) {

        Set<Criteria> expressionSet = Sets.set( expressions );

        boolean foundIndex = applyIndexedFiltersForAnd( expressions, fields, expressionSet, resultSet );

        resultSet.andResults();
        applyLinearSearch(expressionSet, resultSet, foundIndex);
        applyGroups( expressionSet, resultSet );

    }

    private boolean applyIndexedFiltersForAnd( List<Criteria> expressions, Map<String, FieldAccess> fields, Set<Criteria> expressionSet, ResultSetInternal resultSet ) {
        Criterion criteria = null;
        boolean foundIndex = false;

        if ( expressions.size() == 1 && expressions.get(0) instanceof Criterion ) {
            criteria = ( Criterion ) expressions.get(0);
            foundIndex = doFilterWithIndex( criteria, fields, resultSet );
            if ( foundIndex ) {
                expressionSet.remove( criteria );
            }

            return foundIndex;
        }

        int foundCount =0 ;

        for ( Criteria expression : expressions ) {
            /*
             * See if the criteria has an index
             */
            if ( expression instanceof Criterion ) {
                criteria = ( Criterion ) expression;

                if (doFilterWithIndex( criteria, fields, resultSet )) {
                    foundCount++;
                }

            }
        }
        return foundCount > 0;
    }

//    private List applyGroupsWithIndexesForAnd(List items, Set<Query> expressionSet) {
//
//        List<HashSet> listOfSets = new ArrayList();
//        listOfSets.addObject(new HashSet(items));
//
//        List<Query> expressionsWeEvaluated = new ArrayList<>();
//
//        outer:
//        for (Query expression : expressionSet) {
//
//            if (expression instanceof Group) {
//                Group group = (Group) expression;
//                for (Query innerExpression : group.getExpressions()) {
//                    //Don't allow non-index Criterion to avoid too many scans
//                    if (innerExpression instanceof Criterion) {
//                        Criterion c = (Criterion) innerExpression;
//                        if (!this.isIndexed(c.name())) {
//                            continue outer;
//                        }
//                    }
//                    //Don't allow any ors to avoid long scans, at this point
//                    //This is simple for now, it does not recusively look for indexes, future one should.
//                    else if (innerExpression instanceof Group) {
//                        continue;
//                    }
//                }
//
//
//                /*
//                At this point, this group should be indexed only
//                 */
//                List listStream = doFilterGroup((Group) expression);
//                if (listStream.size() > 0) {
//                    listOfSets.addObject(new HashSet(listStream));
//                    expressionsWeEvaluated.addObject(expression);
//                }
//            }
//        }
//        List results = reduceToResults(listOfSets);
//        expressionSet.removeAll(expressionsWeEvaluated);
//
//        return results;
//    }

    private void applyGroups( Set<Criteria> expressionSet, ResultSetInternal resultSet ) {

        if ( expressionSet.size() == 0 ) {
            return;
        }

        for ( Criteria expression : expressionSet ) {

            if ( expression instanceof Group ) {
                doFilterGroup( ( Group ) expression, resultSet );
            }
        }
    }

    private void applyLinearSearch( Set<Criteria> expressionSet, ResultSetInternal resultSet, boolean foundIndex ) {

        if ( expressionSet.size() == 0 ) {
            return;
        }

        Criteria[] expressions = Conversions.array( Criteria.class, QueryFactory.filter( expressionSet, not( instanceOf( Group.class ) ) ) );

        if ( foundIndex ) {
            resultSet.filterAndPrune( ObjectFilter.and( expressions ) );
        } else {
            resultSet.addResults(
                    QueryFactory.filter( searchableCollection.all(),
                            ObjectFilter.and( expressions ) )
            );
        }
        for ( Criteria expression : expressions ) {
            expressionSet.remove( expression );
        }

    }

    private boolean isIndexed( String name ) {
        return searchIndexMap.containsKey( name );
    }

    private boolean doFilterWithIndex( Criterion criterion, Map<String, FieldAccess> fields, ResultSetInternal resultSet ) {

        boolean indexed = indexedOperators.contains( criterion.getOperator() );

        if ( !indexed ) {
            return false;
        }

        String name = criterion.getName();
        Object value = criterion.getValue();
        Operator operator = criterion.getOperator();
        SearchIndex searchIndex = searchIndexMap.get( name );
        LookupIndex lookupIndex = lookupIndexMap.get( name );
        List resultList = null;
        boolean foundIndex;

        if ( lookupIndex != null && operator == Operator.EQUAL ) {
            foundIndex = true;
            resultList = lookupIndex.getAll( value );
            if ( resultList != null ) {
                resultSet.addResults( resultList );
                return foundIndex;
            } else {
                resultSet.addResults( Collections.EMPTY_LIST );
                return foundIndex;
            }
        }

        if ( searchIndex == null ) {
            return false;
        }

        foundIndex = true;

        switch ( operator ) {
            case EQUAL:
                resultList = processResultsFromIndex( searchIndex, searchIndex.findEquals( value ) );
                break;
            case STARTS_WITH:
                resultList = searchIndex.findStartsWith( value );
                break;

            case GREATER_THAN:
                resultList = searchIndex.findGreaterThan( value );
                break;

            case GREATER_THAN_EQUAL:
                resultList = searchIndex.findGreaterThanEqual( value );
                break;

            case LESS_THAN:
                resultList = searchIndex.findLessThan( value );
                break;

            case LESS_THAN_EQUAL:
                resultList = searchIndex.findLessThanEqual( value );
                break;

            case BETWEEN:
                resultList = searchIndex.findBetween( criterion.getValue(), criterion.getValues()[ 1 ] );
                break;

        }

        criterion.clean();

        if ( resultList != null ) {
            resultSet.addResults( resultList );
            return foundIndex;
        } else {
            return foundIndex;
        }

    }

    private List processResultsFromIndex( SearchIndex searchIndex, List results ) {
        if ( searchIndex.isPrimaryKeyOnly() ) {
            //TODO iterate through listStream and lookupWithDefault items from keys, and put those in the actual results
            return null;
        } else {
            return results;
        }
    }

    @Override
    public void setSearchableCollection( SearchableCollection searchableCollection ) {
        this.searchableCollection = searchableCollection;
    }

    @Override
    public void setFields( Map<String, FieldAccess> fields ) {
        this.fields = fields;
    }

    @Override
    public void setSearchIndexMap( Map<String, SearchIndex> searchIndexMap ) {
        this.searchIndexMap = searchIndexMap;
    }

    @Override
    public void setLookupIndexMap( Map<String, LookupIndex> lookupIndexMap ) {
        this.lookupIndexMap = lookupIndexMap;
    }

    @Override
    public void init() {

    }
}