//
// This applet is based on code which was kindly provided by Matt Roughan 
// It uses Matt's PlotCanvas class which extends Canvas
//    Last updated 06/08/98
//

import java.util.*;
import java.applet.*;
import java.awt.*;
import MyUtil.*;

public class PlotDistribution extends Applet {

  Panel cardStack = null;
  CardLayout c1 = null;

  DistributionCard currentCard;
  int N = 15;
  DistributionCard[] theCards = new DistributionCard[N];
  Button[] SelectionButtons = new Button[N];
  String[] DistributionNames = {"Normal", "Beta", "Gamma", 
				"Erlang", "Exponential", 
				"Chi-squared", "Student-t",
				"Laplace", "Pareto", "Cauchy",
				"Anon", "Weibull","Lognormal",
                                "Inverse-Gaussian","Uniform"};

  String startCard;

  MyDialog helpDialog;

  static Frame theFrame;

  //this array of strings is what is displayed by the help button
  String[] helpText = {
    "This applet is designed to plot the distribution function F(x)",
    "and the associated probability density function f(x) for several",
    "of the standard continuous distributions.",
    "",
    "The various parameters are controlled using  the scrollbars",
    "and the scale of the plot can be enlarged using + (plus) or",
    "reduced using - (minus). Pressing h brings up this message.",
    ""
  };
  
  public void init(){
//    BorderLayout new_BorderLayout = new BorderLayout(0,8);
    BorderLayout new_BorderLayout = new BorderLayout(0,0);
    setLayout(new_BorderLayout);

    cardStack = new Panel();
    c1 = new CardLayout();
    cardStack.setBackground(Color.white);
    cardStack.setLayout(c1);
    add("Center", cardStack);

    //create the cards for plotting each distribution
    theCards[0] = new GaussianCard();
    cardStack.add(DistributionNames[0], theCards[0]);

    theCards[1] = new BetaCard();
    cardStack.add(DistributionNames[1], theCards[1]);

    theCards[2] = new GammaCard();
    cardStack.add(DistributionNames[2], theCards[2]);

    theCards[3] = new ErlangCard();
    cardStack.add(DistributionNames[3], theCards[3]);

    theCards[4] = new ExponentialCard();
    cardStack.add(DistributionNames[4], theCards[4]);

    theCards[5] = new ChiSquaredCard();
    cardStack.add(DistributionNames[5], theCards[5]);

    theCards[6] = new StudentTCard();
    cardStack.add(DistributionNames[6], theCards[6]);

    theCards[7] = new LaplaceCard();
    cardStack.add(DistributionNames[7], theCards[7]);

    theCards[8] = new ParetoCard();
    cardStack.add(DistributionNames[8], theCards[8]);

    theCards[9] = new CauchyCard();
    cardStack.add(DistributionNames[9], theCards[9]);

    theCards[10] = new AnonCard();
    cardStack.add(DistributionNames[10], theCards[10]);

    theCards[11] = new WeibullCard();
    cardStack.add(DistributionNames[11], theCards[11]);

    theCards[12] = new LognormalCard();
    cardStack.add(DistributionNames[12], theCards[12]);

    theCards[13] = new InverseGaussianCard();
    cardStack.add(DistributionNames[13], theCards[13]);

    theCards[14] = new UniformCard();
    cardStack.add(DistributionNames[14], theCards[14]);

    //put selection buttons on the bottom of the page
    Panel SelectionBar = new Panel();
    SelectionBar.setBackground(Color.white);
    
    GridLayout new_GridLayout = new GridLayout(5,4, 3,3);
    SelectionBar.setLayout(new_GridLayout);

    SelectionBar.add(new Label());
    SelectionBar.add(new Label());
    SelectionBar.add(new Label());
    SelectionBar.add(new Label());

    for (int i=0; i<SelectionButtons.length; i++) {
      SelectionButtons[i] = new Button(DistributionNames[i]);
      SelectionButtons[i].setForeground(Color.black);
      SelectionButtons[i].setBackground(Color.lightGray);
      SelectionBar.add(SelectionButtons[i]);
    }

    //add help (and quit) button(s)

//    SelectionBar.add(new Label());

    Button help_button = new Button("Help");
    help_button.setForeground(Color.black);
    help_button.setBackground(Color.cyan);
    SelectionBar.add(help_button);

//    if (theFrame != null) SelectionBar.add(new Button("Quit"));
//    else SelectionBar.add(new Label());

    add("South", SelectionBar);

    //if running as an Applet then get the parameter
    try {
      startCard = getParameter("startCard");
    } catch (Exception e) {
    }
  }

