
import java.awt.*;
import java.io.*;
import javax.swing.*;

/**
 * Provides the static framework for a cellular autonome, comprised of an array of Cell objects
 */
public class CellularAutonome11 extends JComponent {
    // Grid of Cells
    
    /**
     * Number of cells along cellular autonome grid axis
     */
    private static int Nx ;
    
    /**
     * Output file name root with path attached
     */
    private static String fileNameRoot ="a";
    public static final double logConversionFactor = Math.log(10);
    /**
     * Total number of cells in cellular autonome
     */
    public static int TotalCellNumber;
    
    /**
     * Number of timestep iterations
     */
    public static int runtimeDuration;
    /**
     * Case integer used to determine loading method
     */
    public static int loadingMechanism;
    public static int neighbourNumber=6;
    /**
     * Total number of ruptures in a timestep
     */
    public static int totalNumberOfRuptures;
    public static int runningRuptureCount;
    private static final int binWidth = 500;
    /**
     * Dissipation factor
     */
    private static double beta;
    
    private static double geometricFactor;
    private static double asymmetricFactor = 1.0;
    
    private static int histOutputPeriod=50;
    private static int histOutputTime;
    
    /**
     * Boolean question on whether to use periodic boundary conditions, default is true.
     */
    private static boolean usePeriodicBC;
    
    /**
     * Number of times a loading increment has been added.
     * This is equivalent to the number of ruptures in the time continuous model 
     * and equivalent to the number uniform load additions in the time continuous.
     */
    public static int time = 0;
    
    /**
     * Total stress load added to each cell from driving plate
     */
   public static double totalLoadingStress = 0;
   
   public static double loadingStressOutputIncrement=0.05;
   public static double nextOutputLoadingStress;
    /**
     * Array of Cell objects that make up the cellular autonome
     */
    public static Cell11[] allCells;
    
    /**
     * Are there new ruptures to propogate?
     */
    public static boolean newRuptures = false;
    
    /**
     * File writer
     */
    private static FileWriter fw;
    
    /**
     * File writed for area stats
     */
    private static FileWriter fwArea, fwStress;
    /**
     * File writer for rupture number and total loading
     */
    public static FileWriter fwRuptureNumber;
    
    /**
     * Maximum rupture strength threshold for lattice
     */
    public static double maxStrength = 1.0;
    
    /**
     * Minimum rupture strength threshold for lattice
     */
    public static double minStrength = 1.0;
    
    /**
     * Percentage noise to be added
     */
    public static double percentageNoise = 0;
    /**
     * Magnitude of the constant stress increment
     */
    public static double constantStressIncrement = 0.001;
    
    public static double maxRelaxStressFactor = 0;
    /**
     * Bin of frequency magnitude for energy levels in leaf springs
     */
    private static int[] leafSpringEnergyBin;
    private static int[] leafSpringStressBin;
    private static int[] leafIncrementStressBin;
    private static int[] connectingIncrementStressBin;
    private static int[] cumLeafStressBin;
    private static int[] cumConnectingStressBin;
    private static double totalCumLeaf, totalCumConnecting;
    /**
     * Bin of frequency magnitude for energy levels in connecting springs
     */
    private static int[] connectingSpringEnergyBin;
    private static int[] connectingSpringStressBin;
    public static int[] areaFreqHist;
    public static int[] areaFreqHist2;
    public static int[] areaFreqHistInterval,areaFreqHistCum;
    
    public static int [] initialRuptureSite;
    public static int [] numberOfRupturesFromSite;

