/*
 * Created on Jan 24, 2005
 */
package uk.co.zonetora.emergence.ants;

import java.util.Random;
import java.util.concurrent.atomic.AtomicBoolean;

/**
 * @author tora
 */
class AntKernel implements Runnable {

    public AntKernel(IWorldUpate list) {
        this(300, 300, 5, MAX_ANTS, 1f, MAX_PHER, 10f, 300f, 0.05f, list);
    }

    private final int width;

    private final int height;

    private final int outRate;

    private final int maxSite;

    private final float pherDepOut;

    private final float pherDepIn;

    private final float pherDepOutMax;

    private final float pherDepInMax;

    private final float foodPercent;

    private final Random rndGen;

    private final AtomicBoolean stop;

    private final Object lock;

    public static final int MAX_FOOD = 250;
    public static final int MAX_PHER = 1000;
    public final static int FWD_ANTS = 0;
    public static final int MAX_ANTS = 25;
    public final static int BK_ANTS = 1;

    public final static int FOOD = 2;

    private final int[][][] grid;

    private final double[][] pheremone;

    private final IWorldUpate list;

    public AntKernel(int width, int height, int antoutrate, int maxsite,
            float pherDepOut, float pherDepOutMax, float pherDepIn,
            float pherDepInMax, float foodPercent, IWorldUpate listener) {
        this.width = width;
        this.height = height;
        this.outRate = antoutrate;
        this.maxSite = maxsite;
        this.pherDepOut = pherDepOut;
        this.pherDepIn = pherDepIn;
        this.pherDepInMax = pherDepInMax;
        this.pherDepOutMax = pherDepOutMax;
        this.stop = new AtomicBoolean(false);
        this.foodPercent = foodPercent;
        this.lock = new Object();
        this.rndGen = new Random();

        this.list = listener;

        this.grid = new int[3][this.width][this.height];
        this.pheremone = new double[this.width][this.height];

        initFood(grid[FOOD]);

        list.worldUpate(grid, pheremone, 0);
    }

    public void run() {

        long cycle = 0;

        long time = 0;

        while (!stop.get()) {

            //System.out.println();

            time = System.currentTimeMillis();
            // Move the ants backwards
            doBackwards();
            time = System.currentTimeMillis() - time;
            //System.out.println("Bks : " + time);

            time = System.currentTimeMillis();
            doForwards();
            time = System.currentTimeMillis() - time;
            //System.out.println("Fwds : " + time);

            time = System.currentTimeMillis();
            // Convert forward ants to backward ones
            for (int i = 0; i < this.width; i++) {
                for (int j = 0; j < this.height; j++) {
                    if (grid[FWD_ANTS][i][j] > 0 && grid[FOOD][i][j] > 0) {
                        int numAnts = grid[FWD_ANTS][i][j];
                        int numFood = grid[FOOD][i][j];
                        int numToTurn = numAnts > numFood ? numFood : numAnts;
                        grid[FWD_ANTS][i][j] -= numToTurn;
                        grid[BK_ANTS][i][j] += numToTurn;
                        grid[FOOD][i][j] -= numToTurn;
                    }
                }
            }
            time = System.currentTimeMillis() - time;
            //System.out.println("Convert: " + time);

            // Remove unneeded ants
            
            for(int i = 0 ; i < this.width ; i++ ) {
                grid[BK_ANTS][i][0] = 0;
                grid[FWD_ANTS][i][this.height-1] = 0;
            }
            
            for(int j = 0 ; j < this.height ; j++ ) {
                grid[BK_ANTS][0][j] = 0;
                grid[FWD_ANTS][this.width-1][j] = 0;
            }

            time = System.currentTimeMillis();
            // Do pheremones
            for (int i = 0; i < this.width; i++) {
                for (int j = 0; j < this.height; j++) {
                    double cPher = pheremone[i][j];

                    double pOutDep = this.pherDepOut * grid[FWD_ANTS][i][j];

                    if (cPher < this.pherDepOutMax) {
                        cPher = Math.min(this.pherDepOutMax, cPher + pOutDep);
                    }

                    float pInDep = this.pherDepIn * grid[BK_ANTS][i][j];
                    if (cPher < this.pherDepInMax) {
                        cPher = Math.min(this.pherDepInMax, cPher + pInDep);
                    }

                    cPher *= 27d / 30d;
                    
                    if( cPher >= 0 && cPher < MAX_PHER ) {
                        pheremone[i][j] = cPher;    
                    } else if( cPher < 0 ) {
                        cPher = 0;
                        pheremone[i][j] = cPher;
                    } else {
                        throw new IllegalStateException(""+cPher);
                    }
                    
                    
                }
            }
            time = System.currentTimeMillis() - time;
            //System.out.println("Pheremones: " + time);

            // Add the new ants
            for (int i = 0; i < outRate; i++) {
                if (accessible(0, 0)) {
                    //System.out.print(".");
                    grid[FWD_ANTS][0][0]++;
                } else {
                    break;
                }
            }

            cycle++;

            if (list != null) {
                list.worldUpate(grid, pheremone, cycle);
            }
        }

    }

