/* 
 * Veletlen.java 
 * különböző eloszlású véletlenszámok generálója, determinisztikussá tehető
 * by pts@fazekas.hu at Sat Apr  7 12:10:32 CEST 2001, mayor update at Sun Apr 22 13:00:49 CEST 2001
 * további doksi: a README-ben és e file legkülső class-ának fejkommentjében
 *
 * Kincskereső Kisgömböc (C) Early May 2001 by eNTitánok (Rév Szilvia,
 * Szabó Péter <pts@inf.bme.hu>, Szurdi Miklós, Weizengruber Attila).
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or   
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of 
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the  
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

package eNTitanok.util;
import eNTitanok.util.Sor;
import java.util.Random;

/**
 * Egy olyan (pszeudo)véletlenszám-generátor, amely determinisztikussá
 * tehető, azaz megmondható neki, hogy a következő néhány ,,véletlenszám''
 * mi legyen. Ezeket a fix számokat az ún. determinizáló sorból veszi. Egyetlen
 * generátorhoz egyetlen determinizáló sor tartozik, ami kezdetben üres.
 *
 * <P>A generált véletlenszámok 0 és N-1 közé eső int-ek, ahol N a
 * kovetkezo() hívás paramétere.
 * @see java.util.Random
 */
public class Veletlen {
  /**
   * A determinizáló sor.
   */
  protected Sor kovetkezo;
  /**
   * Pontosan akkor `true', ha a generált számokat kizárólag a determinizáló
   * sorból szabad venni.
   */
  protected boolean csakDet;
  /**
   * Akkor váltódik ki, ha véletlenszámot kell generálni, `csakDet' igaz,
   * továbbá `kovetkezo' üres.
   */
  public static class CsakDetException extends Error {};
  /**
   * Akkor váltódik ki, ha a `kovetkezo'-ben szereplő szám nem esik a megadott
   * határok közé.
   */
  public static class DetHatarException extends Error {
    public DetHatarException(String s) { super(s)}
    public DetHatarException(int also, int x, int felso) { super(also+" <= "+x+" < "+felso+" kene")}
    public DetHatarException() { super()}
  };
  /**
   * A használt pszeudovéletlenszám-generátor.
   */
  protected Random random;
  /**
   * Egy új véletlenszám-generátort hoz létre, amely a Java alapértelmezés
   * szerinti véletlenszám-generátorát használja.
   * @see java.util.Random
   */
  public Veletlen() { this(new Random()false)}
  /**
   * Egy új véletlenszám-generátort hoz létre, amely a megadott
   * véletlenszám-generátort használja, és a megadott számú determinizáló
   * sort kezel.
   */
  public Veletlen(Random random, boolean csakDet) {
    this.random=random==null?new Random():random;
    this.csakDet=csakDet;
    kovetkezo=new Sor();
  }
  /**
   * Új értéket fűz a determinizáló sor végéhez.
   */
  public void sorba(int ujertek) {
    kovetkezo.vegehezFuz(new Integer(ujertek));
    osszeg+=ujertek;
  }
  /**
   * <U>aasdasd</U>-nál nagyobb-egyenlő, de <I>b</I>-nél kisebb véletlenszámot vesz
   * ki `kovetkezo' elejéről. Kiváltja a megfelelő kivételeket. `null'-t ad
   * vissza, ha `kovetkezo' üres volt.
   */
  protected synchronized Integer csakDetet(int a, int b) {
    // System.out.println(kovetkezo.length()+"L"); // DBG
    Integer i=((Integer)kovetkezo.elsoTorol());
    if (i==null) {
      if (csakDet) throw new CsakDetException();
    } else {
      int iv=i.intValue();
      if (iv<a || iv>=b) throw new DetHatarException(a,iv,b);
    }
    return i;
  }
  /**
   * 0 és <I>n-1</I> közötti, egyenletes eloszlású, egész értékű
   * ,,véletlenszámot'' generál (a határokat is beleértve),
   * az értéket a megadott determinisztikus sorból véve, ha az nem üres.
   * @param n ennél határozottan kisebb lesz a generált szám. Ne legyen
   *        nagyobb Integer.MAX_VALUE/2-nél!
   */
  public int egyenletes(int n) {
    Integer i=csakDetet(0, n);
    if (i!=null) return i.intValue();
    int r=random.nextInt();
    return (r<0?n-r:r)%n;
  }
  /**
   * <I>a</I> és <I>b-1</I> közötti, egyenletes eloszlású, egész értékű
   * ,,véletlenszámot'' generál (a határokat is beleértve),
   * az értéket a megadott determinisztikus sorból véve, ha az nem üres.
   * @param n ennél határozottan kisebb lesz a generált szám. Ne legyen
   *        nagyobb Integer.MAX_VALUE/2-nél!
   */
  public int egyenletes(int a, int b) {
    Integer i=csakDetet(a, b);
    if (i!=null) return i.intValue();
    int r=random.nextInt();
    return (r<0?(b-a)-r:r)%(b-a)+a;
  }
  /**
   * Poisson-eloszlású (<I>lambda</I> paraméterrel), egész értékű 
   * ,,véletlenszámot'' generál. Az átlagos futásidő <I>lambda</I>-val
   * arányos. Várható érték: <I>lambda</I>. Szórás: <I>sqrt(lambda)</I>.
   * Csak felso-nél kisebb (nemnegatív) számot ad vissza.
   * Minimum: 0. Maximum: amennyi belefér.
   *   <P>See Knuth, ACP, Section 3.4.1 Paragraph F.
   */
  public int poisson(double lambda, int felso) {
    if (lambda<=0.0) throw new ArithmeticException();
    Integer i=csakDetet(0, Integer.MAX_VALUE);
    if (i!=null) return i.intValue();
    double hatar=Math.exp(-lambda);
    double szorzat=1;
    int n;
    do {
      for (n=-1;szorzat>hatar;n++) szorzat*=random.nextDouble();
    } while (n<0 || n>=felso);
    return n;
  }
  /**
   * Csakúgy, mint kétparaméteres változata, de a felső határ
   * <code>Integer.MAX_VALUE</code>.
   */
  public int poisson(double lambda) {
    return poisson(lambda, Integer.MAX_VALUE);
  }
  /**
   * Exponenciális eloszlású (<I>lambda</I> paraméterrel) ,,véletlenszám''
   * kerekített (<code>Math.round()</code>), egész értékét generálja.
   * Csak felso-nél kisebb (nemnegatív) számot ad vissza.
   * A futásidő O(1). Várható érték: <I>1/lambda</I>.
   * Szórás: <I>1/lambda</I>. Minimum: 0. Maximum: amennyi belefér.
   * @see java.lang.Math#round
   */
  public int exponencialis(double lambda, int felso) {
    if (lambda<=0.0 || felso<1) throw new ArithmeticException();
    Integer i=csakDetet(0, felso);
    if (i!=null) return i.intValue();
    int n;
    do {
      n=Math.round((float)(-Math.log(random.nextDouble())/lambda));
    } while (n<0 || n>=felso);
    return n;
  }
  /**
   * Csakúgy, mint kétparaméteres változata, de a felső határ
   * <code>Integer.MAX_VALUE</code>.
   */
  public int exponencialis(double lambda) {
    return exponencialis(lambda, Integer.MAX_VALUE);
  }
  /**
   * Normális (Gauss-)eloszlású (<I>m</I> várható értékű, <I>sigma</I>
   * szórású) ,,véletlenszám''
   * kerekített (<code>Math.round()</code>), egész értékét generálja.
   * Csak `felso'-nél kisebb számot ad vissza.
   * Csak `also'-nél nagyobb-egyenlo számot ad vissza.
   * A futásidő JRE-függő.
   * @see java.lang.Math#round
   */
  public int normalis(double m, double sigma, int also, int felso) {
    if (sigma<=0.0 || felso<1) throw new ArithmeticException();
    Integer i=csakDetet(also, felso);
    if (i!=null) return i.intValue();
    int n;
    do {
      // n=Math.round((float)((random.nextGaussian()-m)/sigma));
      n=Math.round((float)(random.nextGaussian()*sigma+m));
      // System.err.println(n); // DEBUG
    } while (n<also || n>=felso);
    return n;
  }
  /**
   * Csakúgy, mint háromparaméteres változata, de a felső határ
   * <code>Integer.MAX_VALUE</code>.
   */
  public int normalis(double m, double sigma) {
    return normalis(m, sigma, Integer.MIN_VALUE, Integer.MAX_VALUE);
  }
  /**
   * Csakúgy, mint kétraméteres változata, de standard normális eloszlás
   * szerint generál (<I>m=0, sigma=1</I>).
   */
  public int normalis() {
    return normalis(0, 1, Integer.MIN_VALUE, Integer.MAX_VALUE);
  }
  /**
   * @return a használt véletlenszám-generátor
   */
  public Random getRandom() { return random; }
  /**
   * @return az utolsónak előírt veletlenszam (0, ha nincs)
   */
  public int getUtolso() {
    if (kovetkezo.isUres()) return 0;
    return ((Integer)kovetkezo.utolso()).intValue();
  }
  
