package ij.plugin;
import ij.*;
import ij.gui.*;
import ij.process.*;
import ij.plugin.PlugIn;
import ij.measure.*;
import ij.util.Tools;
import java.util.*;
import java.awt.*;
import java.awt.event.*;
import java.util.Vector;

/**
This plugin implements the Analyze/Distribution command.
It reads the data from the ResultsTable and plots a frequency histogram.
@author G. Landini at bham. ac. uk
*/
public class Distribution implements PlugIn, TextListener {
    static String parameter = "Area";
    static boolean autoBinning = true;
    static boolean showStats = false;
    static int nBins = 10;
    static String range = "0-0";
    Checkbox checkbox;
    TextField nBinsField, rangeField;
    String defaultNBins, defaultRange;

    public void run(String arg) {
        ResultsTable rt=ResultsTable.getResultsTable();
        int count = rt.getCounter();
        if (count==0) {
            IJ.error("Distribution", "The \"Results\" table is empty");
            return;
        }
        String head= rt.getColumnHeadings();
        //IJ.log(head);

        StringTokenizer t = new StringTokenizer(head, "\t");
        int tokens = t.countTokens()-1;
        String[] strings = new String[tokens];
        strings[0] = t.nextToken(); // first token is empty?
        for(int i=0; i<tokens; i++)
            strings[i] = t.nextToken();

        defaultNBins = ""+nBins;
        defaultRange = range;
        GenericDialog gd = new GenericDialog("Distribution");
        gd.addChoice("Parameter: ", strings, strings[getIndex(strings)]);
        gd.addMessage("Data points: "+ count);
        gd.addCheckbox("Automatic binning", autoBinning);
        gd.addNumericField ("or specify bins:", nBins, 0);
        gd.addStringField ("and range:", range);

        //gd.addCheckbox("Log Statistics", showStats);
        Vector v = gd.getNumericFields();
        nBinsField = (TextField)v.elementAt(0);
        nBinsField.addTextListener(this);
        v = gd.getStringFields();
        rangeField = (TextField)v.elementAt(0);
        rangeField.addTextListener(this);
        checkbox = (Checkbox)(gd.getCheckboxes().elementAt(0));
        gd.showDialog();
        if (gd.wasCanceled())
            return;

        parameter = gd.getNextChoice ();
        autoBinning = gd.getNextBoolean();
        double nMin=0.0, nMax=0.0;
        if (!autoBinning) {
            nBins = (int)gd.getNextNumber();
            range = gd.getNextString();
            String[] minAndMax = Tools.split(range, " -");
            nMin = Tools.parseDouble(minAndMax[0]);
            nMax = minAndMax.length==2?Tools.parseDouble(minAndMax[1]):Double.NaN;
            if (Double.isNaN(nMin) || Double.isNaN(nMax))
                {nMin=0.0; nMax=0.0; range="0-0";}
        }
        //boolean showStats = gd.getNextBoolean();

        //int nBins =5;
        float[] data = rt.getColumn(rt.getColumnIndex(parameter));

        float [] pars = new float [11];
        stats(count, data, pars);
        if (showStats) {
            IJ.log("Param: "+parameter);
            IJ.log("Data: "+ pars[1]);
            IJ.log("Sum: "+pars[2]);
            IJ.log("Min: "+pars[3]);
            IJ.log("Max: "+pars[4]);
            IJ.log("Mean: "+pars[5]);
            IJ.log("AvDev: "+pars[6]);
            IJ.log("StDev: "+pars[7]);
            IJ.log("Var: "+pars[8]);
            IJ.log("Skew: "+pars[9]);
            IJ.log("Kurt: "+pars[10]);
            IJ.log(" ");
        }
        if (autoBinning) {
            //sd = 7, min = 3, max = 4
            // use Scott's method (1979 Biometrika, 66:605-610) for optimal binning: 3.49*sd*N^-1/3
            float binWidth = (float)(3.49 * pars[7]*(float)Math.pow((float)count, -1.0/3.0));
            nBins= (int)Math.floor(((pars[4]-pars[3])/binWidth)+.5);
            if (nBins<2) nBins = 2;
        }

        ImageProcessor ip = new FloatProcessor(count, 1, data, null);
        ImagePlus imp = new ImagePlus("", ip);
//      ImageStatistics stats = new StackStatistics(imp, nBins, 0, 0);
        ImageStatistics stats = new StackStatistics(imp, nBins, nMin, nMax); // GL new line
        int maxCount = 0;
        for (int i=0; i<stats.histogram.length; i++) {
            if (stats.histogram[i]>maxCount)
                maxCount = stats.histogram[i];
        }
        stats.histYMax = maxCount;
        new HistogramWindow(parameter+" Distribution", imp, stats);
        //new HistogramWindow(parameter+" Distribution", new ImagePlus("",ip), nBins);
    }
    
    int getIndex(String[] strings) {
        for (int i=0; i<strings.length; i++) {
            if (strings[i].equals(parameter))
                return i;
        }
        return 0;
    }

    public void textValueChanged(TextEvent e) {
        if (!defaultNBins.equals(nBinsField.getText()))
            checkbox.setState(false);
        if (!defaultRange.equals(rangeField.getText()))
            checkbox.setState(false);
    }

    void stats(int nc, float [] data, float [] pars){
 // ("\tPoints\tEdges_n\tGraph_Length\tMin\tMax\tMean\tAvDev\tSDev\tVar\tSkew\tKurt");
        int i;
        float s = 0, min = Float.MAX_VALUE, max = -Float.MAX_VALUE, totl=0, ave=0, adev=0, sdev=0, var=0, skew=0, kurt=0, p;

        for(i=0;i<nc;i++){
            totl+= data[i];
            //tot& = tot& + 1
                if(data[i]<min) min = data[i];
            if(data[i]>max) max = data[i];
        }

        ave = totl/nc;

        for(i=0;i<nc;i++){
            s = data[i] - ave;
            adev+=Math.abs(s);
            p = s * s;
            var+= p;
            p*=s;
            skew+= p;
            p*= s;
            kurt+= p;
        }

        adev/= nc;
        var/=nc-1;
        sdev = (float) Math.sqrt(var);

        if(var> 0){
            skew = (float)skew / (nc * (float) Math.pow(sdev,3));
            kurt = (float)kurt / (nc * (float) Math.pow(var, 2)) - 3;
        }
        pars[1]=(float) nc;
        pars[2]=totl;
        pars[3]=min;
        pars[4]=max;
        pars[5]=ave;
        pars[6]=adev;
        pars[7]=sdev;
        pars[8]=var;
        pars[9]=skew;
        pars[10]=kurt;

    }

}