    /**
     * Initialises the array of cells with their initial values
     *
     */
    public void init() {
        TotalCellNumber = Nx * Nx;
        totalLoadingStress = 0;
        leafSpringEnergyBin = new int [binWidth];
        connectingSpringEnergyBin = new int [binWidth];
        leafSpringStressBin = new int [binWidth];
        connectingSpringStressBin = new int [binWidth];
        areaFreqHist = new int [TotalCellNumber+1];
        areaFreqHist2 = new int [TotalCellNumber+1];
        areaFreqHistInterval = new int [TotalCellNumber+1];
        areaFreqHistCum = new int [TotalCellNumber+1];
        initialRuptureSite = new int [TotalCellNumber];
        numberOfRupturesFromSite = new int [TotalCellNumber];
//        geometricFactor = 1/( 2 + 2*asymmetricFactor );
        double doubleNeighbourNumber = (double) neighbourNumber;
        geometricFactor = 1/doubleNeighbourNumber;
        System.out.println (geometricFactor);
        runningRuptureCount=0;
        
        nextOutputLoadingStress=loadingStressOutputIncrement;
        histOutputTime = histOutputPeriod;
                
        leafIncrementStressBin = new int [binWidth];
        connectingIncrementStressBin = new int [binWidth];
        cumConnectingStressBin = new int [binWidth];
        cumLeafStressBin = new int [binWidth];
        
        for (int i=0; i<=TotalCellNumber; i++ ){ areaFreqHist[i]=0; }
        
        // Create an array of Cell objects
        allCells = new Cell11[TotalCellNumber];
        Cell11.setAutonomeCellParameters( Nx, neighbourNumber, maxStrength, minStrength, beta, usePeriodicBC, maxRelaxStressFactor);
        for (int i = 0; i < Nx; i++)
            for (int j = 0; j < Nx; j++){
                int n = i + j * Nx;
//                allCells[n] = new Cell11( i, j, Nx, maxStrength, minStrength, beta, usePeriodicBC, maxRelaxStressFactor);
                allCells[n] = new Cell11( i, j);
            }
    }
    
    /**
     * Initialise data streams
     *
     */
    public void initStreams() {
        try {
            String fileName = fileNameRoot + "-meanStress.out";
            fw = new FileWriter( fileName );
        } catch ( IOException e ) { System.out.println("Cannot open logger file"); }
        try {
            String fileName = fileNameRoot + "-freqAreaIntervals.out";
            fwArea = new FileWriter( fileName );
        } catch ( IOException e ) { System.out.println("Cannot open freqArea file"); }
        try {
            String fileName = fileNameRoot + "-StressIntervals.out";
            fwStress = new FileWriter( fileName );
        } catch ( IOException e ) { System.out.println("Cannot open fwStress file"); }
        try {
            String fileName = fileNameRoot + "-RuptureNoAndStressEvolution.out";
            fwRuptureNumber = new FileWriter( fileName );
            fwRuptureNumber.write("time   ruptureCount   totalStressLoad \n");
        } catch ( IOException e ) { System.out.println("Cannot open totalRuptureNumber file"); }        
    }
    
    /**
     * Print the runtime parameters to file
     */
    public void printWorldParameters(){
        try {
            String fileName = fileNameRoot + "-worldPara.out";
            FileWriter fwPara = new FileWriter( fileName );
            fwPara.write("fileNameRoot, " + fileNameRoot + "\n");
            fwPara.write("Nx, " + Nx + "\n");
            fwPara.write("TotalCellNumber, " + TotalCellNumber + "\n");
            fwPara.write("neighbourNumber, " + neighbourNumber + "\n");
            fwPara.write("beta, " + beta + "\n");
            fwPara.write("loadingMechanism, " + loadingMechanism + "\n");
            fwPara.write(" 1: Constant, 2: To rupture, 3: Random \n");
            fwPara.write("constantStressIncrement, " + constantStressIncrement +"\n");
            fwPara.write("maxStrength, " + maxStrength + "\n");
            fwPara.write("minStrength, " + minStrength + "\n");
            fwPara.write("percentageNoise, " + percentageNoise + "\n");
            fwPara.write("usePeriodicBC, " + usePeriodicBC + "\n");
            fwPara.write("runtimeDuration, " + runtimeDuration + "\n");
            fwPara.write("maxRelaxStressFactor, " + maxRelaxStressFactor + "\n");
            fwPara.write("geometricFactor, " + geometricFactor + "\n");
            fwPara.write("asymmetricFactor, " + asymmetricFactor + "\n");
            fwPara.close();
        } catch ( IOException e ) { System.out.println("Cannot open logger file"); }
        
    }
    
