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

import org.boon.Exceptions;
import org.boon.StringScanner;
import org.boon.Universal;
import org.boon.collections.IntList;
import org.boon.core.reflection.BeanUtils;
import org.boon.core.reflection.Invoker;
import org.boon.core.reflection.fields.FieldAccess;

import java.lang.invoke.ConstantCallSite;
import java.lang.invoke.MethodHandle;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Map;

import static org.boon.Exceptions.die;
import static org.boon.Exceptions.handle;

/**
 * Integer operations.
 */
public class Int {

    /**
     * Grown an array
     * @param array array of items to grow
     * @param size size you would like to grow the array to
     * @return the new array
     */
    public static int[] grow( int[] array, final int size ) {

        int[] newArray = new int[ array.length + size ];
        System.arraycopy( array, 0, newArray, 0, array.length );
        return newArray;
    }

    /**
     * Double the size of an array.
     * @param array the array you are going to double
     * @return the new array with the old values copied in
     */
    public static int[] grow( int[] array ) {
        Exceptions.requireNonNull( array );

        int[] newArray = new int[ array.length * 2 ];
        System.arraycopy( array, 0, newArray, 0, array.length );
        return newArray;
    }

    /**
     * Shrink an array
     * @param array the array you want to shrink
     * @param size what size do you want the new array
     * @return the new array
     */
    public static int[] shrink( int[] array, int size ) {

        int[] newArray = new int[ array.length - size ];

        System.arraycopy( array, 0, newArray, 0, array.length - size );
        return newArray;
    }

    /**
     * Takes out all of the 0s
     * @param array array to compact
     * @return return value
     */
    public static int[] compact( int[] array ) {
        Exceptions.requireNonNull( array );

        int nullCount = 0;
        for ( int ch : array ) {

            if ( ch == '\0' ) {
                nullCount++;
            }
        }
        int[] newArray = new int[ array.length - nullCount ];

        int j = 0;
        for ( int ch : array ) {

            if ( ch == '\0' ) {
                continue;
            }

            newArray[ j ] = ch;
            j++;
        }
        return newArray;
    }

    /**
     * Creates an array of bytes
     *
     * @param size size of the array you want to make
     * @return
     */
    public static int[] arrayOfInt( final int size ) {
        return new int[ size ];
    }

    /**
     * @param array
     * @return
     */
    @Universal
    public static int[] array( final int... array ) {
        Exceptions.requireNonNull( array );
        return array;
    }

    /**
     * Returns the lengthOf an array.
     * @param array the array you are getting the length from.
     * @return array length
     */
    @Universal
    public static int lengthOf( int[] array ) {
        return len(array);
    }

    /**
     * Returns the lengthOf an array.
     * @param array the array you are getting the length from.
     * @return array length
     */
    @Universal
    public static int len( int[] array ) {
        return array.length;
    }

    /**
     * Returns the value at the given index.
     * @param array the array you are getting the from.
     * @return the value
     */
    @Universal
    public static int idx( final int[] array, final int index ) {
        final int i = calculateIndex( array, index );

        return array[ i ];
    }

    /**
     * Returns the value at the given index.
     * @param array the array you are getting the from.
     * @return the value
     */
    @Universal
    public static int atIndex( final int[] array, final int index ) {
        final int i = calculateIndex( array, index );

        return array[ i ];
    }

    /**
     * Sets the value at the given index.
     * @param array the array you are getting the from.
     * @return the value
     */
    @Universal
    public static void idx( final int[] array, int index, int value ) {
        final int i = calculateIndex( array, index );

        array[ i ] = value;
    }

    /**
     * Sets the value at the given index.
     * @param array the array you are setting the value to.
     * @param value the new value.
     * @return the value
     */
    @Universal
    public static void atIndex( final int[] array, int index, int value ) {
        final int i = calculateIndex( array, index );

        array[ i ] = value;
    }

    /**
     * Grabs a slice of this array
     * @param array the array you are getting slice from
     * @param startIndex the start index of the slice
     * @param endIndex the end index of the slice
     * @return the new slice
     */
    @Universal
    public static int[] slc( int[] array, int startIndex, int endIndex ) {

        final int start = calculateIndex( array, startIndex );
        final int end = calculateEndIndex( array, endIndex );
        final int newLength = end - start;

        if ( newLength < 0 ) {
            throw new ArrayIndexOutOfBoundsException(
                    String.format( "start index %d, end index %d, length %d",
                            startIndex, endIndex, array.length )
            );
        }

        int[] newArray = new int[ newLength ];
        System.arraycopy( array, start, newArray, 0, newLength );
        return newArray;
    }

    /**
     * Grabs a slice of this array
     * @param array the array you are getting slice from
     * @param startIndex the start index of the slice
     * @param endIndex the end index of the slice
     * @return the new slice
     */
    @Universal
    public static int[] sliceOf( int[] array, int startIndex, int endIndex ) {

        final int start = calculateIndex( array, startIndex );
        final int end = calculateEndIndex( array, endIndex );
        final int newLength = end - start;

        if ( newLength < 0 ) {
            throw new ArrayIndexOutOfBoundsException(
                    String.format( "start index %d, end index %d, length %d",
                            startIndex, endIndex, array.length )
            );
        }

        int[] newArray = new int[ newLength ];
        System.arraycopy( array, start, newArray, 0, newLength );
        return newArray;
    }

    /**
     * Grabs a slice of this array
     * @param array the array you are getting slice from
     * @param startIndex the start index of the slice
     * @return the new slice
     */
    @Universal
    public static int[] slc( int[] array, int startIndex ) {

        final int start = calculateIndex( array, startIndex );
        final int newLength = array.length - start;

        if ( newLength < 0 ) {
            throw new ArrayIndexOutOfBoundsException(
                    String.format( "start index %d, length %d",
                            startIndex, array.length )
            );
        }

        int[] newArray = new int[ newLength ];
        System.arraycopy( array, start, newArray, 0, newLength );
        return newArray;
    }

    /**
     * Grabs a slice of this array
     * @param array the array you are getting slice from
     * @param startIndex the start index of the slice
     * @return the new slice
     */
    @Universal
    public static int[] sliceOf( int[] array, int startIndex ) {

        final int start = calculateIndex( array, startIndex );
        final int newLength = array.length - start;

        if ( newLength < 0 ) {
            throw new ArrayIndexOutOfBoundsException(
                    String.format( "start index %d, length %d",
                            startIndex, array.length )
            );
        }

        int[] newArray = new int[ newLength ];
        System.arraycopy( array, start, newArray, 0, newLength );
        return newArray;
    }

