/******************************************************************************
 *  Name:    
 *  NetID:   
 *  Precept: 
 * 
 *  Description: Provides a data type Polynomial which works by using a symbol
 *  table to pair exponents (integers) with coefficients (doubles).
 ******************************************************************************/


import edu.princeton.cs.algs4.StdIn;
import edu.princeton.cs.algs4.StdOut;
import edu.princeton.cs.algs4.ST;

// (add other imports if necessary)

public class Polynomial
{
    
    // (Public) constant indicating the largest possible exponent that a
    // term can take in this Polynomial class
    
    public static final int MAX_EXPONENT = 10000;
    
    
    // This instance variable is part of the "Suggested Progress Steps" as
    // a possible solution (but there are many other equally good
    // solutions which do not use an ST object, so feel free to comment
    // this instance variable if it is confusing you)
    
    private final ST<Integer, Double> coefficients;
    
    
    // internal constructor, initializes new symbol table
    private Polynomial() {
        coefficients = new ST<Integer, Double>();
    }

    // internal constructor to initialize given a symbol table
    private Polynomial(ST<Integer, Double> s) {
        coefficients = s;
    }
    
    
    // Create a polynomial from an array 'coeffs' in which 'coeffs[i]' is
    // coefficient of x^i (x raised to the i-th power).
    
    public Polynomial(double[] coeffs) {

        if (coeffs.length > MAX_EXPONENT)
            throw new RuntimeException("LInput array is too long.");

        // iterate through coeffs, adding to our sym table only non-zero ones
        coefficients = new ST<Integer, Double>();
        for (int i = 0; i < coeffs.length; i++) {
            if (coeffs[i] != 0) {
                coefficients.put(i, coeffs[i]);
            }
        }
    }
    
    
    // Return the value of the coefficient of the term with exponent
    // 'expo' (and 0.0 if such a coefficient doesn't exist)
    
    public Double coeff(int expo) {

        if (expo < 0 || expo >= MAX_EXPONENT)
            throw new RuntimeException("Exponent not in range.");

        // use this to prevent calling both contains() and get(), which is
        // not efficient as it iterates twice through the table, when we
        // really only need to do it once
        Double d = coefficients.get(expo);

        if (d == null)
            return 0.0;
        else
            return d;

    }
    
    
    // Return a string representation of the polynomial
    
    public String toString() {
        // Use the (provided) pretty printer.
        
        // NOTE: using this class will require compiling it:
        // $ javac-algs4 PolynomialTools.java
        
        return PolynomialTools.toString(this);
    }
    
    
    // Create a new immutable polynomial resulting from the addition of the
    // instance polynomial to 'that'
    
    public Polynomial add(Polynomial that) {

        // create a new ST
        ST<Integer, Double> newPoly = new ST<Integer, Double>();

        // copy over pairings from that
        for (int exp: that.coefficients.keys()) {
            newPoly.put(exp, that.coeff(exp));
        }

        // add the necessary coefficients from this
        for (int exp: coefficients.keys()) {
            newPoly.put(exp, coefficients.get(exp) + that.coeff(exp));
        }

        // return a new polynomial based on this symbol table
        return new Polynomial(newPoly);
    }
    
    
    // Create a new immutable polynomial resulting from the product of the
    // instance polynomial and 'that'
    
    public Polynomial multiply(Polynomial that) {

        // create a new ST
        ST<Integer, Double> newPoly = new ST<Integer, Double>();

        // loop through the keys in both polynomials
        for (int exp1: that.coefficients.keys()) {

            for (int exp2: coefficients.keys()) {

                if (exp1 + exp2 >= MAX_EXPONENT)
                    throw new RuntimeException("Product is too large");

                Double d = newPoly.get(exp1+exp2);
                // if exp1+exp2 is not in the symbol table, put it in
                if (d == null) {
                    newPoly.put(exp1+exp2, that.coeff(exp1) * this.coeff(exp2));
                }
                // otherwise, just add the product to what's already there
                else
                    newPoly.put(exp1+exp2, that.coeff(exp1)*this.coeff(exp2)
                            + d);

            }
        }

        return new Polynomial(newPoly);

    }
    
    
    // Evaluate the polynomial at some value 'x'
    
    public double evaluate(double x) {
        double answer = 0;

        // loop through the exponents, incrementing answer when needed
        for (int exp: coefficients.keys()) {
            answer += Math.pow(x, exp) * coefficients.get(exp);
        }
        return answer;
    }
    
    
    // Some unit testing
    