    public void propagateStressToNeighboursOf(int n){
        double stressIncrement = geometricFactor * allCells[n].getOverFlowStress();
        int neighbour = allCells[n].getNeighbour( 0 ) ;
        
        if( neighbour >-1 ) { 
            allCells[neighbour].propagateStress(stressIncrement); 
            initialRuptureSite[neighbour] = initialRuptureSite[n];
        }

        neighbour = allCells[n].getNeighbour( 1 ) ;
        if( neighbour >-1 ) { 
            allCells[neighbour].propagateStress(stressIncrement); 
            initialRuptureSite[neighbour] = initialRuptureSite[n];
        }

        neighbour = allCells[n].getNeighbour( 2 ) ;
        if( neighbour >-1 ) { 
            allCells[neighbour].propagateStress(asymmetricFactor * stressIncrement); 
            initialRuptureSite[neighbour] = initialRuptureSite[n];
        }

        neighbour = allCells[n].getNeighbour( 3 ) ;
        if( neighbour >-1 ) { 
            allCells[neighbour].propagateStress(asymmetricFactor * stressIncrement); 
            initialRuptureSite[neighbour] = initialRuptureSite[n];
        }      
    }
    
    /**
     *
     *
     */
    public void sendDataToDataStream() {
        double totalStress = 0 ;
        double cellStress;
        String s;
        
        /*	try {
                fw.write("Time " + time + "\n");
                } catch ( IOException e ) { System.out.println("Cannot write to logger file"); }
         */
        try {LomnitzAutonome.autonomeThread.sleep(50);} catch (InterruptedException e) {}
        for (int n = 0; n < TotalCellNumber; n++){
            cellStress = allCells[n].getCellStress();
            totalStress += cellStress;
        }
        
        double meanStress = totalStress / TotalCellNumber ;
        
        try { fw.write(meanStress + "\n") ; } catch (IOException e) {}
        try {	LomnitzAutonome.autonomeThread.sleep(50);	} catch (InterruptedException e) {}
        
        // Bin leaf spring energy proxy
        for(int n = 0; n < TotalCellNumber; n++){
            cellStress = allCells[n].getCellStress();
            
            int cellStressBin = (int) (cellStress * binWidth);
            leafSpringStressBin[cellStressBin]++;
            
            double cellEnergyProxy = binWidth * cellStress * cellStress;
            int cellEnergyBin = (int) cellEnergyProxy ;
            leafSpringEnergyBin[cellEnergyBin]++;
        }
        
        //Bin connecting spring energy proxy
        for(int n = 0; n < TotalCellNumber; n++){
            cellStress = allCells[n].getCellStress();
           
            for(int i=0 ; i<4 ; i++){
                int neighbour = allCells[n].getNeighbour(i) ;
                if(neighbour >=0 ) {
                    double connectingSpringStressDiff = Math.abs(cellStress - allCells[neighbour].getCellStress());
                    
                    int cellStressBin = (int) (connectingSpringStressDiff * binWidth);
                    connectingSpringStressBin[cellStressBin]++;
                    
                    int cellEnergyBin = (int) (binWidth * connectingSpringStressDiff * connectingSpringStressDiff);
                    connectingSpringEnergyBin[cellEnergyBin]++;
                }
            }
        }
        
//        double nextIncrement = getStressIncrement( loadingMechanism );
//        if( totalLoadingStress + nextIncrement > nextOutputLoadingStress ){
        if( totalLoadingStress >= nextOutputLoadingStress ){
//            double incrementToOutput = nextOutputLoadingStress-totalLoadingStress;
//            totalLoadingStress += incrementToOutput;
//            for (int n = 0; n < TotalCellNumber; n++){
//                allCells[n].incrementStress(incrementToOutput, 0);
//            }
//        if(histOutputTime==time){
            try{
                fwArea.write("Time = " + time + " runningRuptureCount = " + runningRuptureCount);
            } catch ( IOException e ) { System.out.println("Cannot print to running histogram file"); }      
            writeAreaFreqHist( TotalCellNumber, areaFreqHistInterval,areaFreqHistCum, fwArea);
            histOutputTime += histOutputPeriod;
            for(int n=1 ;n<=TotalCellNumber; n++){
                areaFreqHistInterval[n]=0;
            }
            
            for(int n = 0; n < binWidth; n++){
                connectingIncrementStressBin[n]=0;
                leafIncrementStressBin[n]=0;
            }
                    
            for(int n = 0; n < TotalCellNumber; n++){
                cellStress = allCells[n].getCellStress();
                
                int cellStressBin = (int) (cellStress * binWidth);
                leafIncrementStressBin[cellStressBin]++;
                cumLeafStressBin[cellStressBin]++;
                totalCumLeaf++;
           
                for(int i=0 ; i<4 ; i++){
                    int neighbour = allCells[n].getNeighbour(i) ;
                    if(neighbour >=0 ) {
                        double connectingSpringStressDiff = Math.abs(cellStress - allCells[neighbour].getCellStress());
                    
                        cellStressBin = (int) (connectingSpringStressDiff * binWidth);
                        connectingIncrementStressBin[cellStressBin]++;  
                        cumConnectingStressBin[cellStressBin]++;                 
                        totalCumConnecting++;
                    }
                }
            }
        
            try{
                fwStress.write("> time =" + time + " \n");
                for(int n=0 ;n<binWidth; n++){
                    double leafIncrementStress =  leafIncrementStressBin[n];
                    double connectingIncrementStress =  0.25 * connectingIncrementStressBin[n];
                    double leafCumStress =  cumLeafStressBin[n]/totalCumLeaf;
                    double connectingCumStress =  cumConnectingStressBin[n]/totalCumConnecting;
                    fwStress.write(n + " " + leafIncrementStress + " " + connectingIncrementStress + " " + leafCumStress + " " + connectingCumStress + " \n");
                }
            } catch ( IOException e ) { System.out.println("Cannot write to stressIncrement file"); }
        }
        
    }
    