  //things to do when the application starts up
  public void start() {
    //the default startup distribution is distribution 0
    currentCard = theCards[0];
    c1.show(cardStack, DistributionNames[0]);
    theCards[0].init();
    theCards[0].plot();      

    //if the parameter was set, then use this to select the startup
    //distribution
    if (startCard != null) {
      for (int i=0; i<DistributionNames.length; i++) {
	if (startCard.equals(DistributionNames[i])) {
	  currentCard = theCards[i];
	  c1.show(cardStack, DistributionNames[i]);	  
	  theCards[i].init();
	  theCards[i].plot();
	}
      }
    }
  }

  //what to do when a button is pushed
  public boolean action(Event evt, Object arg) {
    for (int i=0; i<SelectionButtons.length; i++) {
      //if a selection button is pushed, change the current 
      //displayed distribution
      if (evt.target == SelectionButtons[i]) {
	currentCard = theCards[i];
	c1.show(cardStack, DistributionNames[i]);
	currentCard.init();
	currentCard.plot();
	return true;
      }
    }
    if ("Help".equals(arg)) {
      helpDialog = new MyDialog("About Plot Distribution", helpText);
    } 
    if ("Quit".equals(arg)) {
      theFrame.dispose();
      System.exit(0);
    } 
    return false;
  }

  //check to see whether a scroll bar has been used
  public boolean handleEvent(Event evt) {
    for (int i=0; i<currentCard.NumberOfParameters; i++) {
      if (evt.target == currentCard.ParameterScrollbars[i]) {
	currentCard.setParameter(i, 
            currentCard.ParameterScrollbars[i].getValue());
	currentCard.plot();
	return true;
      }
    }
    return super.handleEvent(evt);
  }

  //react to keystrokes
  public boolean keyDown(Event e, int key) {
    if (key == 'q' && theFrame != null) {
	theFrame.dispose();
	System.exit(0);
    }
    if (key == 'h') {
      helpDialog = new MyDialog("About Plot Distribution", helpText);
      return true;
    }
    if (key == '-') {
      currentCard.scale(false);
      return true;
    }
    if (key == '+') {
      currentCard.scale(true);
      return true;
    }
    return false;
  }

  //if you want to run it as a stand-alone application
  public static void main(String args[]) {
    theFrame = new Frame("Plot Distribution");
    PlotDistribution PD = new PlotDistribution();
    PD.init();
    PD.reshape(5, 10, 500,600);
    theFrame.add("Centre", PD);
    theFrame.pack();
    theFrame.resize(510,620);
    PD.start();
    theFrame.show();
  }
} //end PlotDistribution

//
// This abstract class is intended to provide the graphics object
// which plots a probability distribution. To extend it to a real class
// it must have defined functions: density, normalisation and
// startingValue
//
abstract class DistributionCard extends Panel {

  //the name of the distribution to be plotted
  String DistributionName;

  //of the distribution to be plotted
  String DistributionDesc;

  //a window upon which to plot the distribution
  PlotCanvas PlotWindow;

  //variables to do with the parameters of the distribution
  int NumberOfParameters;
  String[] ParameterStrings;
  double[] ParameterDefaults;
  double[] ParameterValues;
  double[][] ParameterRanges;
  Label[] ParameterLabels;
  Scrollbar[] ParameterScrollbars;

  //the range and domain that will be displayed, along with the number
  //of points to be plotted, and the distance between these points.
  double[] range = new double[2];
  double[] domain = new double[2];
  double BinSize;
  int NumberOfBins;

  double[] x;   //the range of x-values over which to plot the distribution
  double[] p;   //the probability density over the range of x-values
  double[] F;   //the probability distribution of the range of x-values
  double normalisationFactor; //a factor to be used in normalising the distribution
  double low,high,step,default_par;
  int nn,default_par_index;

  //a set of allowable scaling factors when displaying the plot
  double[] scales = {0.1, 0.2, 0.5, 0.8, 1.0, 1.5, 2.0};
  int theScale = 4;  //the default scaling factor should be 1.0

