/**
 * Simulation Module
 * @author James D Bloom (UI)
 * @author Clare Clark (Maths)
 * @author Maxim (replacement UI and cleanup)
 *
 */

package pipe.modules.simulation;

import java.awt.Container;
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.text.DecimalFormat;
import java.util.ArrayList;

import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.border.EtchedBorder;
import javax.swing.border.TitledBorder;

import pipe.dataLayer.DataLayer;
import pipe.gui.CreateGui;
import pipe.gui.widgets.ButtonBar;
import pipe.gui.widgets.PetriNetChooserPanel;
import pipe.gui.widgets.ResultsHTMLPane;
import pipe.modules.Module;

public class Simulation implements Module {
  private static final String MODULE_NAME = "Simulation";

  private PetriNetChooserPanel sourceFilePanel;
  private ResultsHTMLPane results;
  
  private JTextField jtfFirings,jtfCycles;

  public void run(DataLayer pnmlData) {
    JDialog guiDialog = new JDialog(CreateGui.getApp(),MODULE_NAME,true);
    
    // 1 Set layout
    Container contentPane=guiDialog.getContentPane();
    contentPane.setLayout(new BoxLayout(contentPane,BoxLayout.PAGE_AXIS));
    
    // 2 Add file browser
    contentPane.add(sourceFilePanel    =new PetriNetChooserPanel("Source net",pnmlData));
    
    // 2.5 Add edit boxes
    JPanel settings=new JPanel();
    settings.setLayout(new BoxLayout(settings,BoxLayout.LINE_AXIS));
    settings.add(new JLabel("Firings:"));
    settings.add(Box.createHorizontalStrut(5));
    settings.add(jtfFirings=new JTextField("100",5));
    settings.add(Box.createHorizontalStrut(10));
    settings.add(new JLabel("Cycles:"));
    settings.add(Box.createHorizontalStrut(5));
    settings.add(jtfCycles=new JTextField("5",5));
    settings.setBorder(new TitledBorder(new EtchedBorder(),"Simulation parameters"));
    settings.setMaximumSize(new Dimension(Integer.MAX_VALUE,settings.getPreferredSize().height));
    contentPane.add(settings);
    
    // 3 Add results pane
    contentPane.add(results=new ResultsHTMLPane());
    
    // 4 Add button
    contentPane.add(new ButtonBar("Simulate",simulateButtonClick));

    // 5 Make window fit contents' preferred size
    guiDialog.pack();
    
    // 6 Move window to the middle of the screen
    guiDialog.setLocationRelativeTo(null);
    
    guiDialog.setVisible(true);
  }

  public String getName() {
    return MODULE_NAME;
  }
  
  /**
   * Simulate button click handler
   */
  ActionListener simulateButtonClick=new ActionListener() {
    public void actionPerformed(ActionEvent arg0) {
      DataLayer sourceDataLayer=sourceFilePanel.getDataLayer();
      String s="<h2>Petri net simulation results</h2>";
      if(sourceDataLayer==null) return;
      if(!sourceDataLayer.getPetriNetObjects().hasNext()) s+="No Petri net objects defined!";
      else {
        try {
          int firings=Integer.parseInt(jtfFirings.getText());
          int cycles =Integer.parseInt(jtfCycles .getText());
          s+=simulate(sourceDataLayer,cycles,firings);
          
        } catch (NumberFormatException e) {
          s+="Invalid parameter!";
        }
      }
      results.setText(s);
    }
  };
  
  String simulate(DataLayer data,int cycles,int firings) {
    data.storeState();

    int[] markup = data.getInitialMarkupMatrix();
    double averageTokens[] = new double[markup.length];
    int    totalTokens[]   = new int[markup.length];
    double avgResult[]     = new double[markup.length];
    double errorResult[]   = new double[markup.length];

    double overallAverages[][] = new double[cycles][markup.length];
    
    int i,j;

    // Initialise arrays
    for(i=0;i<markup.length;i++) {
      averageTokens[i] = 0;
      totalTokens[i] = 0;
      avgResult[i] = 0;
      errorResult[i] = 0;
    }

    //Initialise matrices
    for(i=0;i<cycles;i++)
      for(j=0;j<markup.length;j++)
        overallAverages[i][j] = 0;
    
    for(i=0; i < cycles; i++) {
      //Need to initialise the transition count again
      int transCount = 0;

      //Get initial marking
      markup = data.getInitialMarkupMatrix();

      data.restoreState();

      //Initialise matrices for each new cycle
      for(j=0; j < markup.length; j++) {
        averageTokens[j] = 0;
        totalTokens[j] = 0;
        avgResult[j] = 0;
      }

      //Add initial marking to the total
      addTotal(markup,totalTokens);

      //Fire as many transitions as required and evaluate averages
      for(j=0; j<firings; j++) {
        //Fire a random transition
        data.fireRandomTransition();
        //Get the new marking from the dataLayer object
        markup = data.getCurrentMarkupMatrix();
        //Add to the totalTokens array
        addTotal(markup,totalTokens);
        //Increment the transition count
        transCount++;
      }

      //Evaluate averages
      for(j=0; j < markup.length; j++) {
        //Divide by transCount + 1 as total number of markings
        //considered includes the original marking which is outside
        //the loop which counts the number of randomly fired transitions.
        averageTokens[j] = (totalTokens[j]/(transCount + 1.0));

        //add appropriate to appropriate row of overall averages for each cycle
        overallAverages[i][j] = averageTokens[j];
      }
    }

    //Add up averages for each cycle and divide by number of cycles
    //Perform evaluation on the overallAverages matrix.
    //for each column
    for(i=0; i < markup.length; i++) {
      //for each row
      for(j = 0; j < cycles; j++) 
        avgResult[i] = avgResult[i] + overallAverages[j][i] ;
      avgResult[i] = (avgResult[i]/cycles);
    }


    //Generate the 95% confidence interval for the table of results

    //Find standard deviation and mulitply by 1.95996 assuming approx
    //to gaussian distribution

    //For each column in result array
    for(i=0; i < markup.length; i++) {
      //Find variance
      for(j=0; j < cycles ;j++)
        //Sum of squares
        errorResult[i] = errorResult[i] + (overallAverages[j][i] - avgResult[i])*(overallAverages[j][i] - avgResult[i]);
      
      //Divide by number of cycles
      //Find standard deviation by taking square root
      //Multiply by 1.95996 to give 95% confidence interval
      errorResult[i]=1.95996*Math.sqrt(errorResult[i]/cycles);
    }
    
    ArrayList results=new ArrayList();

    DecimalFormat f=new DecimalFormat();
    f.setMaximumFractionDigits(5);

    if(averageTokens != null && errorResult != null && averageTokens.length > 0 && errorResult.length > 0) {  // Write table of results
      results.add("Place");
      results.add("Average number of tokens");
      results.add("95% confidence interval (+/-)");
      for (i=0;i<averageTokens.length;i++) {
        results.add(data.getPlace(i).getName());
        results.add(f.format(averageTokens[i]));
        results.add(f.format(errorResult[i]));
      }

    }
    
    data.restoreState();
    
    return ResultsHTMLPane.makeTable(results.toArray(),3,false,true,true,true);
  }
  
  private void addTotal(int array[], int dest[]) {
    if(array.length == dest.length)
      for(int i=0;i<dest.length;i++)
        dest[i]+=array[i];
  }
}