    private void doForwards() {
        // Move the ants forwards
        for (int i = this.width - 1; i >= 0; i--) {
            for (int j = this.height - 1; j >= 0; j--) {
                if (grid[FWD_ANTS][i][j] == 0) {
                    continue;
                }

                double rPher = i + 1 < this.width ? pheremone[i + 1][j] : 0;
                double lPher = j + 1 < this.height ? pheremone[i][j + 1] : 0;

                double shouldMove = shouldMove(lPher, rPher);
                double moveLeft = moveLeft(lPher, rPher);

                int moving = (int) Math.round(grid[FWD_ANTS][i][j] * shouldMove
                        * (rndGen.nextDouble() + 0.5d));

                if (moving > grid[FWD_ANTS][i][j]) {
                    moving = grid[FWD_ANTS][i][j];
                }

                assert moving >= 0 : "" + grid[FWD_ANTS][i][j] + " " + shouldMove;
                
                int lMoving = (int) Math.round(moving * moveLeft
                        * (rndGen.nextDouble() + 0.5d));

                if (lMoving > moving) {
                    lMoving = moving;
                }

                assert lMoving >= 0;
                
                int rMoving = moving - lMoving;

                assert rMoving >= 0 : "" + moving + " " + lMoving + "" + rMoving;
                
                // move left
                if (j + 1 < this.height) {
                    grid[FWD_ANTS][i][j + 1] += lMoving;
                    if (totAnts(i,j+1) > this.maxSite ) {
                        int xtra = this.maxSite - grid[BK_ANTS][i][j+1];
                        int over = grid[FWD_ANTS][i][j + 1] - xtra;
                        grid[FWD_ANTS][i][j + 1] = xtra;
                        rMoving += over;
                    }

                } else {
                    rMoving += lMoving;
                }

                lMoving = 0;

                // move right
                if (i + 1 < this.width) {
                    grid[FWD_ANTS][i + 1][j] += rMoving;

                    if (totAnts(i+1,j) > this.maxSite) {
                        int xtra = this.maxSite - grid[BK_ANTS][i+1][j];
                        lMoving += grid[FWD_ANTS][i + 1][j] - xtra;
                        grid[FWD_ANTS][i + 1][j] = xtra;
                    }
                } else {
                    lMoving += rMoving;
                }

                if (j + 1 < this.height) {
                    grid[FWD_ANTS][i][j + 1] += lMoving;

                    if (totAnts(i,j+1) > this.maxSite) {
                        int xtra = this.maxSite - grid[BK_ANTS][i][j+1];
                        moving -= grid[FWD_ANTS][i][j + 1] - xtra;
                        grid[FWD_ANTS][i][j + 1] = xtra;
                    }
                } else {
                    moving -= lMoving;
                }

                grid[FWD_ANTS][i][j] -= moving;
                assert grid[FWD_ANTS][i][j] >= 0 : grid[FWD_ANTS][i][j];
            }
        }
    }