  //define a constructor
  DistributionCard(String DistributionName, 
                   String DistributionDesc, 
		   String[] ParameterStrings,
		   double[] ParameterDefaults,
		   double[][] ParameterRanges,
		   double[] range,
		   double[] domain,
		   double BinSize) {
    
    this.DistributionName = DistributionName;
    this.DistributionDesc = DistributionDesc;
    
    this.NumberOfParameters = ParameterStrings.length;
    this.ParameterStrings = ParameterStrings;
    this.ParameterDefaults = ParameterDefaults;
    this.ParameterValues = ParameterDefaults;
    this.ParameterRanges = ParameterRanges;

    //set the values of x,p, and F
    this.domain = domain;
    this.range = range;
    this.BinSize = BinSize;
    NumberOfBins = (int) ((range[1] - range[0])/BinSize) + 1;
    x = new double[NumberOfBins];
    p = new double[NumberOfBins];
    F = new double[NumberOfBins];

    recalculate();

    //Layout the panels subcomponents
//    setLayout(new BorderLayout(0, 4));
    setLayout(new BorderLayout(0, 0));

    //create the plot window
    PlotWindow =  new PlotCanvas(this.range[0], this.domain[0], 
				 this.range[1], this.domain[1], 
				 "x", " f(x)", " F(x)",
				 Color.black, Color.blue, Color.red, 
				 this.DistributionName+" Distribution",
				 this.DistributionDesc);

    add("Center", PlotWindow); 

    //create the scroll bars, with labels
    Panel scrollbars = new Panel();
    ParameterLabels = new Label[NumberOfParameters];
    ParameterScrollbars = new Scrollbar[NumberOfParameters];

    scrollbars.setLayout(new GridLayout(NumberOfParameters, 3, 5, 5)); 
    scrollbars.setForeground(Color.black);
    scrollbars.setBackground(Color.white);
    for (int i=0; i<NumberOfParameters; i++) {
      ParameterLabels[i] = new 
         Label(ParameterStrings[i]+" = "+ParameterDefaults[i]);

      low = ParameterRanges[i][0];
      high = ParameterRanges[i][1];
      step = ParameterRanges[i][2];
      nn = 1 + Math.round( Math.round( (high - low)/step) );
      default_par = ParameterDefaults[i];
      default_par_index = 1 + Math.round(Math.round((default_par - low)/step));
      ParameterScrollbars[i] = new Scrollbar(Scrollbar.HORIZONTAL,
                                             default_par_index, 1, 1, nn);

      ParameterLabels[i].setBackground(Color.white);
      ParameterScrollbars[i].setBackground(Color.lightGray);
      scrollbars.add(new Label());
      scrollbars.add(ParameterScrollbars[i]);
      scrollbars.add(ParameterLabels[i]);
    }
    add("South", scrollbars);
  }
  
  public void init() {
    PlotWindow.init();
  }

  //define a method for changing parameter values
  public void setParameter(int i, double value) {

    double low,high,step,par_value;

    low = ParameterRanges[i][0];
    high = ParameterRanges[i][1];
    step = ParameterRanges[i][2];
    par_value = low + step* (value - 1);
    par_value = (Math.round(Math.round(100.0*par_value)))/100.0;

    if ((par_value >= low) & (par_value <= high)) {
     ParameterValues[i] = par_value;
//     ParameterLabels[i].setText(ParameterStrings[i]+" = "+ParameterDefaults[i]);
     ParameterLabels[i].setText(ParameterStrings[i]+" = "+ParameterValues[i]);
      recalculate();
    } else {
      System.out.println("Error: Parameter outside acceptible range!");
    }
  }

  //define a method for resizing the graph
  public void scale(boolean enlarge) {
    double scaleFactor;
    if (enlarge) {
      theScale--;
      if (theScale < 0) {
	theScale = 0;
      	scaleFactor = 1.0;
      } else scaleFactor = scales[theScale]/scales[theScale+1];
    }
    else {
      theScale++;
      if (theScale >= scales.length) {
	theScale = scales.length-1;
	scaleFactor = 1.0;
      } else scaleFactor = scales[theScale]/scales[theScale-1];
    }

    //rescale the axes
    for (int i=0; i<range.length; i++) {
      range[i] = range[i]*scaleFactor;
      domain[i] = domain[i]*scaleFactor;
    }
    
    //recalculate the distribution
    NumberOfBins = (int) ((range[1] - range[0])/BinSize) + 1;
    x = new double[NumberOfBins];
    p = new double[NumberOfBins];
    F = new double[NumberOfBins];    
    recalculate();

    //replot the distribution
    PlotWindow.rescale(range[0], domain[0], range[1], domain[1]); 
    plot();
  }

  //define a method for plotting the graph
  public void plot() {
    PlotWindow.plot(x,p, Color.blue);       //plot the density
    PlotWindow.plot(x,F, Color.red, true);  //overplot the distribution in red
    PlotWindow.repaint();
  }