    /**
     * Method determines the stress increment to be added when it is used to propogate only one rupture
     *
     *
     * @return minimum stress increment required to initiate next rupture
     */
    public double proximityToFailure() {
        double minimumProximityToFailure = 1 ;
        
        for (int n = 0; n < TotalCellNumber; n++){
            if(allCells[n].getProximityToFailure() < minimumProximityToFailure) {
                minimumProximityToFailure = allCells[n].getProximityToFailure();
            }
        }
        
        return minimumProximityToFailure;
    }
    
    /**
     * returns beta
     *
     *
     * @return beta
     */
    public double getBeta() {
        return beta;
    }
    
    /**
     * Set cell dimension
     * @param value Number of cells along an axis
     */
    public void setNx(int value){
        Nx = value;
        TotalCellNumber = Nx * Nx;
    }
    
    public void setNeighbourNumber(int value){
        neighbourNumber = value;
    }
        
    /**
     * Sets beta
     * @param value Conservation factor
     */
    public void setBeta(double value) { beta = value; }
    
    public void setAsymmetricFactor(double value) { asymmetricFactor = value; }
    
    public void setPeriodicBC(boolean value ){ usePeriodicBC = value; }
    /**
     * sets maximumStrength
     * @param value The maximum cell strength value
     */
    public void setMaxStrength(double value) { maxStrength = value; }
    
    /**
     * sets minimumStrength
     * @param value The minimum cell strength value
     */
    public void setMinStrength(double value) { minStrength = value; }
    
    public void setFileNameRoot(String value) { fileNameRoot = value; }
    
    public void setLoadingMechanism(String value){
        if(value.equals("constantLoading")){loadingMechanism = 1;}
        if(value.equals("loadToRupture")){loadingMechanism = 2;}
        if(value.equals("randomLoading")){loadingMechanism=3;}
    }
        
    public double getStressIncrement(int loadingMechanism){
        double stressIncrement = 0.0;
        if(loadingMechanism==1){stressIncrement =  constantStressIncrement;}
        if(loadingMechanism==2){stressIncrement =  1.005 * proximityToFailure();}
        if(loadingMechanism==3){stressIncrement =  0.01 * Math.random();}
        
        return stressIncrement;
    }
    /**
     *
     *
     *
     * @param value
     */
    public void setPercentageNoise(double value) { percentageNoise = value; }
    
    public void setRelaxationStressFactor(double value) { maxRelaxStressFactor = value; }
    
    /**
     *
     *
     */
    public void startAutonome() { LomnitzAutonome.autonomeThread.start(); }
    