  /**
   * Az eddig sorba() helyezettek összege.
   */
  private int osszeg=0;
  /**
   * Új értéket fűz a determinizáló sor végéhez, de kivonja belőle az utolsó
   * elemet.
   */
  public void sorbaDelta(int ujertek) {
    // System.err.println(kovetkezo.length()+" uj="+ujertek+" ut="+osszeg); // DBG
    // System.err.println("EGESZ:"+kovetkezo.dump());
    kovetkezo.vegehezFuz(new Integer(ujertek-osszeg));
    osszeg=ujertek;
  }
  
  // Most a belső osztályok következnek. Azért nem külön .java file-ba raktam
  // őket, mert kényelmesebb együtt kezelni és látni őket, erős köztük a
  // kohézió (no meg encapsulation).

  /**
   * Az őt megvalósító objektumok képesek <code>int</code> típusú
   * véletlenszámokat generálni. Az intervallum és az eloszlás tetszőleges.
   */
  public static interface Generalo {
    /**
     * Generálja a következő véletlenszámot.
     */
    public int general();
    /**
     * Új elemet helyez el a véletlensorba.
     */
    public void sorba(int ujertek);
    /**
     * Új értéket fűz a determinizáló sor végéhez, de kivonja belőle az utolsó
     * elemet.
     */
    public void sorbaDelta(int ujertek);
  }
  