  //definition of a Gamma function which works for positive integers,
  //and half integers, This will be useful in several of the instances
  //of a Distribution (ideally this should be set up to work for all
  //real numbers not just integers and half-integers).
  double Gamma(double x) {
    if (x == Math.round(x)) {
      if (x>1) return (x-1)*Gamma(x-1);
      if (x==1 | x==0) return 1.0;
    } else if (2*x == Math.round(2*x)) {
      if (x>0.5) return (x-1)*Gamma(x-1);
      if (x==0.5) return Math.sqrt(Math.PI);
    }
    return 0.0;
  }

  //define the function which recalculates the values of the density
  //and distribution - it calls the three abstract functions
  void recalculate() {
    normalisationFactor = normalisation();
    F[0] = startingValue();
    for (int i=0; i<x.length; i++) {
      x[i] = range[0] + BinSize*i;
      p[i] = density(x[i]);
      if (i>0) F[i] = F[i-1] + BinSize*p[i-1];
    }
  }

  // define a function which finds the first value of the distribution
  // function over the range specified

  abstract double startingValue();

  // define an abstract method for calculating the Normalisation factor
  // of the distribution. This is really just a computational
  // convenience, to prevent the same calculations being made on every
  // call to density(x)

  abstract double normalisation();

  // define an abstract method for calculating the probability density,
  // any calculation which are repeated in this function should go in
  // the normalisationFactor

  abstract double density(double x);

}//end DistributionCard

//
// define some real instances of the abstract class above
//

// define a Gaussian distribution card
class GaussianCard extends  DistributionCard {

  static String[] parameterNames = {"mu","sigma^2"};
  static double[] parameterDefaults = {0.0, 1.0};
  static double[][] parameterRanges = {{-10.0,10.0,0.2},{0.2, 10.0,0.2}};
  static double[] r = {-10.0, 10.0};
  static double[] d = {0.0, 1.0};

  GaussianCard() {
    super("Normal (or Gaussian)", 
   "f(x) = ( 2 Pi sigma^2 )^( -1/2 ) exp( - ( 1/2 ) ( ( x - mu ) / sigma )^2 )",
	  parameterNames, 
	  parameterDefaults, 
	  parameterRanges, 
	  r, d,
	  0.1);
  }

  double startingValue() {
    double value = 0.0;
    double SVBinSize = BinSize;
    double SVNoofBins = x.length;
    for (int j=1; j<=SVNoofBins; j++) {
      value += SVBinSize*density(range[0] - SVBinSize*j);
    }
    return value;
  }

  double normalisation() {
    double variance = ParameterValues[1];
    return Math.sqrt(2*Math.PI*variance);
  }

  double density(double x) {
    double mean = ParameterValues[0];
    double variance = ParameterValues[1];
    return Math.exp( -(x-mean)*(x-mean)/(2*variance) )/normalisationFactor;
  }
}


//define a beta distribution card
class BetaCard extends  DistributionCard {

  static String[] parameterNames = {"mu","nu"};
  static double[] parameterDefaults = {1.5, 1.5};
  static double[][] parameterRanges = {{0.5,20.0,0.5},{0.5,20.0,0.5}};
  static double[] r = {0.0, 1.0};
  static double[] d = {0.0, 10.0};
  
  BetaCard() {
    super("Beta", 
          "f(x)= Gam(mu+nu)/(Gam(mu) Gam(nu)) x^(mu-1) (1-x)^(nu-1)",
	  parameterNames, 
	  parameterDefaults, 
	  parameterRanges, 
	  r, d,
	  0.01);
  } 

  double startingValue() {
    return 0.0;
  }

  double normalisation() {
    double mu = ParameterValues[0];
    double nu = ParameterValues[1];
    return Gamma(mu)*Gamma(nu)/Gamma(mu+nu);
  }

  double density(double x) {
    double mu = ParameterValues[0];
    double nu = ParameterValues[1];
    if (x>0) {
      return Math.pow(x, mu-1)*Math.pow((1-x), nu-1)/ normalisationFactor;
    } else if (x==0) { 
      double ee = BinSize/2.0;
      return Math.pow(ee, mu-1)*Math.pow((1-ee), nu-1)/ normalisationFactor;
    }
    else return 0.0;
  }
}

//define gamma distribution card
class GammaCard extends  DistributionCard {

