/*
 * Copyright 2002-2013 the original author or authors.
 * 
 * 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.
 */
package org.springframework.data.gemfire.function;

import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;

import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;

import com.gemstone.gemfire.cache.execute.ResultSender;

/**
 * Sends collection results using a {@link ResultSender} in chunks determined by batchSize
 * 
 * @author David Turanski
 * @since 1.3.0
 */
class BatchingResultSender  {
	private final int batchSize;
	private ResultSender<Object> resultSender;
	
	public BatchingResultSender(int batchSize, ResultSender<Object> resultSender) {
		Assert.notNull(resultSender, "resultSender cannot be null");
		Assert.isTrue(batchSize >= 0, "batchSize must be >= 0");
		this.batchSize = batchSize;
		this.resultSender = resultSender;
	}
	
 
	public void sendResults(Iterable<?> result) {
		if (batchSize == 0) {
			resultSender.lastResult(result);
			return;
		}	
	
		List<Object> chunk = new ArrayList<Object>(batchSize);
		
		for (Iterator<?> it = result.iterator(); it.hasNext();) {
		    if (chunk.size() < batchSize) {
				chunk.add(it.next());
		    }		    
			 
			if (chunk.size() == batchSize || !it.hasNext()) {
                if (it.hasNext()) {
                		resultSender.sendResult(chunk);
                } else {
                		resultSender.lastResult(chunk);
                }
           		chunk.clear();
			}
		}
	}

	
	public void sendArrayResults(Object result) {
		
		if (batchSize == 0) {
			resultSender.lastResult(result);
			return;
		}
		
		Assert.isTrue(ObjectUtils.isArray(result));		
		
		int length = Array.getLength(result);
		
		for (int from =0; from <  length; from += batchSize) {		
			int to = Math.min(length,from + batchSize);
			Object chunk = copyOfRange(result,from, to);

			if (to == length -1) {
				resultSender.lastResult(chunk);
			} else {
				resultSender.sendResult(chunk);
			}	
		}
	}


	/**
	 * @param result
	 * @param from
	 * @param to
	 * @return
	 */
	private Object copyOfRange(Object result, int from, int to) {
				
		Class<?> arrayClass = result.getClass();
		int size = to - from;
		
		if (int[].class.isAssignableFrom(arrayClass)) {
			int[] array = new int[size];
			for(int i = 0; i < size ; ++i){
                array[i] = Array.getInt(result, from + i);
            }
			return array;
		}
		
		if (float[].class.isAssignableFrom(arrayClass)) {
			float[] array = new float[size];
			for(int i = 0; i < size ; ++i){
                array[i] = Array.getFloat(result, from + i);
            }
			return array;
		}
		
		if (double[].class.isAssignableFrom(arrayClass)) {
			double[] array = new double[size];
			for(int i = 0; i < size ; ++i){
                array[i] = Array.getDouble(result, from + i);
            }
			return array;
		}
		
		if (boolean[].class.isAssignableFrom(arrayClass)) {
			boolean[] array = new boolean[size];
			for(int i = 0; i < size ; ++i){
                array[i] = Array.getBoolean(result, from + i);
            }
			return array;
		}
		
		if (byte[].class.isAssignableFrom(arrayClass)) {
			byte[] array = new byte[size];
			for(int i = 0; i < size ; ++i){
                array[i] = Array.getByte(result, from + i);
            }
			return array;
		}
		
		if (short[].class.isAssignableFrom(arrayClass)) {
			short[] array = new short[size];
			for(int i = 0; i < size ; ++i){
                array[i] = Array.getShort(result, from + i);
            }
			return array;
		}
		
		if (long[].class.isAssignableFrom(arrayClass)) {
			long[] array = new long[size];
			for(int i = 0; i < size ; ++i){
                array[i] = Array.getLong(result, from + i);
            }
			return array;
		}
		
		if (char[].class.isAssignableFrom(arrayClass)) {
			char[] array = new char[size];
			for(int i = 0; i < size ; ++i){
                array[i] = Array.getChar(result, from + i);
            }
			return array;
		}
		
	    return Arrays.copyOfRange((Object[])result, from, to);
	 
	}
}
