package javacodebook.core.fraction;

/**
 * Eine Klasse, die einen Bruch repräsentiert und die 
 * 4 Grundrechenarten für Brüche beherscht.
 * @author Mark Donnermeyer
 */
public class Fraction {
    
    private long numerator;     // der Zaehler
    private long denominator;   // der Nenner
    
    /**
     * Einen neuen Bruch erzeugen. Der Wert des Bruches 
     * ist 0 (Intern ist der Wert 0/1).
     */
    public Fraction() {
        denominator  = 1;
        numerator = 0;
    }
    
    /**
     * Einen neuen Bruch erzeugen. Der Bruch wird mit 
     * einer ganzen Zahl initialisiert.
     * @param value der Wert des Bruches. 
     */
    public Fraction(long value) {
        numerator = value;
        denominator = 1;
    }
    
    /**
     * Einen neuen Bruch erzeugen.
     * @param numerator der Zähler des Bruches
     * @param denominator der Nenner des Bruches. 
     * Der Nenner darf nicht 0 sein.
     */
    public Fraction(long numerator, long denominator) {
        this.numerator = numerator;
        this.denominator = denominator;
        cancelDown();
    }
    
    /**
     * Einen neuen Bruch erzeugen.
     * @param other Der Bruch, aus dem der neue Bruch
     * erzeugt wird.
     */
    public Fraction(Fraction other) {
        this.numerator = other.numerator;
        this.denominator = other.denominator;
    }
    /**
     * Einen neuen Bruch erzeugen.
     * @param fraction der Bruch als String in der 
     * Form <Zähler>/<Nenner>
     */
    public Fraction(String fr) {
        int slash = fr.indexOf('/');
        try {
            numerator   = new Long(
                    fr.substring(0,slash).trim()).longValue();
            
            denominator = new Long(
                    fr.substring(slash+1,
                    fr.length()).trim()).longValue();
        }
        catch (Exception nfe) {
            throw new NumberFormatException(
                fr + " ist kein Bruch"
            );
        }
        this.numerator   = numerator;
        this.denominator = denominator;
        cancelDown();
    }
    
    /**
     * Berechnet den "größten gemeinsame Teiler (ggt, engl. gcd)" 
     * zweier Zahlen
     */
    private static long gcd(long n1, long n2) {

        if (n1 == 0 || n2 == 0)  return 0;
        
        if (n1 < 0) n1 *= -1;
        if (n2 < 0) n2 *= -1;
        
        while (true) {
            if (n1 < n2) {
                n2 %= n1;
                if (n2 == 0)  return n1;
            }
            else {
                n1 %= n2;
                if (n1 == 0)  return n2;
            }
        }
    }
    
    /**
     * Berechnet das "kleinste gemeinsame Vielfache (kgV, engl. lcm)" 
     * zweier Zahlen.
     */
    private static long lcm(long n1, long n2) {
        if (n1 == 0 || n2 == 0)
            return 0;
        else
            return n1 / gcd(n1, n2) * n2;
    }
    
    
    /**
     * Setzt den Zaehler des Bruches neu
     */ 
    public void setNumerator(long numerator) {
        this.numerator = numerator;
        cancelDown();
    }
    
    /**
     * Gibt den Zaehler des Bruches zurück
     */ 
    public long getNumerator() {
        return numerator;
    }
    
    /**
     * Setzt den Nenner des Bruches neu
     */ 
    public void setDenominator(long denominator) {

        if (denominator == 0)
            throw new ArithmeticException("Nenner ist 0.");
        
        if (denominator > 0)
            this.denominator = denominator;
        else {
            this.numerator *= -1;
            this.denominator  = -denominator;
        }
        
        cancelDown();
    }
    
    /**
     * gibt den Nenner des Bruches zurück
     */ 
    public long getDenominator() {
        return denominator;
    }
    
    
    public String toString() {
        if (numerator < 0) 
            return "-("+(0-numerator)+"/"+denominator+")";
        else
            return "("+numerator + "/"+denominator+")";
    }
    
    /**
     * Kürzt den Bruch
     */
    private void cancelDown() {
        if (denominator < 0)
        {
            numerator *= -1;
            denominator *= -1;
        }
        
        if (numerator == 0)
            denominator = 1;
        else {
            long tmp = gcd(numerator, denominator);
            numerator /= tmp;
            denominator  /= tmp;
        }
    }

    /**
     * Addiert zwei Brüche und gibt das Ergebnis zurück
     * @param fr der Bruch, der zu diesem Bruch addiert wird
     * @returns den neuen Bruch
     */
    public Fraction add(Fraction fr) {
        long newDenom = denominator * fr.getDenominator();
        long newNumer = numerator * fr.getDenominator() 
                      + denominator * fr.getNumerator();

        return new Fraction(newNumer, newDenom);
    }
    
    /**
     * Subtrahiert einen Bruch von diesem Bruch und gibt 
     * das Ergebnis zurück
     * @param fr der Bruch, der von diesem Bruch 
     * subtrahiert wird
     * @returns den neuen Bruch
     */
    public Fraction sub(Fraction fr) {
        return add(fr.neg());
    }
    
    /**
     * Multipliziert einen Bruch zu diesem Bruch und gibt 
     * das Ergebnis zurück
     * @param fr der Bruch, der zu diesem Bruch 
     * multipliziert wird
     * @returns den neuen Bruch
     */
    public Fraction mul(Fraction fr) {
        return new Fraction(numerator * fr.getNumerator(), 
                        denominator * fr.getDenominator());
    }
    
    /**
     * Dividiert diesen Bruch durch einen anderen Bruch und 
     * gibt das Ergebnis zurück
     * @param fr der Bruch, durch den dieser Bruch 
     * dividiert wird
     * @returns den neuen Bruch
     */
    public Fraction div(Fraction fr) {
        return new Fraction(numerator * fr.getDenominator(), 
                        denominator * fr.getNumerator());
    }
    
    /**
     * Negiert diesen Bruch und gibt das Ergebnis zurück
     * @returns den neuen Bruch
     */
    public Fraction neg()
    {
        return new Fraction(0-numerator, denominator);
    }
}