  static String[] parameterNames = {"alpha","lambda"};
  static double[] parameterDefaults = {1.0, 1.0};
  static double[][] parameterRanges = {{0.5,5.0,0.5},{0.1,10.0,0.1}};
  static double[] r = {0.0, 10.0};
  static double[] d = {0.0, 2.0};
  
  GammaCard() {
    super("Gamma", 
          "f(x) = lambda exp( - lambda x) (lambda x)^(alpha - 1) / Gam(alpha)",
	  parameterNames, 
	  parameterDefaults, 
	  parameterRanges, 
	  r, d,
	  0.01);
  } 

  double startingValue() {
    return 0.0;
  }

  double normalisation() {
    double alpha = ParameterValues[0];
    double beta = ParameterValues[1];
    return Gamma(alpha)/Math.pow(beta, alpha);
  }

  double density(double x) {
    double alpha = ParameterValues[0];
    double beta = ParameterValues[1];
    if (x>0) {
      return Math.pow(x, alpha-1)*Math.exp(-beta*x)/normalisationFactor;
    } else if (x==0) { 
      double ee = BinSize/2.0;
      return Math.pow(ee, alpha-1)*Math.exp(-beta*ee)/normalisationFactor;
    }
    else return 0.0;
  }

  //override the method for calculating the distribution function to
  //use a more accurate integral
  void recalculate() {
    normalisationFactor = normalisation();
    F[0] = startingValue();
    for (int i=0; i<x.length; i++) {
      x[i] = range[0] + BinSize*i;
      p[i] = density(x[i]);
      if (i>0) F[i] = F[i-1] + BinSize*(p[i-1] + p[i])/2.0;
    }
  }
}

//define Erlang distribution card
class ErlangCard extends  DistributionCard {

  static String[] parameterNames = {"n","lambda"};
  static double[] parameterDefaults = {1.0, 1.0};
  static double[][] parameterRanges = {{1.0,80.0,1.0},{0.5,10.0,0.5}};
  static double[] r = {0.0, 10.0};
  static double[] d = {0.0, 1.0};
  
  ErlangCard() {
    super("Erlang", 
          "f(x) = lambda exp( - lambda x) (lambda x)^(alpha - 1) / Gam(alpha)",
	  parameterNames, 
	  parameterDefaults, 
	  parameterRanges, 
	  r, d,
	  0.01);
  } 

  double startingValue() {
    return 0.0;
  }

  double normalisation() {
    double alpha = ParameterValues[0];
    double beta = ParameterValues[1];
    return Gamma(alpha)/Math.pow(beta, alpha);
  }

  double density(double x) {
    double alpha = ParameterValues[0];
    double beta = ParameterValues[1];
    return Math.pow(x, alpha-1)*Math.exp(-beta*x)/normalisationFactor;
  }

  //override the method for calculating the distribution function to
  //use a more accurate integral
  void recalculate() {
    normalisationFactor = normalisation();
    F[0] = startingValue();
    for (int i=0; i<x.length; i++) {
      x[i] = range[0] + BinSize*i;
      p[i] = density(x[i]);
      if (i>0) F[i] = F[i-1] + BinSize*(p[i-1] + p[i])/2.0;
    }
  }
}

//define exponential distribution card
class ExponentialCard extends  DistributionCard {

  static String[] parameterNames = {"lambda"};
  static double[] parameterDefaults = {2.0};
  static double[][] parameterRanges = {{0.2, 10.0, 0.2}};
  static double[] r = {0.0, 2.0};
  static double[] d = {0.0, 5.0};
  
  ExponentialCard() {
    super("(Negative) Exponential", 
          "f(x) = lambda exp( -lambda x)",
	  parameterNames, 
	  parameterDefaults, 
	  parameterRanges, 
	  r, d,
	  0.01);
  } 

  double startingValue() {
    return 0.0;
  }

  double normalisation() {
    return 1.0;
  }

  double density(double x) {
    double lambda = ParameterValues[0];
    return lambda*Math.exp(-lambda*x);
  }

  double distribution(double x) {
    double lambda = ParameterValues[0];
    return (1 - Math.exp(-lambda*x));
  }

  //override this function to use an analytic expresion for F
  void recalculate() {
    normalisationFactor = normalisation();
    F[0] = startingValue();
    for (int i=0; i<x.length; i++) {
      x[i] = range[0] + BinSize*i;
      p[i] = density(x[i]);
      F[i] = distribution(x[i]);
    }
  }
}

//define chi-squared distribution card
class ChiSquaredCard extends  DistributionCard {

