/*******************************************************************************
 * Copyright (c) 2015 Attila Kovacs <attila[AT]sigmyne.com>.
 * All rights reserved. 
 * 
 * This file is part of crush.
 * 
 *     crush is free software: you can redistribute it and/or modify
 *     it under the terms of the GNU General Public License as published by
 *     the Free Software Foundation, either version 3 of the License, or
 *     (at your option) any later version.
 * 
 *     crush is distributed in the hope that it will be useful,
 *     but WITHOUT ANY WARRANTY; without even the implied warranty of
 *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *     GNU General Public License for more details.
 * 
 *     You should have received a copy of the GNU General Public License
 *     along with crush.  If not, see <http://www.gnu.org/licenses/>.
 * 
 * Contributors:
 *     Attila Kovacs <attila[AT]sigmyne.com> - initial API and implementation
 ******************************************************************************/

package crush;


import java.util.concurrent.ArrayBlockingQueue;

import jnum.data.DataPoint;
import jnum.data.WeightedPoint;

/**
 * 
 * A class that aims to reduce the creation of commonly used temporary arrays during data reduction, and thereby 
 * improve performance. This is achieved by creating new arrays, of the supported types, only as needed, and then
 * recycling them for future use.
 * <p>
 * 
 * Commonly, {@link Instrument}, {@link Integration}, and {@link SourceModel} have built-in recycler objects that
 * manage the reuse of commonly used arrays for their particular data sizes.
 * <p> 
 * 
 * When the user needs a temporary array storage for holding temporary data for one of these objects, one
 * should obtain an instance using a <code>get[...]Array(int size)</code> call, which will return an uninitialized 
 * used or new array, with capacity <i>at least</i> that of the desired <code>size</code> or larger. Then, once
 * the temporary array is no longer needed, one should call one of the {@link #recycle()} methods for reuse.
 * <p>
 * 
 * As mentioned, the arrays obtained through the recycler are typically uninitialized, since it is assumed
 * the user will populate it with its own data. If needed, the user should explicitly initialize the array
 * after obtaining it, e.g. with an {@link Arrays#fill()}, or an appropriate initializing loop.
 * <p>
 * 
 * 
 * @author Attila Kovacs <attila@sigmyne.com>
 * 
 */
public class Recycler {
	private ArrayBlockingQueue<int[]> ints;
	private ArrayBlockingQueue<float[]> floats;
	private ArrayBlockingQueue<double[]> doubles;
	private ArrayBlockingQueue<DataPoint[]> points;

	public Recycler() {}
	
	public Recycler(int capacity) {
		this();
		setSize(capacity);
	}
	
	public synchronized int[] getIntArray(int size) {
		if(ints != null) if(!ints.isEmpty()) {
			try { 
				int[] i = ints.take(); 
				if(i.length == size) return i;	
			}
			catch(InterruptedException e) { CRUSH.error(this, e); }
		}
		return new int[size];
	}
	
	
	public synchronized float[] getFloatArray(int size) {
		if(floats != null) if(!floats.isEmpty()) {
			try { 
				float[] f = floats.take(); 
				if(f.length == size) return f;	
			}
			catch(InterruptedException e) { CRUSH.error(this, e); }
		}
		return new float[size];
	}
	
	public synchronized double[] getDoubleArray(int size) {
		if(doubles != null) if(!doubles.isEmpty()) {
			try { 
				double[] d = doubles.take(); 
				if(d.length == size) return d;	
			}
			catch(InterruptedException e) { CRUSH.error(this,  e); }
		}
		return new double[size];
	}
	
	public synchronized DataPoint[] getDataPointArray(int size) {
		if(points != null) if(!points.isEmpty()) {
			try { 
				DataPoint[] p = points.take(); 
				if(p.length == size) return p;	
			}
			catch(InterruptedException e) { CRUSH.error(this, e); }
		}
		return DataPoint.createArray(size);
	}
	
	public synchronized void recycle(int[] array) { 
		if(ints == null) return;
		
		if(ints.remainingCapacity() > 0) {
			try { ints.put(array); }
			catch(InterruptedException e) {}
		}
		else CRUSH.warning(this, "int[] recycler overflow.");
	}
	
	public synchronized void recycle(float[] array) { 
		if(floats == null) return;
		
		if(floats.remainingCapacity() > 0) {
			try { floats.put(array); }
			catch(InterruptedException e) {}
		}
		else CRUSH.warning(this, "float[] recycler overflow.");
	}
	
	public synchronized void recycle(double[] array) { 
		if(doubles == null) return;
		
		if(doubles.remainingCapacity() > 0) {
			try { doubles.put(array); }
			catch(InterruptedException e) {}
		}
		else CRUSH.warning(this, "double[] recycler overflow.");
	}
	
	public synchronized void recycle(WeightedPoint[] array) {
		if(!(array instanceof DataPoint[])) return;
		if(points == null) return;
		
		if(points.remainingCapacity() > 0) {
			try { points.put((DataPoint[]) array); }
			catch(InterruptedException e) {}
		}
		else CRUSH.warning(this, "WeightedPoint[] recycler overflow.");
	}
	
	public synchronized void clear() {
	    ints.clear();
		floats.clear();
		doubles.clear();
		points.clear();
	}
	
	public int size() {
		return floats == null ? 0 : floats.size();
	}
	
	public void setSize(int capacity) {
		if(size() <= 0) {
		    ints = null;
			floats = null;
			doubles = null;
			points = null;
		}
		if(size() != capacity) {
		    ints = new ArrayBlockingQueue<>(capacity);
			floats = new ArrayBlockingQueue<>(capacity);
			doubles = new ArrayBlockingQueue<>(capacity);
			points = new ArrayBlockingQueue<>(capacity);
		}
	}
	
}