    public static void main(String[] args) {
        
        // =========
        // Our tests
        // =========
        
        runStaffTests();
        
        
        // ================================
        // Your own tests below? (optional)
        // ================================

        double[] c1 = {1, 3, 4};
        double[] c2 = {1, 1, 2};
        Polynomial p1 = new Polynomial(c1);
        Polynomial p2 = new Polynomial(c2);

        System.out.println("------------------------------------------------");
        System.out.println(p2.multiply(p1));
        System.out.println(p1.multiply(p2));
        System.out.println("------------------------------------------------");
        System.out.println(p1.add(p2));
        System.out.println(p2.add(p1));

        // ...
    }
    
    
    
    
    
    
    // Below are tests we have created as an example and so that you may
    // check your work on your computer
    
    public static void runStaffTests() {
        
        // ===================================
        // Creation of test Polynomial objects
        // ===================================
        
        // Empty polynomial!
        Polynomial mz = new Polynomial(new double[0]);
        
        // Single terms (to test constructor)
        Polynomial m10 = PolynomialTools.makeSingleTerm(4.0, 10);
        Polynomial m5  = PolynomialTools.makeSingleTerm(2.0,  5);
        Polynomial m2  = PolynomialTools.makeSingleTerm(7.5,  2);
        Polynomial m1  = PolynomialTools.makeSingleTerm(1.0,  1);
        Polynomial m0  = PolynomialTools.makeSingleTerm(13.0, 0);
        
        // Polynomials (to test operations)
        Polynomial p1a = m10.add(m5).add(m2);
        Polynomial p1b = m2.add(m10).add(m5);
        Polynomial p2 = m0.add(m2).add(m5);
        Polynomial p3 = m0.add(m10);
        Polynomial p4 = m0.add(m1);
        Polynomial p5 = p4.multiply(p4); // multiply with itself
        Polynomial p6 = p4.multiply(p3); // multiply with different
        
        
        // ================================================================
        // Tests using string formatting through the PolynomialTools module
        // ================================================================
        
        // Empty polynomial (testing edge case)
        PolynomialTools.runStringTest(mz,  "0.0");
        
        // Testing monomials (so just formatting)
        PolynomialTools.runStringTest(m10, "4.0*x^10");
        PolynomialTools.runStringTest(m10, "4.0*x^10");
        PolynomialTools.runStringTest(m5, "2.0*x^5");
        PolynomialTools.runStringTest(m2, "7.5*x^2");
        PolynomialTools.runStringTest(m1, "1.0*x^1");
        PolynomialTools.runStringTest(m0, "13.0");
        
        // Testing polynomials (so result of operation)
        PolynomialTools.runStringTest(p1a, "7.5*x^2 + 2.0*x^5 + 4.0*x^10");
        PolynomialTools.runStringTest(p1b, "7.5*x^2 + 2.0*x^5 + 4.0*x^10");
        PolynomialTools.runStringTest(p1a, p1b.toString()); // transitivity
        PolynomialTools.runStringTest(p2, "13.0 + 7.5*x^2 + 2.0*x^5");
        PolynomialTools.runStringTest(p3, "13.0 + 4.0*x^10");
        PolynomialTools.runStringTest(p4, "13.0 + 1.0*x^1");
        
        // Testing multiplication
        PolynomialTools.runStringTest(p5, "169.0 + 26.0*x^1 + 1.0*x^2");
        PolynomialTools.runStringTest(p6, "169.0 + 13.0*x^1 + 52.0*x^10 + 4.0*x^11");
        
        
        // ======================
        // Tests using 'evaluate'
        // ======================
        
        Polynomial p66 = new Polynomial( new double[] {1.0, 1.0, 1.0,
                                                       1.0, 1.0, 1.0, 1.0 } );
        Polynomial p77 = new Polynomial( new double[] {0.0,
                                                       1.0 + 1.0/2.0 + 1.0/3.0 +
                                                       1.0/4.0 + 1.0/5.0 + 1.0/6.0 } );
        Polynomial p88 = p66.multiply(p77);
        
        // [because doubles are tricky] "fake" test using evaluation
        PolynomialTools.runEvaluateTest(p88, 1.0, 17.15);
        PolynomialTools.runEvaluateTest(p88, 2.0, 622.3);
        
    }
    
}