  static String[] parameterNames = {"n (d.f.)"};
  static double[] parameterDefaults = {1.0};
  static double[][] parameterRanges = {{1.0, 10.0,1.0}};
  static double[] r = {0.0, 10.0};
  static double[] d = {0.0, 1.0};
  
  ChiSquaredCard() {
    super("Chi-squared", 
          "f(x) = ( 1 / 2 ) exp( - x / 2 ) ( x / 2 )^(n / 2 - 1) / Gamma( n / 2)",
	  parameterNames, 
	  parameterDefaults, 
	  parameterRanges, 
	  r, d,
	  0.01);
    setParameter(0,1.0);
  } 

  double startingValue() {
    return 0.0;
  }

  double normalisation() {
    return Math.pow(2.0, ParameterValues[0]/2.0)*Gamma(ParameterValues[0]/2.0);
  }

  double density(double x) {
    double nu = ParameterValues[0];
    if (x>0) 
      return Math.pow(x,(nu-2)/2.0)*Math.exp(-x/2.0)/normalisationFactor;
    else if (x==0) { 
      double ee = BinSize/2.0;
      return Math.pow(ee,(nu-2)/2.0)*Math.exp(-ee/2.0)/normalisationFactor;
    }
    else return 0.0;
  }
}

//define Student-t distribution card
class StudentTCard extends  DistributionCard {

  static String[] parameterNames = {"n (d.f.)"};
  static double[] parameterDefaults = {1.0};
  static double[][] parameterRanges = {{1.0,60.0,1.0}};
  static double[] r = {-10.0, 10.0};
  static double[] d = {0.0, 1.0};

  StudentTCard() {
    super("Student-t", 
          "f(x) = (1+x^2)^(-(n+1)/2) (n Pi)^(-1/2) Gam((n+1)/2) / Gamma(n/2) ",
	  parameterNames, 
	  parameterDefaults, 
	  parameterRanges, 
	  r, d,
	  0.1);
  }

  double startingValue() {
    double value = 0.0;
    double SVBinSize = BinSize/2.0;
    double SVNoofBins = 5*x.length;
    for (int j=1; j<=SVNoofBins; j++) {
      value += SVBinSize*density(range[0] - SVBinSize*j);
    }
    return value;
  }

  double normalisation() {
    double nu = ParameterValues[0];
    return Gamma(nu/2.0)*Math.sqrt(nu*Math.PI)/Gamma((nu+1)/2.0);
  }

  double density(double x) {
    double nu = ParameterValues[0];
    return Math.pow(1 + x*x/nu, -(nu+1)/2.0 )/normalisationFactor;
  }
}

//define Laplace distribution card
class LaplaceCard extends  DistributionCard {

  static String[] parameterNames = {"mu","a"};
  static double[] parameterDefaults = {0.0, 1.0};
  static double[][] parameterRanges = {{-10.0,10.0,1.0},{0.5,10.0,0.5}};
  static double[] r = {-10.0, 10.0};
  static double[] d = {0.0, 1.0};

  LaplaceCard() {
    super("Laplace (or Double Exponential)", 
          "f(x) = 2 a exp( - | x - mu | / a )",
	  parameterNames, 
	  parameterDefaults, 
	  parameterRanges, 
	  r, d,
	  0.1);
  }

  double startingValue() {
    double value = 0.0;
    double SVBinSize = BinSize;
    double SVNoofBins = 2*x.length;
    for (int j=1; j<=SVNoofBins; j++) {
      value += SVBinSize*density(range[0] - SVBinSize*j);
    }
    return value;
  }

  double normalisation() {
    return 2*ParameterValues[1];
  }

  double density(double x) {
    double mean = ParameterValues[0];
    double scale = ParameterValues[1];
    return Math.exp(-Math.abs(x - mean)/scale)/normalisationFactor;
  }
}

class ParetoCard extends  DistributionCard {

  static String[] parameterNames = {"a","c"};
  static double[] parameterDefaults = {1.0, 2.0};
  static double[][] parameterRanges = {{0.1,10.0,0.1},{0.1, 10.0,0.1}};
  static double[] r = {0.0, 10.0};
  static double[] d = {0.0, 5.0};
  
  ParetoCard() {
    super("Pareto", 
          "f(x) = c a^c / x^( c + 1 ),    x > a  ( and 0 otherwise )",
	  parameterNames, 
	  parameterDefaults, 
	  parameterRanges, 
	  r, d,
	  0.01);
  } 

  double startingValue() {
    double a = ParameterValues[0];
    double c = ParameterValues[1];
    if (r[0]>=a) 
      return 1 - Math.pow(a/r[0],c);
    else return 0;
  }