    private void doBackwards() {
        // Move the ants backwards
        for (int i = 0; i < this.width; i++) {
            for (int j = 0; j < this.height; j++) {
                if (grid[BK_ANTS][i][j] == 0) {
                    continue;
                }

                double lPher = j - 1 >= 0 ? pheremone[i][j-1] : 0;
                double rPher = i - 1 >= 0 ? pheremone[i-1][j] : 0;


                double shouldMove = shouldMove(lPher, rPher);
                double moveLeft = moveLeft(lPher, rPher);

                int moving = (int) Math.round(grid[BK_ANTS][i][j] * shouldMove
                        * (rndGen.nextDouble() + 0.5));

                assert moving >= 0 : "" + grid[BK_ANTS][i][j] + " " + shouldMove;
                
                if (moving > grid[BK_ANTS][i][j]) {
                    moving = grid[BK_ANTS][i][j];
                }

                int lMoving = (int) Math.round(moving * moveLeft
                        * (rndGen.nextDouble() + 0.5));

                if (lMoving > moving) {
                    lMoving = moving;
                }

                assert lMoving >= 0;
                
                
                int rMoving = moving - lMoving;

                assert rMoving >= 0 : "" + moving + " " + lMoving + "" + rMoving;
                
                // move left
                if (j - 1 >= 0) {
                    grid[BK_ANTS][i][j - 1] += lMoving;

                    if (totAnts(i,j-1) > this.maxSite) {
                        int xtra = (this.maxSite-grid[FWD_ANTS][i][j-1]);
                        int over = grid[BK_ANTS][i][j - 1] - xtra;
                        grid[BK_ANTS][i][j - 1] = xtra;
                        rMoving += over;
                    }

                } else {
                    rMoving += lMoving;
                }

                lMoving = 0;

                // move right
                if (i - 1 >= 0) {
                    grid[BK_ANTS][i - 1][j] += rMoving;

                    if (totAnts(i-1,j) > this.maxSite) {
                        int xtra = (this.maxSite - grid[FWD_ANTS][i-1][j]);
                        lMoving += grid[BK_ANTS][i - 1][j] - xtra;
                        grid[BK_ANTS][i - 1][j] = xtra;
                    }
                } else {
                    lMoving += rMoving;
                }

                if (j - 1 >= 0) {
                    grid[BK_ANTS][i][j - 1] += lMoving;

                    if (totAnts(i,j-1) > this.maxSite) {
                        int xtra = (this.maxSite - grid[FWD_ANTS][i][j-1]);
                        moving -= grid[BK_ANTS][i][j - 1] - xtra;
                        grid[BK_ANTS][i][j - 1] = xtra;
                    }
                } else {
                    moving -= lMoving;
                }
                
                
                grid[BK_ANTS][i][j] -= moving;
                assert grid[BK_ANTS][i][j] >= 0 : grid[BK_ANTS][i][j];
            }
        }
    }

    public void stop() {
        this.stop.set(true);
    }

    
    private final int totAnts(int x, int y) {
        return grid[BK_ANTS][x][y] + grid[FWD_ANTS][x][y];
    }
    
    private final double moveLeft(double pherLeft, double pherRight) {
        
        double l = (5 + pherLeft);
        l *= l;
        double r = (5 + pherRight);
        r *= r;
        
        assert l > 0 : "Bad l" + l;
        assert r > 0 : "Bad r" + r;
        
        double res = l / ( l + r ) ;
        
        assert res >= 0 : "Bad result" + res;
        assert res <= 1 : "Bad result" + res;
        return res;
    }

    private final double shouldMove(double pherLeft, double pherRight) {
        return rndGen.nextDouble();
    }

    private final boolean accessible(int x, int y) {
        return grid[FWD_ANTS][x][y] + grid[BK_ANTS][x][y] < maxSite;
    }

    private void initFood(int[][] foodGrid) {
        // step through 20 x 20 squares, if < % then each sqaure
        // in the 20x20 gets a random amount of food ( 0 - 1000 ) on it
/*
        for (int i = 60; i < foodGrid.length; i += 20) {
            for (int j = 60; j < foodGrid[i].length; j += 20) {
                if (rndGen.nextFloat() < foodPercent) {
                    for (int ii = i; ii < foodGrid.length && ii < i + 20; ii++) {
                        for (int jj = j; jj < foodGrid[ii].length
                                && jj < j + 20; jj++) {
                            foodGrid[ii][jj] = rndGen.nextInt(MAX_FOOD);
                        }
                    }
                }
            }
        }
*/
        
        for( int i = 30 ; i < foodGrid.length ; i++ ) {
            for(int j = 30 ; j < foodGrid[i].length ; j ++ ) {
                if( rndGen.nextFloat() < foodPercent) {
                    foodGrid[i][j] = rndGen.nextInt(MAX_FOOD);
                }
            }
        }
    }

    public int getWidth() {
        return this.width;
    }

    public int getHeight() {
        return this.height;
    }
}
