package preypredator;
import it.unimi.dsi.fastutil.ints.IntArrayList;

import java.util.Random;

//import mcsma.utils.IntMatrix;

public class PreyPredator {
	
	public static final int WIDTH = 10;
	public static final int HEIGHT = 10;
	
	public static final float GRASS_MIN = 0;
	public static final float GRASS_MAX = 100;
	public static final float GRASS_ORIG = 20;
	public static final float GRASS_FACT = 1.1f;
	
	public static final float PREY_ORIG = 10;
	public static final int PREY_RADIUS = 5;
	public static final int PREY_MAX = 100;
	
	public static final float PRED_ORIG = 10;
	public static final int PRED_RADIUS = 5;
	public static final int PRED_MAX = 100;
	
	// Attributes
	
	private final float[] grass;
	private final float[] preys;
	private final float[] preds;
	
	
	// Temporary buffers used in each step
	
	private final IntArrayList preysX;
	private final IntArrayList preysY;
	private final IntArrayList predsX;
	private final IntArrayList predsY;
	
	private final PPRuntime runtime;
	
	public PreyPredator(int nbPreys, int nbPredators, PPRuntime runtime) {
		this.grass = new float[WIDTH * HEIGHT];
		this.preys = new float[WIDTH * HEIGHT];
		this.preds = new float[WIDTH * HEIGHT];
		
		initGrid(grass, 50, GRASS_ORIG);
		initGrid(preys, 20, PREY_ORIG);
		initGrid(preds, 10, PRED_ORIG);
		
		this.preysX = new IntArrayList();
		this.preysY = new IntArrayList();
		this.predsX = new IntArrayList();
		this.predsY = new IntArrayList();
		
		this.runtime = runtime; 
	}	
	
	public void setup() {
		for (int j = 0; j < HEIGHT; j++) {
			for (int i = 0; i < WIDTH; i++) {
				if (preys[j * WIDTH + i] > 0) {
					preysX.add(i);
					preysY.add(j);
				}
				
				if (preds[j * WIDTH + i] > 0) {
					predsX.add(i);
					predsY.add(j);
				}
			}
		}
	}
	
	public void step() {
		//runtime.growGrass(grass, GRASS_FACT, GRASS_MIN, GRASS_MAX);
		
		for (int i = 0; i < HEIGHT; i++) {
			for (int j = 0; j < WIDTH; j++) {
				if (preys[i * WIDTH + j] > 0) {
					preysX.add(i);
					preysY.add(j);
				}
				
				if (preds[i * WIDTH + j] > 0) {
					predsX.add(i);
					predsY.add(j);
				}
			}
		}
		
		int[] newPreysX = new int[preysX.size()];
		int[] newPreysY = new int[preysY.size()];
		int[] newPredsX = new int[predsX.size()];
		int[] newPredsY = new int[predsY.size()];
		
		
		/*runtime.selectMaxTarget(preys, PREY_RADIUS,
				preysX.toIntArray(), preysY.toIntArray(),
				newPreysX, newPreysY);
		
		runtime.selectMaxTarget(preds, PRED_RADIUS,
				predsX.toIntArray(), predsY.toIntArray(),
				newPredsX, newPredsY);
		*/
		
		// PREYS
		
		for (int i = 0; i < preysX.size(); i++) {
			final int pos = preysY.get(i) * WIDTH + preysX.get(i);
			final int newPos = newPreysY[i] * WIDTH + newPreysX[i];
			
			// Check an eventual conflict with another prey
			if (preys[newPos] > 0) {
				continue;
			}
			
			// Movement
			preys[newPos] = preys[pos];
			preys[pos] = 0;
			
			// Eating
			if (grass[newPos] >= 0) {
				preys[newPos] = clamp(preys[newPos] + grass[newPos], 0, PREY_MAX);
				grass[newPos] = 0;
			}
			
			// Reproduction
			if (preys[newPos] == PREY_MAX) {
				preys[pos] = PREY_ORIG;
			}
		}
		
		// PREDATOR
		
		for (int i = 0; i < predsX.size(); i++) {
			final int pos = predsY.get(i) * WIDTH + predsX.get(i);
			final int newPos = newPredsY[i] * WIDTH + newPredsX[i];
			
			// Check an eventual conflict with another predator
			if (preds[newPos] > 0) {
				continue;
			}
			
			// Movement
			preds[newPos] = preds[pos];
			preds[pos] = 0;
			
			// Eating
			if (preys[newPos] >= 0) {
				preds[newPos] = clamp(preds[newPos] + preys[newPos], 0, PRED_MAX);
				preys[newPos] = 0;
			}
			
			// Reproduction
			if (preds[newPos] == PRED_MAX) {
				preds[pos] = PRED_ORIG;
			}
		}
		
	}
	
	private void initGrid(float[] grid, int nbCells, float value) {
		int remainingCells = nbCells;
		Random rng = new Random();
		
		while (remainingCells > 0) {
			final int pos = rng.nextInt(grid.length);
			
			if (grid[pos] == 0) {
				grid[pos] = value;
				remainingCells--;
			}
		}
	}
	
	// Helpers
	private float clamp(float value, float min, float max) {
		return Math.max(min, Math.min(value, max));
	}

}