  double normalisation() {
    double a = ParameterValues[0];
    double c = ParameterValues[1];
    return 1.0/(c*Math.pow(a,c));
  }

  double density(double x) {
    double a = ParameterValues[0];
    double c = ParameterValues[1];
    if (x>=a) 
      return 1/ (Math.pow(x,c+1)*normalisationFactor);
    else return 0;
  }

  double distribution(double x) {
    double a = ParameterValues[0];
    double c = ParameterValues[1];
    if (x>=a) 
      return 1 - Math.pow(a/x,c);
    else return 0;
  }

  //override this function to use an analytic expresion for F
  void recalculate() {
    normalisationFactor = normalisation();
    F[0] = startingValue();
    for (int i=0; i<x.length; i++) {
      x[i] = range[0] + BinSize*i;
      p[i] = density(x[i]);
      F[i] = distribution(x[i]);
    }
  }
}

class CauchyCard extends  DistributionCard {

  static String[] parameterNames = {"m","b"};
  static double[] parameterDefaults = {0.0, 1.0};
  static double[][] parameterRanges = {{-5.0,5.0,0.2},{0.5,10.0,0.5}};
  static double[] r = {-10.0, 10.0};
  static double[] d = {0.0, 1.0};
  
  CauchyCard() {
    super("Cauchy", 
          "f(x) = b / ( Pi ( b^2 + ( x - m )^2 ) )",
	  parameterNames, 
	  parameterDefaults, 
	  parameterRanges, 
	  r, d,
	  0.01);
  } 

  double startingValue() {
    double value = 0.0;
    double SVBinSize = BinSize/2.0;
    double SVNoofBins = 5*x.length;
    for (int j=1; j<=SVNoofBins; j++) {
      value += SVBinSize*density(range[0] - SVBinSize*j);
    }
    return value;
  }

  double normalisation() {
    return Math.PI * ParameterValues[1];
  }

  double density(double x) {
    double mu = ParameterValues[0];
    double b = ParameterValues[1];
    return 1.0/((1 + Math.pow((x-mu)/b,2))*normalisationFactor);
  }
}

class AnonCard extends  DistributionCard {

  static String[] parameterNames = {};
  static double[] parameterDefaults = {};
  static double[][] parameterRanges = {};
  static double[] r = {-10.0, 10.0};
  static double[] d = {0.0, 1.0};
  
  AnonCard() {
    super("An Anonymous", 
          "f(x) = ( 1 - cos(x) ) / ( Pi x^2 )   ( 1 / ( 2 Pi )  if  x = 0 )",
	  parameterNames, 
	  parameterDefaults, 
	  parameterRanges, 
	  r, d,
	  0.01);
  } 

  double startingValue() {
    double value = 0.0;
    double SVBinSize = BinSize/2.0;
    double SVNoofBins = 5*x.length;
    for (int j=1; j<=SVNoofBins; j++) {
      value += SVBinSize*density(range[0] - SVBinSize*j);
    }
    return value;
  }

  double normalisation() {
    return Math.PI;
  }

  double density(double x) {
    if (x != 0)
      return (1 - Math.cos(x))/(Math.pow(x,2)*normalisationFactor);
    else return 1/(2*Math.PI);
  }
}

//define weibull distribution card
class WeibullCard extends  DistributionCard {

  static String[] parameterNames = {"lambda", "beta"};
  static double[] parameterDefaults = {2.0, 3.0};
  static double[][] parameterRanges = {{0.1, 10.0,0.1},{0.1,10.0,0.1}};
  static double[] r = {0.0, 2.0};
  static double[] d = {0.0, 5.0};
  
  WeibullCard() {
    super("Weibull", 
          "f(x) = lambda beta x^( beta - 1 ) exp( -lambda x^beta )",
	  parameterNames, 
	  parameterDefaults, 
	  parameterRanges, 
	  r, d,
	  0.005);
  } 

  double startingValue() {
    return 0.0;
  }

  double normalisation() {
    return 1.0;
  }

  double density(double x) {
    double lambda = ParameterValues[0];
    double beta = ParameterValues[1];
    return lambda*beta*Math.pow(x, beta-1)*Math.exp(-lambda*Math.pow(x,beta));
  }

  double distribution(double x) {
    double lambda = ParameterValues[0];
    double beta = ParameterValues[1];
    return (1 - Math.exp(-lambda*Math.pow(x,beta)));
  }