    /**
     * Grabs a slice of this array
     * @param array the array you are getting slice from
     * @param endIndex the end index of the slice
     * @return the new slice
     */
    @Universal
    public static int[] slcEnd( int[] array, int endIndex ) {

        final int end = calculateEndIndex( array, endIndex );
        final int newLength = end; // +    (endIndex < 0 ? 1 : 0);

        if ( newLength < 0 ) {
            throw new ArrayIndexOutOfBoundsException(
                    String.format( "start index %d, length %d",
                            endIndex, array.length )
            );
        }

        int[] newArray = new int[ newLength ];
        System.arraycopy( array, 0, newArray, 0, newLength );
        return newArray;
    }

    /**
     * Grabs a slice of this array
     * @param array the array you are getting slice from
     * @param endIndex the end index of the slice
     * @return the new slice
     */
    @Universal
    public static int[] endSliceOf( int[] array, int endIndex ) {

        final int end = calculateEndIndex( array, endIndex );
        final int newLength = end; // +    (endIndex < 0 ? 1 : 0);

        if ( newLength < 0 ) {
            throw new ArrayIndexOutOfBoundsException(
                    String.format( "start index %d, length %d",
                            endIndex, array.length )
            );
        }

        int[] newArray = new int[ newLength ];
        System.arraycopy( array, 0, newArray, 0, newLength );
        return newArray;
    }

    /**
     * Checks to see if a value is in the array.
     * @param array the array you are getting slice from
     * @return true if the value is in the array
     */
    @Universal
    public static boolean in( int value, int[] array ) {
        for ( int currentValue : array ) {
            if ( currentValue == value ) {
                return true;
            }
        }
        return false;
    }

    /**
     * Copies an array.
     * @param array the array you want to copy
     * @return the copied array
     */
    @Universal
    public static int[] copy( int[] array ) {
        int[] newArray = new int[ array.length ];
        System.arraycopy( array, 0, newArray, 0, array.length );
        return newArray;
    }

    /**
     * Adds an int to the array. Grows the array by one and adds the int to the end.
     * @param array the array
     * @param v the value you are adding to the end.
     * @return the new array
     */
    @Universal
    public static int[] add( int[] array, int v ) {
        int[] newArray = new int[ array.length + 1 ];
        System.arraycopy( array, 0, newArray, 0, array.length );
        newArray[ array.length ] = v;
        return newArray;
    }

    /**
     * adds two array together and returns the results.
     * @param array first array
     * @param array2 second array
     * @return
     */
    @Universal
    public static int[] add( int[] array, int[] array2 ) {
        int[] newArray = new int[ array.length + array2.length ];
        System.arraycopy( array, 0, newArray, 0, array.length );
        System.arraycopy( array2, 0, newArray, array.length, array2.length );
        return newArray;
    }

    /**
     * Inserts a value in the midst of another array.
     * @param array
     * @param idx
     * @param v
     * @return
     */
    @Universal
    public static int[] insert( final int[] array, final int idx, final int v ) {

        if ( idx >= array.length ) {
            return add( array, v );
        }

        final int index = calculateIndex( array, idx );

        //Object newArray = Array.newInstance(array.getClass().getComponentType(), array.length+1);
        int[] newArray = new int[ array.length + 1 ];

        if ( index != 0 ) {
            /* Copy up to the length in the array before the index. */
            /*                 src     sbegin  dst       dbegin   length of copy */
            System.arraycopy( array, 0, newArray, 0, index );
        }

        boolean lastIndex = index == array.length - 1;
        int remainingIndex = array.length - index;

        if ( lastIndex ) {
            /* Copy the area after the insert. Make sure we don't write over the end. */
            /*                 src  sbegin   dst       dbegin     length of copy */
            System.arraycopy( array, index, newArray, index + 1, remainingIndex );

        } else {
            /* Copy the area after the insert.  */
            /*                 src  sbegin   dst       dbegin     length of copy */
            System.arraycopy( array, index, newArray, index + 1, remainingIndex );

        }

        newArray[ index ] = v;
        return newArray;
    }

    /**
     * Inserts an array in the midst of another array.
     * @param array
     * @return
     */
    @Universal
    public static int[] insert( final int[] array, final int fromIndex, final int[] values ) {

        if ( fromIndex >= array.length ) {
            return add( array, values );
        }

        final int index = calculateIndex( array, fromIndex );

        //Object newArray = Array.newInstance(array.getClass().getComponentType(), array.length+1);
        int[] newArray = new int[ array.length + values.length ];

        if ( index != 0 ) {
            /* Copy up to the length in the array before the index. */
            /*                 src     sbegin  dst       dbegin   length of copy */
            System.arraycopy( array, 0, newArray, 0, index );
        }

        boolean lastIndex = index == array.length - 1;

        int toIndex = index + values.length;
        int remainingIndex = newArray.length - toIndex;

        if ( lastIndex ) {
            /* Copy the area after the insert. Make sure we don't write over the end. */
            /*                 src  sbegin   dst       dbegin     length of copy */
            System.arraycopy( array, index, newArray, index + values.length, remainingIndex );

        } else {
            /* Copy the area after the insert.  */
            /*                 src  sbegin   dst       dbegin     length of copy */
            System.arraycopy( array, index, newArray, index + values.length, remainingIndex );

        }

        for ( int i = index, j = 0; i < toIndex; i++, j++ ) {
            newArray[ i ] = values[ j ];
        }
        return newArray;
    }