  /**
   * <I>[a,b)</I>-n egyenletes eloszlású számokat generál.
   */
  public static class Egyenletes extends Veletlen implements Generalo {
    /**
     * Alsó határ, belelértendő az intervallumba.
     */
    protected int a;
    /**
     * Felső határ, nem tartozik az intervallumhoz.
     */
    protected int b;
    /**
     * @see Veletlen#Veletlen
     */
    public Egyenletes(Random random, boolean csakDet, int a, int b) {
      super(random, csakDet);
      this.a=a; this.b=b;
    }
    public Egyenletes(int a, int b) {
      super();
      this.a=a; this.b=b;
    }
    /**
     * Átállítja a felső határt.
     */
    public void setB(int b) { this.b=b; }
    /**
     * Generálja a következő véletlenszámot.
     */
    public int general() {
      return this.egyenletes(a, b);
    }
  } /// class Egyenletes
  
  /**
   * <I>[d,b+d)</I>-n <I>lambda</I> paraméterű, Poisson-eloszlású számoknál
   * $d$-vel nagyobb számokat generál.
   */
  public static class Poisson extends Veletlen implements Generalo {
    /**
     * Alsó határ, eltolás, belelértendő az intervallumba.
     */
    protected int d;
    /**
     * Felső határ (eltolás nélkül), nem tartozik az intervallumhoz.
     */
    protected int b;
    /**
     * Az eloszlás paramétere. (Egyben várható értéke és szórásnégyzete.)
     */
    protected double lambda;
    /**
     * @see Veletlen#Veletlen
     */
    public Poisson(Random random, boolean csakDet, int b, int d, double lambda) {
      super(random, csakDet);
      this.b=b; this.d=d; this.lambda=lambda;
    }
    public Poisson(int b, int d) {
      super();
      this.b=b; this.d=d; this.lambda=lambda;
    }
    /**
     * Generálja a következő véletlenszámot.
     */
    public int general() {
      return d+this.poisson(lambda, b);
    }
  } /// class Poisson