    /**
     * Method to pause cellularAutonome
     *
     */
    public void pauseAutonome() {
        synchronized (LomnitzAutonome.autonomeThread) { 
            LomnitzAutonome.autonomeThread.pleaseWait = true;
        }
    }
    
    /**
     * Method to resume running of cellularAutonome after it has been paused
     *
     */
    public void resumeAutonome() {
        synchronized (LomnitzAutonome.autonomeThread) {
            LomnitzAutonome.autonomeThread.pleaseWait = false ;
            LomnitzAutonome.autonomeThread.notify() ;
        }
    }
    
    /**
     * Method to save data safely by closing files before exiting program
     *
     */
    public void saveData() {
        //close meanStress File
        try { fw.close();} catch ( IOException e ) { System.out.println("Cannot close meanStress file"); }
        try { fwStress.close();} catch ( IOException e ) { System.out.println("Cannot close stressIncrement file"); }
        try { fwArea.close();} catch ( IOException e ) { System.out.println("Cannot close freqArea file"); }
        try { fwRuptureNumber.close();} catch ( IOException e ) { System.out.println("Cannot close fwRuptureNumber file"); }
        
        double total = 0;
        for(int n=0 ;n<binWidth; n++){ total +=leafSpringEnergyBin[n];}
        
        System.out.println("Total = " + total ) ;
        
        // LeafSpringEnergy distribution
        try{
            String fileName = fileNameRoot + "-LSE.out";
            FileWriter lse = new FileWriter( fileName );
            lse.write("n normLeafSpringEnergyBin normLeafSpringStressBin \n");
            for(int n=0 ;n<binWidth; n++){
                double normLeafSpringEnergyBin = leafSpringEnergyBin[n] / total;
                double normLeafSpringStressBin = leafSpringStressBin[n] / total;
                lse.write(n + " " + normLeafSpringEnergyBin + " " + normLeafSpringStressBin + " \n");
            };
            lse.close();
        } catch ( IOException e ) { System.out.println("Cannot close leaf energy file"); }
                
        
        // ConnectingSpringEnergy distribution
        try{
            String fileName = fileNameRoot + "-CSE.out";
            FileWriter cse = new FileWriter( fileName );
            writeCSE (binWidth, connectingSpringEnergyBin , total, cse);       
            cse.close();
        } catch ( IOException e ) { System.out.println("Cannot close contact energy file"); }
        
        // Area Frequency histogram file
        try{
            String fileName = fileNameRoot + "-AreaFreqHist.out";
            FileWriter fwArea = new FileWriter( fileName );
            writeAreaFreqHist( TotalCellNumber, areaFreqHist, fwArea);            
            fwArea.close();
        } catch ( IOException e ) { System.out.println("Cannot close contact energy file"); }
        
        
        try{
            String fileName = fileNameRoot + "-AreaFreqHist2.out";
            FileWriter fwArea = new FileWriter( fileName );
            writeAreaFreqHist(TotalCellNumber, areaFreqHist2, fwArea);
            fwArea.close();
        } catch ( IOException e ) { System.out.println("Cannot close contact energy file"); }
        
        System.out.println("Saved and closed logger file");
    }
    
   public static void writeAreaFreqHist(int TotalCellNumber, int areaFreqHist[], FileWriter fw){        
        try{
            for(int n=1 ;n<=TotalCellNumber; n++){
                if(areaFreqHist[n]>0){
                    double logN = Math.log(n) / logConversionFactor;
                    double logArea = Math.log( areaFreqHist[n] ) / logConversionFactor;
                    fw.write(logN + " " + logArea + " \n");
                }
            }
        } catch ( IOException e ) { System.out.println("Cannot write to histogram file"); }
    }

   public static void writeAreaFreqHist(int TotalCellNumber, int areaFreqHist[], int areaFreqHist2[], FileWriter fw){        
        try{
            for(int n=1 ;n<=TotalCellNumber; n++){
                if((areaFreqHist[n]>0)||(areaFreqHist2[n]>0)){
                    double logN = Math.log(n) / logConversionFactor;
                    double logArea = Math.log( areaFreqHist[n] ) / logConversionFactor;
                    double logArea2 = Math.log( areaFreqHist2[n] ) / logConversionFactor;
                    fw.write(logN + " " + logArea + " " + logArea2 + " \n");
                }
            }
        } catch ( IOException e ) { System.out.println("Cannot write to histogram file"); }
    }