  //override this function to use an analytic expresion for F
  void recalculate() {
    normalisationFactor = normalisation();
    F[0] = startingValue();
    for (int i=0; i<x.length; i++) {
      x[i] = range[0] + BinSize*i;
      p[i] = density(x[i]);
      F[i] = distribution(x[i]);
    }
  }
}

// define a Lognormal distribution card
class LognormalCard extends  DistributionCard {

  static String[] parameterNames = {"mu","sigma^2"};
  static double[] parameterDefaults = {0.0, 1.0};
  static double[][] parameterRanges = {{-3.0,3.0,0.1},{0.1,10.0,0.1}};
  static double[] r = {0.005, 5.0};
  static double[] d = {0.0, 2.0};
  static double e1 = 1.0E-10;

  LognormalCard() {
    super("Lognormal", 
          "f(x) = (2 Pi (sigma x)^2 )^( -1/2 ) exp( - (1/2) ( ( log(x) - mu ) / sigma )^2 )",
	  parameterNames, 
	  parameterDefaults, 
	  parameterRanges, 
	  r, d,
	  0.005);
  }

  double startingValue() {
    return 0.005;
  }

  double normalisation() {
    return 1.0;
  }

  double density(double x) {
    double mu = ParameterValues[0];
    double vv = ParameterValues[1];
    if (x>e1) {
      double lx2 = Math.pow(Math.log(x)-mu,2);
      return Math.exp( -lx2/(2.0*vv) )/(x*Math.sqrt(2.0*Math.PI*vv));
    } else {
      double lx2 = Math.pow(Math.log(e1)-mu,2);
      return Math.exp( -lx2/(2.0*vv) )/(e1*Math.sqrt(2.0*Math.PI*vv));
    }
  }
}

// define an Inverse Gaussian distribution card
class InverseGaussianCard extends  DistributionCard {

  static String[] parameterNames = {"mu","a"};
  static double[] parameterDefaults = {1.0, 1.0};
  static double[][] parameterRanges = {{0.1,9.0,0.1},{0.1,20.0,0.1}};
  static double[] r = {0.005, 5.0};
  static double[] d = {0.0, 2.0};
  static double e1 = 1.0E-10;

  InverseGaussianCard() {
    super("Inverse Gaussian", 
          "f(x) = ( a / ( 2 Pi x^3 ) )^( 1/2 ) exp( - a ( x - mu )^2 / ( 2 mu^2 x ) )",
	  parameterNames, 
	  parameterDefaults, 
	  parameterRanges, 
	  r, d,
	  0.005);
  }

  double startingValue() {
    return 0.005;
  }

  double normalisation() {
    return 1.0;
  }

  double density(double x) {
    double mu = ParameterValues[0];
    double a = ParameterValues[1];
    if (x>e1) {
      return Math.sqrt(a/(2.0*Math.PI*Math.pow(x,3)))*Math.exp(-a*Math.pow(x-mu,2)/(2.0*Math.pow(mu,2)*x));
    } else {
      return 0.0;
    }
  }
}

// define a Uniform distribution card
class UniformCard extends  DistributionCard {

  static String[] parameterNames = {"a","d"};
  static double[] parameterDefaults = {0.0, 1.0};
  static double[][] parameterRanges = {{-4.8,4.8,0.2},{0.2,9.8,0.2}};
  static double[] r = {-5.0, 5.0};
  static double[] d = {0.0, 2.0};

  UniformCard() {
    super("Uniform", 
          "f(x) = 1 / d   for a < x < a + d   ( and 0 otherwise )",
	  parameterNames, 
	  parameterDefaults, 
	  parameterRanges, 
	  r, d,
	  0.01);
  }

  double startingValue() {
    return r[0];
  }

  double normalisation() {
    double d = ParameterValues[1];
    return Math.sqrt(d);
  }

  double density(double x) {
    double a = ParameterValues[0];
    double d = ParameterValues[1];
    if ((x<a) | (x>(a+d))) 
       return 0;
    else 
       return 1/normalisationFactor;
  }

  double distribution(double x) {
    double a = ParameterValues[0];
    double d = ParameterValues[1];
    if (x<a) 
       return 0;
    else if (x>(a+d)) 
       return 1;
    else  
       return ((x-a)/d);
  }

  //override this function to use an analytic expresion for F
  void recalculate() {
    normalisationFactor = normalisation();
    F[0] = startingValue();
    for (int i=0; i<x.length; i++) {
      x[i] = range[0] + BinSize*i;
      p[i] = density(x[i]);
      F[i] = distribution(x[i]);
    }
  }
}