    /**
     * Calculates the index for slice notation so -1 is one minus length and so on.
     * @param array array in question
     * @param originalIndex the index give which might be negative or higher than length.
     * @return
     */
    private static int calculateIndex( int[] array, int originalIndex ) {
        final int length = array.length;

        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
         */
         /* 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 - 1;
        }
        return index;
    }

    /**
     * Calculates the index for slice notation so -1 is one minus length and so on.
     * @param array array in question
     * @param originalIndex the index give which might be negative or higher than length.
     * @return
     */
    private static int calculateEndIndex( int[] array, int originalIndex ) {
        final int length = array.length;

        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
         */
         /* 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 interface for a very fast reduce by. */
    public static interface ReduceBy {
        long reduce(long sum, int value);
    }

    /**
     * A very fast reduce by.
     * If performance is your thing, this seems to be as fast a plain for loop when benchmarking with JMH.
     *
     * @param array array of items to reduce by
     * @param reduceBy reduceBy interface
     * @return the final value
     */
    public static long reduceBy( final int[] array, ReduceBy reduceBy ) {

        long sum = 0;
        for ( int v : array ) {
            sum = reduceBy.reduce(sum, v);
        }
        return sum;
    }

    /**
     *
     * @param array array of items to reduce by
     * @param start where to start in the array
     * @param length where to end in the array
     * @param reduceBy the function to do the reduce by
     * @return the reduction
     */
    public static long reduceBy( final int[] array, final int start, final int length, ReduceBy reduceBy ) {

        long sum = 0;

        for (int index = start; index < length; index++) {
            int v = array[index];
            sum = reduceBy.reduce(sum, v);
        }
        return sum;
    }

    /**
     *
     * @param array array of items to reduce by
     * @param length where to end in the array
     * @param reduceBy the function to do the reduce by
     * @return the reduction
     */
    public static long reduceBy( final int[] array, final int length, ReduceBy reduceBy ) {

        long sum = 0;

        for (int index = 0; index < length; index++) {
            int v = array[index];
            sum = reduceBy.reduce(sum, v);
        }
        return sum;
    }

    /**
     * Reduce by functional support for int arrays.
     * @param array array of items to reduce by
     * @param object object that contains the reduce by function
     * @param <T> the type of object
     * @return the final reduction
     */
    public  static <T> long reduceBy( final int[] array, T object ) {
        if (object.getClass().isAnonymousClass()) {
            return reduceByR(array, object );
        }

        try {
            ConstantCallSite callSite = Invoker.invokeReducerLongIntReturnLongMethodHandle(object);
            MethodHandle methodHandle = callSite.dynamicInvoker();
            try {

                long sum = 0;
                for ( int v : array ) {
                    sum = (long) methodHandle.invokeExact( sum, v );

                }
                return sum;
            } catch (Throwable throwable) {
                return handle(Long.class, throwable, "Unable to perform reduceBy");
            }
        } catch (Exception ex) {
            return reduceByR(array, object);
        }

    }

    /**
     * Reduce by functional support for int arrays.
     * @param array array of items to reduce by
     * @param object object that contains the reduce by function
     * @param <T> the type of object
     * @return the final reduction
     */
    public static <T> long reduceBy( final int[] array, T object, String methodName ) {

        if (object.getClass().isAnonymousClass()) {
            return reduceByR(array, object, methodName);
        }

        try {
            ConstantCallSite callSite = Invoker.invokeReducerLongIntReturnLongMethodHandle(object, methodName);
            MethodHandle methodHandle = callSite.dynamicInvoker();
            try {

                long sum = 0;
                for ( int v : array ) {
                        sum = (long) methodHandle.invokeExact( sum, v );

                }
                return sum;
            } catch (Throwable throwable) {
                return handle(Long.class, throwable, "Unable to perform reduceBy");
            }
        } catch (Exception ex) {
            return reduceByR(array, object, methodName);
        }

    }

    /**
     * Fallback to reflection if the call-site will not work or did not work
     * @param array array of items to reduce by
     * @param object function object
     * @param <T> type of function object.
     * @return
     */
    private  static <T> long reduceByR( final int[] array, T object ) {
        try {

            Method method = Invoker.invokeReducerLongIntReturnLongMethod(object);

            long sum = 0;
            for ( int v : array ) {
                sum = (long) method.invoke(object, sum, v);

            }
            return sum;

        } catch (Throwable throwable) {
            return handle(Long.class, throwable, "Unable to perform reduceBy");
        }

    }

    /**
     * Reflection based reduce by.
     * @param array array of items to reduce by
     * @param object function
     * @param methodName name of method
     * @param <T> type of function
     * @return reduction
     */
    private  static <T> long reduceByR( final int[] array, T object, String methodName ) {
        try {

            Method method = Invoker.invokeReducerLongIntReturnLongMethod(object, methodName);

            long sum = 0;
            for ( int v : array ) {
                sum = (long) method.invoke(object, sum, v);

            }
            return sum;

        } catch (Throwable throwable) {
            return handle(Long.class, throwable, "Unable to perform reduceBy");
        }

    }

    /**
     * Reflection based reduce by.
     * @param array array of items to reduce by
     * @param object function
     * @param methodName name of method
     * @param <T> type of function
     * @return reduction
     */
    private  static <T> long reduceByR( final int[] array, int length, T object, String methodName ) {
        try {

            Method method = Invoker.invokeReducerLongIntReturnLongMethod(object, methodName);

            long sum = 0;
            for (int index=0; index< length; index++) {
                int v = array[index];
                sum = (long) method.invoke(object, sum, v);

            }
            return sum;

        } catch (Throwable throwable) {
            return handle(Long.class, throwable, "Unable to perform reduceBy");
        }

    }

    /**
     * Reflection based reduce by.
     * @param array array of items to reduce by
     * @param object function
     * @param <T> type of function
     * @return reduction
     */
    private  static <T> long reduceByR( final int[] array, int length, T object ) {
        try {

            Method method = Invoker.invokeReducerLongIntReturnLongMethod(object);

            long sum = 0;
            for (int index=0; index< length; index++) {
                int v = array[index];
                sum = (long) method.invoke(object, sum, v);

            }
            return sum;

        } catch (Throwable throwable) {
            return handle(Long.class, throwable, "Unable to perform reduceBy");
        }

    }

    /**
     * Reduce By
     * @param array array of items to reduce by
     * @param length where to end in the array
     * @param object function
     * @return reduction
     */
    public static long reduceBy( final int[] array,  int length,
                                 Object object ) {

        if (object.getClass().isAnonymousClass()) {
            return reduceByR(array, length, object );
        }

        try {
            ConstantCallSite callSite = Invoker.invokeReducerLongIntReturnLongMethodHandle(object );
            MethodHandle methodHandle = callSite.dynamicInvoker();
            try {

                long sum = 0;
                for (int index=0; index < length; index++) {
                    int v = array[index];
                    sum = (long) methodHandle.invokeExact( sum, v );

                }
                return sum;
            } catch (Throwable throwable) {
                return handle(Long.class, throwable, "Unable to perform reduceBy");
            }
        } catch (Exception ex) {
            return reduceByR(array, length, object );
        }

    }

    /**
     * Reduce By
     * @param array array of items to reduce by
     * @param length where to end in the array
     * @param function function
     * @param function functionName
     * @return reduction
     */
    public static long reduceBy( final int[] array,  int length,
                                 Object function, String functionName ) {

        if (function.getClass().isAnonymousClass()) {
            return reduceByR(array, length, function, functionName );
        }

        try {
            ConstantCallSite callSite = Invoker.invokeReducerLongIntReturnLongMethodHandle(function, functionName );
            MethodHandle methodHandle = callSite.dynamicInvoker();
            try {

                long sum = 0;
                for (int index=0; index < length; index++) {
                    int v = array[index];
                    sum = (long) methodHandle.invokeExact( sum, v );

                }
                return sum;
            } catch (Throwable throwable) {
                return handle(Long.class, throwable, "Unable to perform reduceBy");
            }
        } catch (Exception ex) {
            return reduceByR(array, length, function, functionName );
        }

    }

    /**
     * Reduce By
     * @param array array of items to reduce by
     * @param length where to end in the array
     * @param object function
     * @return reduction
     */
    public static long reduceBy( final int[] array, int start, int length,
                                 Object object ) {

        if (object.getClass().isAnonymousClass()) {
            return reduceByR(array, object );
        }

        try {
            ConstantCallSite callSite = Invoker.invokeReducerLongIntReturnLongMethodHandle(object );
            MethodHandle methodHandle = callSite.dynamicInvoker();
            try {

                long sum = 0;
                for (int index=start; index < length; index++) {
                    int v = array[index];
                    sum = (long) methodHandle.invokeExact( sum, v );

                }
                return sum;
            } catch (Throwable throwable) {
                return handle(Long.class, throwable, "Unable to perform reduceBy");
            }
        } catch (Exception ex) {
            return reduceByR(array, object );
        }

    }

    /**
     * Checks to see if two values are the same
     * @param expected expected value
     * @param got got value
     * @return true if equal throws exception if not equal
     */
    public static boolean equalsOrDie(int expected, int got) {
        if (expected != got) {
            return die(Boolean.class, "Expected was", expected, "but we got ", got);
        }
        return true;
    }

    /**
     * Checks to see if two arrays are equals
     * @param expected expected array
     * @param got got array
     * @return true if equal or throws exception if not.
     */
    public static boolean equalsOrDie(int[] expected, int[] got) {

        if (expected.length != got.length) {
            die("Lengths did not match, expected length", expected.length,
                    "but got", got.length);
        }

        for (int index=0; index< expected.length; index++) {
            if (expected[index]!= got[index]) {
                die("value at index did not match index", index , "expected value",
                        expected[index],
                        "but got", got[index]);

            }
        }
        return true;
    }

    /**
     * Compares two values
     * @param expected expected value
     * @param got got value
     * @return true or false
     */
    public static boolean equals(int expected, int got) {

        return expected == got;
    }

    /**
     * Checks to see if two arrays are equals
     * @param expected expected array
     * @param got got array
     * @return true if equal or false if not.
     */
    public static boolean equals(int[] expected, int[] got) {

        if (expected.length != got.length) {
            return false;
        }

        for (int index=0; index< expected.length; index++) {
            if (expected[index]!= got[index]) {
               return false;
            }
        }
        return true;
    }

    /**
     * Sum
     * Provides overflow protection.
     * @param values values in int
     * @return sum
     */
    public static int sum( int[] values ) {
        return sum( values, 0, values.length);
    }

    /**
     * Sum
     * Provides overflow protection.
     * @param values values in int
     * @return sum
     */
    public static int sum( int[] values,  int length ) {
        return sum( values, 0, length);
    }

    /**
     * Sum
     * Provides overflow protection.
     * @param values values in int
     * @return sum
     */
    public static int sum( int[] values, int start, int length ) {
        long sum = 0;
        for (int index = start; index < length; index++ ) {
            sum+= values[index];
        }

        if (sum < Integer.MIN_VALUE) {
            die ("overflow the sum is too small", sum);
        }

        if (sum > Integer.MAX_VALUE) {
            die ("overflow the sum is too big", sum);
        }

        return (int) sum;

    }

    /**
     * Sum
     * Provides overflow protection.
     * @param values values in int
     * @return sum
     */
    public static long bigSum( int[] values ) {
        return bigSum(values, 0, values.length);
    }

    /**
     * Sum
     * Provides overflow protection.
     * @param values values in int
     * @return sum
     */
    public static long bigSum( int[] values,  int length ) {
        return bigSum(values, 0, length);
    }

    /**
     * Big Sum
     * @param values values in int
     * @return sum
     */
    public static long bigSum( int[] values, int start, int length ) {
        long sum = 0;
        for (int index = start; index < length; index++ ) {
            sum+= values[index];
        }

        return sum;

    }

    /**
     * Max
     * @param values values in int
     * @return max
     */
    public static int max( int[] values, final int start, final int length ) {
        int max = Integer.MIN_VALUE;
        for (int index = start; index < length; index++ ) {
            if ( values[index] > max ) {
                max = values[index];
            }
        }

        return max;
    }

    /**
     * max
     * @param values values in int
     * @return max
     */
    public static int max( int[] values ) {
        return max(values, 0, values.length);
    }

    /**
     * max
     * @param values values in int
     * @return max
     */
    public static int max( int[] values, int length ) {
        return max(values, 0, length);
    }

    /**
     * Min
     * @param values values in int
     * @return min
     */
    public static int min( int[] values, final int start, final int length ) {
        int min = Integer.MAX_VALUE;
        for (int index = start; index < length; index++ ) {
            if (values[index] < min) min = values[index];
        }
        return min;
    }

    /**
     * Min
     * @param values values in int
     * @return min
     */
    public static int min( int[] values ) {
       return min(values, 0, values.length);
    }

    /**
     * Min
     * @param values values in int
     * @return min
     */
    public static int min( int[] values, int length ) {
        return min(values, 0, length);
    }

    /**
     * Average
     * @param values values in int
     * @return average
     */
    public static int mean( int[] values, final int start, final int length ) {
        return (int) Math.round(meanDouble(values, start, length));
    }

    /**
     * Average
     * @param values values in int
     * @return average
     */
    public static int mean( int[] values, final int length ) {
        return (int) Math.round(meanDouble(values, 0, length));
    }

    /**
     * Average
     * @param values values in int
     * @return average
     */
    public static int mean( int[] values ) {

        return (int) Math.round(meanDouble(values, 0, values.length));
    }

    /**
     * Calculate Variance.
     *
     * @param values values
     * @param start start
     * @param length length
     * @return variance
     */
    public static int variance(int[] values, final int start, final int length) {
        return (int) Math.round(varianceDouble(values, start, length));
    }

    private static double meanDouble( int[] values, final int start, final int length ) {
        double mean = ((double)bigSum(values, start, length))/ ((double) length);
        return mean;
    }

    /**
     * Calculate Variance.
     *
     * @param values values
     * @param start start
     * @param length length
     * @return variance
     */
    public static double varianceDouble(int[] values, final int start, final int length) {
        double mean = meanDouble(values, start, length);
        double temp = 0;
        for(int index = start; index < length; index++) {
            double a = values[index];
            temp += (mean-a)*(mean-a);
        }
        return temp / length;
    }

    /**
     * Calculate Variance.
     *
     * @param values values
     * @param length length
     * @return variance
     */
    public static int variance(int[] values,  final int length) {
        return (int) Math.round(varianceDouble(values, 0, length));
    }

    /**
     * Calculate Variance.
     *
     * @param values values
     * @return variance
     */
    public static int variance(int[] values) {
        return (int) Math.round(varianceDouble(values, 0, values.length));
    }

    /**
     * Calculate standard deviation.
     *
     * @param values values
     * @param start start
     * @param length length
     * @return standard deviation
     */
    public static int standardDeviation(int[] values, final int start, final int length) {
        double variance = varianceDouble(values, start, length);
        return (int)Math.round(Math.sqrt(variance));
    }

    /**
     * Calculate standard deviation.
     *
     * @param values values
     * @param length length
     * @return standard deviation
     */
    public static int standardDeviation(int[] values,  final int length) {
        double variance = varianceDouble(values, 0, length);
        return (int)Math.round(Math.sqrt(variance));
    }

    /**
     * Calculate standard deviation.
     *
     * @param values values
     * @return standard deviation
     */
    public static int standardDeviation(int[] values) {
        double variance = varianceDouble(values, 0, values.length);
        return (int)Math.round(Math.sqrt(variance));
    }

    /**
     * Calculate Median
     *
     * @param start start
     * @param values values
     * @param length length
     * @return median
     */
    public static int median(int[] values, final int start, final int length) {
        int[] sorted = new int[length];
        System.arraycopy(values, start, sorted, 0, length);
        Arrays.sort(sorted);

        if (length % 2 == 0) {
            int middle = sorted.length / 2;
            double median = (sorted[middle-1] + sorted[middle]) / 2.0;
            return (int) Math.round(median);
        } else {
            return sorted[sorted.length / 2];
        }
    }

    /**
     * Calculate Median
     * @param values values
     * @param length length
     * @return median
     */
    public static int median(int[] values, final int length) {
        return median(values, 0, length);
    }

    /**
     * Calculate Median
     * @param values values
     * @return median
     */
    public static int median(int[] values) {
        return median(values, 0, values.length);
    }

    /**
     * Checks to see if two arrays are equals
     * @param expected expected array
     * @param got got array
     * @return true if equal or false if not.
     */
    public static boolean equals(int start, int end, int[] expected, int[] got) {

        if (expected.length != got.length) {
            return false;
        }

        for (int index=start; index< end; index++) {
            if (expected[index]!= got[index]) {
                return false;
            }
        }
        return true;
    }

    public static int hashCode(int array[]) {
        if (array == null)
            return 0;

        int result = 1;
        for (int element : array) {

            result = 31 * result + element;
        }

        return result;
    }

    public static int hashCode(int start, int end, int array[]) {
        if (array == null)
            return 0;

        int result = 1;

        for (int index=start; index< end; index++) {

            result = 31 * result + array[index];
        }

        return result;
    }

    /**
     * Calculate a sum of a property from a list.
     * @param inputList
     * @param propertyPath to item we want to sum
     * @return sum
     */
    public static long sum( Collection<?> inputList, String propertyPath ) {
        if (inputList.size() == 0 ) {
            return 0;
        }

        long sum = 0l;

        if (propertyPath.contains(".") || propertyPath.contains("[")) {

            String[] properties = StringScanner.splitByDelimiters(propertyPath, ".[]");

            for (Object o : inputList) {
                sum+=BeanUtils.getPropertyInt(o, properties);
            }

        } else {

            Map<String, FieldAccess> fields =  BeanUtils.getFieldsFromObject(inputList.iterator().next());
            FieldAccess fieldAccess = fields.get(propertyPath);
            for (Object o : inputList) {
                sum += fieldAccess.getInt(o);
            }
        }

        return sum;
    }

    private static double mean( Collection<?> inputList, String propertyPath ) {
        double mean = sum(inputList, propertyPath)/inputList.size();
        return Math.round(mean);
    }

    public static double variance(Collection<?> inputList, String propertyPath) {
        double mean = mean(inputList, propertyPath);
        double temp = 0;

        double a;

        if (propertyPath.contains(".") || propertyPath.contains("[")) {

            String[] properties = StringScanner.splitByDelimiters(propertyPath, ".[]");

            for (Object o : inputList) {
                a =BeanUtils.getPropertyInt(o, properties);
                temp += (mean-a)*(mean-a);
            }

        } else {

            Map<String, FieldAccess> fields =  BeanUtils.getFieldsFromObject(inputList.iterator().next());
            FieldAccess fieldAccess = fields.get(propertyPath);
            for (Object o : inputList) {
                a = fieldAccess.getInt(o);
                temp += (mean-a)*(mean-a);
            }
        }

        return Math.round(temp / inputList.size());

    }

    /**
     * Calculate standard deviation.
     *
     * @return standard deviation
     */
    public static double standardDeviation(Collection<?> inputList, String propertyPath) {
        double variance = variance(inputList, propertyPath);
        return Math.round(Math.sqrt(variance));
    }

    /**
     * Calculate standard deviation.
     *
     * @return standard deviation
     */
    public static int median(Collection<?> inputList, String propertyPath) {
        return IntList.toIntList(inputList, propertyPath).median();
    }

    /**
     * Round up to the nearest power of 2
     * @param number number you want to round up to.
     * @return rounded up to the power of 2.
     */
    public static int roundUpToPowerOf2( int number ) {
        int rounded = number >= 1_000
                ? 1_000
                : ( rounded = Integer.highestOneBit( number ) ) != 0
                ? ( Integer.bitCount( number ) > 1 ) ? rounded << 1 : rounded
                : 1;

        return rounded;
    }

}


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

import org.boon.messages.ResourceBundleLocator;

import java.text.MessageFormat;
import java.util.*;

/**
 * This is the validator context.
 * It holds thread local state that is important to validation.
 * It is an interface into underlying component and/or MVC/Model 2 architectures
 * to abstract and simplify access to the context of things we need to perform validation.
 *
 * @author Rick Hightower
 */
public class ValidationContext {

    private ArrayDeque<String> bindingPath = new ArrayDeque<>();

    /**
     * Used to find the resource bundle. This can vary based on the
     * web framework you are using, and whether you want to use Spring
     * Message sources or not.
     */
    private ResourceBundleLocator resourceBundleLocator;

    /**
     * Holds the parent object of the field. A parent object is an
     * object that contains fields.
     */
    private Object parentObject;

    /**
     * Context for validaiton rule variables.
     */
    private Map<String, Object> params;

    /**
     * Holds the data(context) for the current thread.
     */
    private static ThreadLocal<ValidationContext> holder = new ThreadLocal<>();

    /**
     * Used to determine if an argument or message refers to an item that
     * should be looked up in the resourceBundle.
     */
    private String i18nMarker = "{";

    private String currentSubject;

    /**
     * Provides access to the ValidationContext.
     *
     * @return xx
     */
    public static ValidationContext getCurrentInstance() {
        return holder.get();
    }

    /**
     * Allows the subclass to register an instance of itself
     * as the context. The subclass will either be JSF, Struts 2 (WebWork) or
     * Spring MVC aware.
     *
     * @param context xx
     */
    protected void register( ValidationContext context ) {
        holder.set( context );
    }

    /**
     * Get the parent object. Allows the FieldValidators to access
     * the parent object.
     *
     * @return xx
     */
    public Object getParentObject() {
        return parentObject;
    }

    /**
     * Allows our integration piece for JSF or Spring MVC to set the
     * parent object. The parent object is the form bean in Spring MVC speak.
     *
     * @param parentObject xx
     */
    public void setParentObject( Object parentObject ) {
        this.parentObject = parentObject;
    }

    /**
     * Proivdes a list of parameters that we can access from field validators.
     *
     * @return xx
     */
    public Map<String, Object> getParams() {
        return params;
    }

    public void setParams( Map<String, Object> params ) {
        this.params = params;
    }

    /**
     * Gets the proposed property value.
     * This is the value before it gets applied to the actual bean.
     *
     * @param propertyName xx
     * @return xx
     */
    public Object getProposedPropertyValue( String propertyName ) {
        return null;
    }

    private String calculateBindingPath() {
        StringBuilder builder = new StringBuilder( 255 );
        int index = 0;
        for ( String component : bindingPath ) {
            index++;
            builder.append( component );
            if ( index != bindingPath.size() ) {
                builder.append( '.' );
            }
        }
        return builder.toString();
    }

    public void pop() {
        bindingPath.pop();
    }

    public void pushProperty( final String component ) {
        bindingPath.push( component );
    }

    public void pushObject( final Object object ) {
        String simpleName = object.getClass().getSimpleName();
        simpleName = simpleName.substring( 0, 1 ).toLowerCase() + simpleName.substring( 1, simpleName.length() );
        bindingPath.push( simpleName );
    }

    public static String getBindingPath() {
        if ( getCurrentInstance() != null ) {
            return getCurrentInstance().calculateBindingPath();
        }
        return "";
    }

    public static ValidationContext create() {
        holder.set( new ValidationContext() );
        return get();
    }

    public static ValidationContext get() {
        return holder.get();
    }

    public static void destroy() {
        holder.set( null );
    }

    Map<String, Object> objectRegistry;

    public Map<String, Object> getObjectRegistry() {
        return objectRegistry;
    }

    public void setObjectRegistry( Map<String, Object> objectRegistry ) {
        this.objectRegistry = objectRegistry;
    }

    public String getMessage( String key ) {

        String message = doGetMessageFromBundle( key );

        return message == null ? key : message;

    }

    public String getCurrentSubject() {
        return currentSubject;
    }

    public void setCurrentSubject( String currentSubject ) {
        this.currentSubject = currentSubject;
    }

    public String createMessage( String message, String subject, Object[] actualArgs ) {
        List argumentList = new ArrayList( Arrays.asList( actualArgs ) );

    	/* If the subject is found addObject it as the first
         * argument to the argument list. */
        if ( subject != null ) {
            argumentList.add( 0, this.getMessage( subject ) );

        }
        try {
            /* Attempt to create the message. */
            return MessageFormat.format( message, argumentList.toArray() );
        } catch ( Exception ex ) {

            return message;
        }

    }

    public void setResourceBundleLocator( ResourceBundleLocator resourceBundleLocator ) {
        this.resourceBundleLocator = resourceBundleLocator;
    }

    protected ResourceBundleLocator getResourceBundleLocator() {
        return resourceBundleLocator;
    }

    /**
     * The doGetMessageFromBundle does a bit of magic. If the message starts with {
     * than it assumes it is an i18N message and looks it up in the
     * resource bundle. If it starts with #{ it assumes it is an expression
     * and uses OGNL, JSF EL, or the Universal EL to look up the expression
     * in the context.
     */
    private String doGetMessageFromBundle( String key ) {

        if ( resourceBundleLocator == null ) {
            return null;
        }
        /* Find the resourceBundle. */
        ResourceBundle bundle = this.resourceBundleLocator.getBundle();

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

        String message = null; //holds the message

    	/* If the message starts with an i18nMarker look it up
         * in the resource bundle.
    	 */
        if ( key.startsWith( this.i18nMarker ) ) {
            try {
                key = key.substring( 1, key.length() - 1 );
                message = lookupMessageInBundle( key, bundle, message );
            } catch ( MissingResourceException mre ) {
                message = key;
            }
        } else {
            /*
             * If it does not start with those markers see if it has a ".". If
			 * it has a dot, try to look it up. If it is not found then just
			 * return the key as the message.
			 */
            if ( key.contains( "." ) ) {
                try {
                    message = lookupMessageInBundle( key, bundle, message );
                } catch ( MissingResourceException mre ) {
                    message = key;
                }
            } else {
                message = key;
            }
        }
        return message;
    }

    private String lookupMessageInBundle( String key, ResourceBundle bundle,
                                          String message ) {
        if ( getCurrentSubject() != null ) {
            try {
                message = bundle.getString( key + "." + getCurrentSubject() );

            } catch ( MissingResourceException mre ) {
                message = bundle.getString( key );
            }
        } else {
            return bundle.getString( key );
        }
        return message;
    }

}


/*
 * 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.Exceptions;
import org.boon.core.reflection.fields.FieldAccess;

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

import static org.boon.sort.Sorting.thisUniversalComparator;

/**
 * Created by Richard on 3/8/14.
 */
public class SortingInternal {

    /**
     * This is the work horse. It does all of the sorting work for the simple cases.
     * Nulls are last by default.
     * @param list the list you want to sort.
     * @param sortBy what you want to sort the list by.
     * @param fields the reflection fields
     * @param ascending true for ascending
     */
    public static void sort( List list, String sortBy, Map<String, FieldAccess> fields, boolean ascending) {

        sort(list, sortBy, fields, ascending, false);

    }

    /**
     * This is the work horse. It does all of the sorting work for the simple cases.
     * @param list the list you want to sort.
     * @param sortBy what you want to sort the list by.
     * @param fields the reflection fields
     * @param ascending true for ascending
     */
    public static void sort( List list, String sortBy, Map<String, FieldAccess> fields, boolean ascending,
                             boolean nullsFirst) {

        try {

            /* If this list is null or empty, we have nothing to do so return. */
            if ( list == null || list.size() == 0 ) {
                return;
            }

            /* Grab the first item in the list and see what it is. */
            Object o = list.get( 0 );

            /* if the sort by string is is this, and the object is comparable then use the objects
            themselves for the sort.
             */
            if ( sortBy.equals( "this" )  ) {

                Collections.sort(list, thisUniversalComparator(ascending, nullsFirst));
                return;
            }

            /* If you did not sort by "this", then sort by the field. */

            final FieldAccess field = fields.get( sortBy );

            if ( field != null ) {

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

            }

        } catch (Exception ex) {
            Exceptions.handle(ex, "list", list, "\nsortBy", sortBy, "fields", fields, "ascending", ascending,
            "nullFirst", nullsFirst);
        }
    }

}


package org.boon.slumberdb.service.server;

import org.boon.Logger;
import org.boon.Maps;
import org.boon.Str;
import org.boon.collections.LazyMap;
import org.boon.concurrent.Timer;
import org.boon.slumberdb.config.GlobalConfig;
import org.boon.slumberdb.service.config.DataStoreServerConfig;
import org.boon.slumberdb.service.protocol.Action;
import org.boon.slumberdb.service.protocol.requests.BatchSetRequest;
import org.boon.slumberdb.service.protocol.requests.SetRequest;
import org.boon.slumberdb.service.results.*;
import org.boon.slumberdb.stores.DataStoreSource;
import org.boon.slumberdb.stores.MasterDataStore;
import org.boon.slumberdb.stores.queue.DataOutputQueueTransferQueue;

import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

import static org.boon.Boon.configurableLogger;
import static org.boon.Boon.puts;

/**
 * Created by Richard on 9/10/14.
 */
public class ResponseHandler {

    private static Map<String, StatCount> stats = new ConcurrentHashMap<>();
    private final boolean debug = GlobalConfig.DEBUG;
    Status status = new Status();
    long time;
    long lastStatusSend = 0;
    private int drainerId;
    private long lastStatusDisplay = Timer.timer().time();
    private MasterDataStore masterDataStore = null;
    private Logger logger = configurableLogger(this.getClass());
    private boolean verboseStatus = true;

    /*

.-.   .-.  .--.  .-..-. .-.
|  `.'  | / {} \ | ||  `| |
| |\ /| |/  /\  \| || |\  |
`-' ` `-'`-'  `-'`-'`-' `-'
.----..-.  .-..-. .---.
| {_   \ \/ / | |{_   _}
| {__  / /\ \ | |  | |
`----'`-'  `-'`-'  `-'
.----..----.  .----. .-.   .-.   .----.   .--.  .---.  .--.      .----..---.  .----. .----. .----.
| {_  | {}  }/  {}  \|  `.'  |   | {}  \ / {} \{_   _}/ {} \    { {__ {_   _}/  {}  \| {}  }| {_
| |   | .-. \\      /| |\ /| |   |     //  /\  \ | | /  /\  \   .-._} } | |  \      /| .-. \| {__
`-'   `-' `-' `----' `-' ` `-'   `----' `-'  `-' `-' `-'  `-'   `----'  `-'   `----' `-' `-'`----'

     */
    private DataStoreServerConfig config;
    private DataOutputQueueTransferQueue queue = null;
    private DataStoreServer storeServer = null;

    /**
     * Main exit from system.
     *

     *

     * If you are debugging something not coming out of the system.
     * Start here.
     */
    private void handleResponseFromDataStore(Result result) {

        if (debug) {
            logger.info("ResponseHandler::handleResponseFromDataStore", result);
        }

        if (result instanceof SingleResult) {
            SingleResult singleResult = (SingleResult) result;
            int size = handleSingleResult(singleResult);
            counter(size, singleResult.source());
        }
        else if (result instanceof SearchBatchResult) {
            SearchBatchResult searchBatchResult = (SearchBatchResult) result;
            sendBatchResponse((BatchResult) result);
            int size = searchBatchResult.getResults().size();
            counter(size, searchBatchResult.source());
        }
        else if (result instanceof BatchResult) {
            BatchResult batchResult = (BatchResult) result;
            int size = handleBatchResult(batchResult);
            counter(size, batchResult.source());
        }
        else if (result instanceof ErrorResult) {
            ErrorResult errorResult = (ErrorResult) result;
            int size = handleErrorResult(errorResult);
            counter(size, errorResult.source());
        }
    }

    private int handleBatchResult(BatchResult batchResult) {
        int size = batchResult.getResults().size();
        switch (batchResult.source()) {
            case LOCAL_DB:
                masterDataStore.addAll(new BatchSetRequest(DataStoreSource.MEMORY,
                        Action.SET_BATCH_INTERNAL, batchResult.messageId(), batchResult.clientId(), batchResult.getResults()));
                break;
            case REPLICA:
            case REMOTE_DB:
                masterDataStore.addAll(new BatchSetRequest(DataStoreSource.LOCAL_STORES,
                        Action.SET_BATCH_INTERNAL, batchResult.messageId(), batchResult.clientId(), batchResult.getResults()));
                break;

        }

        if (size > 0) {
            sendMapInBatches(batchResult);
        }
        return size;
    }

    private int handleSingleResult(SingleResult result) {
        String clientId = result.clientId();

        switch (result.source()) {
            case LOCAL_DB:
                masterDataStore.set(result.key(), result.getValue());
                break;
            case REPLICA:
            case REMOTE_DB:
                masterDataStore.setSource(new SetRequest(DataStoreSource.LOCAL_STORES, Action.SET_INTERNAL, result));
                break;
        }

        storeServer.sendMessageToClientId(clientId, result.toTextMessage());

        return 1;
    }

    private int handleErrorResult(ErrorResult result) {
        String clientId = result.clientId();
        String message = "[\"error\",\"" + result.getTitle() + "\",\"" + result.source().toString() + "\"]";
        storeServer.sendMessageToClientId(clientId, message);
        return 1;
    }

    private void readDataSourceOutputQueue() {
        Response queueItem;

        while (true) {

            queueItem = queue.take();

            if (verboseStatus) {
                showStatus(time, queueItem);
            }

            while (queueItem != null) {

                if (queueItem instanceof Result) {
                    handleResponseFromDataStore((Result) queueItem);

                } else if (queueItem instanceof Stat) {
                    handleStatFromDataStore((Stat) queueItem);
                }
                queueItem = queue.poll();

            }

            time = Timer.timer().time();

            sendWorkerStats(time);
        }

    }

    private void sendWorkerStats(long now) {

        long duration = now - lastStatusSend;
        if (duration > 30_000) {

            lastStatusSend = now;

            StatCount count;

            count = new StatCount(now, DataStoreSource.LOCAL_DB, Action.GET,
                    Str.addObjects("RESPONSE HANDLER ", drainerId),
                    this.status.fromLocalDB);

            this.queue.put(count);

            count = new StatCount(now, DataStoreSource.REMOTE_DB, Action.GET,
                    Str.addObjects("RESPONSE HANDLER ", drainerId),
                    this.status.fromRemoteDB);

            this.queue.put(count);

            count = new StatCount(now, DataStoreSource.MEMORY, Action.GET,
                    Str.addObjects("RESPONSE HANDLER ", drainerId),
                    this.status.fromMemory);

            this.queue.put(count);

            count = new StatCount(now, DataStoreSource.END, Action.GET,
                    Str.addObjects("RESPONSE HANDLER ", drainerId),
                    this.status.notFound);

            this.queue.put(count);
        }

    }

    private void showStatus(long time, Response queueItem) {

        if (queueItem == null) {

            long duration = time - lastStatusDisplay;

            if (duration > (config.statusInterval() + (drainerId * 100))) {
                lastStatusDisplay = time;
                puts("_____ Read Status ___", drainerId);
                puts("_____________________");

                puts("    FROM MEMORY    ", status.fromMemory);
                puts("    FROM LOCAL DB  ", status.fromLocalDB);
                puts("    FROM REMOTE DB ", status.fromRemoteDB);
                puts("    NOT FOUND      ", status.notFound);
                puts("");
            }
        }

    }

    public void startQueueDrainer() {
        storeServer.startWorker("ResponseHandler for DataStore " + this.drainerId,
                new Runnable() {
                    @Override
                    public void run() {
                        readDataSourceOutputQueue();
                    }
                }
        );

    }

    public void init(DataStoreServer dataStoreServer, DataStoreServerConfig config, int index,
                     MasterDataStore masterDataStore, DataOutputQueueTransferQueue queue) {

        this.storeServer = dataStoreServer;
        this.config = config;
        this.drainerId = index;
        this.masterDataStore = masterDataStore;
        this.queue = queue;
        startQueueDrainer();
    }

    private void sendMapInBatches(BatchResult batchResult) {

        int batchResultSize = config.batchResultSize();
        final String clientId = batchResult.clientId();
        final Map<String, String> results = batchResult.getResults();

        if (debug) logger.info("ResponseHandler::sendMapInBatches()", clientId, results.size());
        if (results.size() <= batchResultSize) {
            sendBatchResponse(batchResult);
        } else {

            LazyMap map = new LazyMap(results.size() > batchResultSize ? batchResultSize : results.size(), true);

            final Set<String> keys = results.keySet();

            for (String key : keys) {
                map.put(key, results.get(key));
                if (map.size() >= batchResultSize) {
                    sendBatchResponse(new BatchResult(batchResult, map.clearAndCopy()));

                }
            }

            if (map.size() > 0) {
                sendBatchResponse(new BatchResult(batchResult, map.clearAndCopy()));
            }

        }
    }

    private void sendBatchResponse(BatchResult batchResult) { //(final String clientId, Map<String, Object> map) {

        final String clientId = batchResult.clientId();
        if (!storeServer.clientExistsStill(clientId)) {
            return;
        }

        if (debug) logger.info("ResponseHandler::sendBatchResponse", batchResult);

        if (batchResult.getResults().size() == 0) {
            return;
        }

        storeServer.sendMessageToClientId(clientId, batchResult.toTextMessage());
    }

    private void counter(StatCount statCount) {

        stats.put(statCount.toKey(), statCount);
    }

    private void counter(int size, DataStoreSource source) {

        switch (source) {
            case LOCAL_DB:
                status.fromLocalDB += size;
                break;
            case REMOTE_DB:
                status.fromRemoteDB += size;
                break;
            case MEMORY:
                status.fromMemory += size;
                break;
            case END:
                status.notFound += size;
                break;

            default:
                logger.warn("ResponseHandler::UNKNOWN source", source, size);
        }

    }

    private void handleStatFromDataStore(Stat stat) {
        if (stat instanceof StatCount) {
            counter((StatCount) stat);
        } else if (stat instanceof StatsResults) {
            handleCollectStats((StatsResults) stat);
        }
    }

    private void handleCollectStats(StatsResults result) {
        Map<String, StatCount> map = Maps.copy(stats);

        result.setResults(map);

        storeServer.sendMessageToClientId(result.request().clientId(), result.toTextMessage());
    }

    private static class Status {
        long fromRemoteDB;
        long fromLocalDB;
        long fromMemory;
        long notFound;
    }

    public void setVerboseStatus(boolean verboseStatus) {
        this.verboseStatus = verboseStatus;
    }
}