  /**
   * <I>[d,b+d)</I>-n <I>lambda</I> paraméterű, exponencialis eloszlású
   * számok egészre kerekítettjeinél
   * $d$-vel nagyobb számokat generál.
   */
  public static class Exponencialis extends Veletlen implements Generalo {
    /**
     * Alsó határ, eltolás, belelértendő az intervallumba.
     */
    protected int d;
    /**
     * Felső határ (eltolás nélkül), nem tartozik az intervallumhoz.
     */
    protected int b;
    /**
     * Az eloszlás paramétere. (Egyben várható értékének és szórásának is
     * reciproka.)
     */
    protected double lambda;
    /**
     * @see Veletlen#Veletlen
     */
    public Exponencialis(Random random, boolean csakDet, int b, int d, double lambda) {
      super(random, csakDet);
      this.b=b; this.d=d; this.lambda=lambda;
    }
    public Exponencialis(int b, int d) {
      super();
      this.b=b; this.d=d; this.lambda=lambda;
    }
    /**
     * Generálja a következő véletlenszámot.
     */
    public int general() {
      return d+this.exponencialis(lambda, b);
    }
  } /// class Exponencialis

  /**
   * <I>[a,b)</I>-n <I>m</I> várható értékű, <I>sigma</I> szórású,
   * normális eloszlású számok egészre kerekítettjeinél
   * $d$-vel nagyobb számokat generál.
   */
  public static class Normalis extends Veletlen implements Generalo {
    /**
     * Alsó határ, belelértendő az intervallumba.
     */
    protected int a;
    /**
     * Felső határ, nem tartozik az intervallumhoz.
     */
    protected int b;
    /**
     * Az eloszlás egyik paramétere, egyben várható értéke.
     */
    protected double m;
    /**
     * Az eloszlás másik paramétere, egyben szórása.
     */
    protected double sigma;
    /**
     * @see Veletlen#Veletlen
     */
    public Normalis(Random random, boolean csakDet, int a, int b, double m, double sigma) {
      super(random, csakDet);
      this.b=b; this.a=a; this.m=m; this.sigma=sigma;
    }
    public Normalis(int a, int b) {
      super();
      this.b=b; this.a=a; this.m=m; this.sigma=sigma;
    }
    /**
     * Generálja a következő véletlenszámot.
     */
    public int general() {
      // System.err.println("DDD"); // DEBUG
      int i=this.normalis(m, sigma, a, b);
      // System.err.println("Gen."); // DEBUG
      return i;
    }
  } /// class Normalis
  /**
   * A megadott diszkrét, véges eloszlású, nemnegatív értékű számokat
   * generál.
   */
  public static class Veges extends Veletlen implements Generalo {
    /**
     * `eloszlas' elemeinek összege. Bele kell, hogy férjen az int-be.
     */
    protected int osszeg;
    /**
     * Az eloszlás. Nemnegatív gyakoriságértékek, melyek összege pozitív.
     * Az `i' kimenetel valószínűsége: <I>eloszlas[i]/osszeg</I>.
     */
    protected int[] eloszlas;
    /**
     * Segédfüggvény pl. `osszeg' kiszámítására.
     */
    private synchronized void osszegez(int eloszlas[]) {
      if (eloszlas==null || eloszlas.length==0) throw new ArithmeticException();
      this.eloszlas=eloszlas;
      osszeg=0;
      for (int i=eloszlas.length-1;i>=0;i--) osszeg+=eloszlas[i];
      if (osszeg<1) throw new ArithmeticException();
    }
    /**
     * @see Veletlen#Veletlen
     */
    public Veges(Random random, boolean csakDet, int eloszlas[]) {
      super(random, csakDet);
      this.eloszlas=eloszlas; osszegez(eloszlas);
    }
    public Veges(int eloszlas[]) {
      super();
      this.eloszlas=eloszlas; osszegez(eloszlas);
    }
    /**
     * Generálja a következő véletlenszámot.
     */
    public int general() {
      Integer ii=this.csakDetet(0, eloszlas.length);
      if (ii!=null) return ii.intValue();
      int n=this.egyenletes(osszeg);
      int i;
      for (i=eloszlas.length-1;i>=0;i--)
        if (0>(n-=eloszlas[i])) return i;
      throw new ArithmeticException()/* notreached */
    }
  } /// class Veges
} /// class Veletlen