   public static void writeCSE (int binNumber, int connectingSpringEnergyBin[] , double total, FileWriter fw){        
        try{
            fw.write("n normConnectingSpringEnergyBin normConnectingSpringStressBin \n");
            for(int n=0 ;n<binNumber; n++){
                double normConnectingSpringEnergyBin =  0.25 * connectingSpringEnergyBin[n] / total;
                double normConnectingSpringStressBin =  0.25 * connectingSpringStressBin[n] / total;
                fw.write(n + " " + normConnectingSpringEnergyBin + " " + normConnectingSpringStressBin + " \n");
            }
        } catch ( IOException e ) { System.out.println("Cannot write to CSE file"); }
    }
 
    /**
     * Draws cellular autonome according to some colour specification
     *
     *
     * @param g
     */
    public void paint(Graphics g) {
        
        Graphics2D g2 = (Graphics2D)g;
        int cellWidth = 3;
        
        // Paint cell structure colored by stress magnitude:
        for (int n = 0; n < TotalCellNumber; n++){
            
            // paint each cell using paint ( g2, type, cellWidth, xOffset, yOffset)
            allCells[n].paint( g2, 1, cellWidth, 10, 20);
            allCells[n].paint( g2, 2, cellWidth, 330, 20);
            allCells[n].paint( g2, 3, cellWidth, 650, 20);
        }
        
        g2.drawString("beta = " + beta + "    maxStrength = " + maxStrength + "    minStrength = " + minStrength, 20, 335);
        g2.drawString("Percentage noise = " + percentageNoise, 10, 348);
    }
    
    /**
     *
     *
     */
    public static void countClusterAreas() {
        int clusterNumber[] = new int[TotalCellNumber+1];
        int ruptureAreaCount[] = new int[TotalCellNumber+1];
        int maxNumber = 0;
        int ruptureCount = 0;
        int up;
        int number=0;
        int neighbour1, neighbour2, neighbour3, neighbour4 ;
        int totalNumberOfRuptures=0;
        /* If element not ruptured, clusterNumber[]=0, else  clusterNumber = number of ruptures already counted */
        for (int n = 0; n < TotalCellNumber; n++){
            ruptureAreaCount[n] = 0;
            
            if(allCells[n].isCellRuptured()){
                ruptureCount++;
                clusterNumber[n] = ruptureCount;
            }
            else { clusterNumber[n] = 0; }
        }
        for(int t=0; t<150; t++) {
            for (int n = 0; n < TotalCellNumber; n++){
                if (clusterNumber[n]>0){
                    neighbour1 = allCells[n].getNeighbour(0);
                    neighbour2 = allCells[n].getNeighbour(1);
                    neighbour3 = allCells[n].getNeighbour(2);
                    neighbour4 = allCells[n].getNeighbour(3);
                    up=0;
                    if((neighbour1 > -1)&&(neighbour2 > -1)){up = Math.max( clusterNumber[neighbour1], clusterNumber[neighbour2]);}
                    if(neighbour3 > -1){up = Math.max( up, clusterNumber[neighbour3] );}
                    if(neighbour4 > -1){up = Math.max( up, clusterNumber[neighbour4] );}
                    if (up>clusterNumber[n]){ clusterNumber[n] = up; }
                }
            }
        }
        /* Run through cluster, and count similar values into breakAmount */
        for(int n=0; n<TotalCellNumber; n++) {
            number = clusterNumber[n];
            if (number>maxNumber){ maxNumber = number; }
            ruptureAreaCount[number] ++ ;
        }
        
        for(int n=1; n<maxNumber; n++) {
            if(ruptureAreaCount[n]>0){
                //                    System.out.println(ruptureAreaCount[n]);
                areaFreqHist[ruptureAreaCount[n]]++;
                
                totalNumberOfRuptures++;
                
//                try{
//                    fwArea.write(totalNumberOfRuptures + " " + ruptureAreaCount[n] + " \n");}
//                catch ( IOException e ) { System.out.println("Cannot print to freqArea file"); }
            }
        }
    }
}
