from flask import Flask, render_template, request, redirect, Response
from sympy import *
from helpers import apology
import numpy as np
from matplotlib import pyplot as plt

app = Flask(__name__)

# Convert our commonly used variables into sympy symbols
x, y, z, t, X, Y, Z, T = symbols('x y z t X Y Z T')

@app.route('/')
def hello():
    return render_template('index.html')

@app.route('/differentiation', methods=['GET', 'POST'])
def differentiation():
    if request.method == "POST":
        # Check if inputs were given
        if not request.form.get("function"):
            return apology("must provide a function", 400)
        f = request.form.get("function")

        # Setup our symbols for SymPy
        f = setup_symbols(f)

        # Differentiate and return latex expressions
        fprime = latex(f.diff(x))
        value = latex(f)
        return render_template("differentiated.html", value=value, fprime=fprime)
    else:
        return render_template("differentiation.html")

@app.route("/integration", methods=["GET", "POST"])
def integration():
    if request.method == "POST":
        # Check if inputs were given
        if not request.form.get("function"):
            return apology("must provide a function", 400)
        f = request.form.get("function")

        # Setup our symbols for SymPy
        f = setup_symbols(f)

        # Integrate and return latex expressions
        fintegral = latex(f.integrate(x))
        value = latex(f)
        return render_template("integrated.html", value=value, fintegral=fintegral)
    else:
        return render_template("integration.html")

@app.route("/riemann", methods = ["GET", "POST"])
def riemann():
    if request.method == "POST":
        # Check if inputs were given
        if not request.form.get("function"):
            return apology("must provide a function", 400)
        if not request.form.get("lowerbound"):
            return apology("must provide a lower bound", 400)
        if not request.form.get("upperbound"):
            return apology("must provide an upper bound", 400)
        if not request.form.get("subintervals"):
            return apology("must provide a number of subintervals", 400)
        if not request.form.get("sumtype"):
            return apology("must choose left or right", 400)

        # Get our info from form
        f = request.form.get("function")
        sumtype = request.form.get("sumtype")
        lb = int(request.form.get("lowerbound"))
        ub = int(request.form.get("upperbound"))
        si = int(request.form.get("subintervals"))

        # Setup our symbols for SymPy
        f = setup_symbols(f)

        # Run through Riemann Sum algorithm, creatings lists for display
        #           of inputs, outputs, and areas (their products)

        value = latex(f)
        dx = round((ub - lb) / si, 3)
        inputs = list()
        if sumtype == "1":
            for i in range(0, si):
                inputs.append(round(dx * (i), 3))
        if sumtype == "2":
            for i in range(0, si):
                inputs.append(round(dx * (i + 1), 3))
        outputs = list()
        for input in inputs:
            temp = f.subs(x, input)
            outputs.append(round(temp, 3))
        rectangles = list()
        for output in outputs:
            temp = output * dx
            rectangles.append(round(temp, 3))
        result = round(sum(rectangles), 3)

        # Choose template based on left or right sum
        if sumtype == "1":
            return render_template("summed.html", value=value, sumtype=sumtype, lb=lb, ub=ub, si=si, dx=dx,
                                   inputs=inputs, outputs=outputs, rectangles=rectangles, result=result)
        else:
            return render_template("rightSummed.html", value=value, sumtype=sumtype, lb=lb, ub=ub, si=si, dx=dx,
                                   inputs=inputs, outputs=outputs, rectangles=rectangles, result=result)
    else:
        return render_template("riemann.html")

@app.route("/maxmin", methods=["GET", "POST"])
def maxmin():
    if request.method == "POST":
        # Check if inputs were given
        if not request.form.get("function"):
            return apology("must provide a function", 400)
        if not request.form.get("lowerbound"):
            return apology("must provide a lower bound", 400)
        if not request.form.get("upperbound"):
            return apology("must provide an upper bound", 400)

        # Get input from form
        f = request.form.get("function")
        lb = round(sympify(request.form.get("lowerbound")), 3)
        ub = round(sympify(request.form.get("upperbound")), 3)

        # Setup our symbols for SymPy
        f = setup_symbols(f)

        # Get Derivative, solve for real solutions, update candidates list
        fprime = f.diff(x)
        solutions = list()
        solutions.append(round(f.subs(x,0), 3))
        candidates = list()
        for solution in solutions:
            candidates.append(solution)
        candidates.append(lb)
        candidates.append(ub)

        # Fill values list with solutions
        values = list()
        for candidate in candidates:
            temp = round(f.subs(x, candidate), 3)
            values.append(temp)

        # Find max/min of values
        maximum = max(values)
        newvar = min(values)

        # Create Graph
        lam_f = lambdify(x, f, 'numpy')
        X = np.linspace(lb-100, ub+100, (ub-lb)*10)
        y = lam_f(X)

        # plt.figure(figsize=(3,4))
        plt.scatter(X, y, c='g', marker='.', )
        max_idx = np.argmax(values)
        min_idx = np.argmin(values)
        plt.plot(candidates[max_idx], values[max_idx], c='r', marker='o', ms=10, label='max')
        plt.plot(candidates[min_idx], values[min_idx], c='b', marker='o', ms=10, label='min')
        plt.legend()
        plt.savefig('static/img/new_plot.png')
        plt.close()

        # Turn all into latex
        value = latex(f)
        fprime = latex(fprime)
        for i, solution in enumerate(solutions):
            solutions[i] = latex(solution)
        return render_template("optimized.html", value=value, fprime=fprime, solutions=solutions, lb=lb, ub=ub,
                               candidates=candidates, newvar=newvar, values=values, maximum=maximum,
                               url='static/img/new_plot.png')
    else:
        return render_template("maxmin.html")

@app.route("/aprox", methods=["GET", "POST"])
def aprox():
    if request.method == "POST":
        # Check if inputs were given
        if not request.form.get("function"):
            return apology("must provide a function", 400)
        if not request.form.get("easy"):
            return apology("must provide an easy value", 400)
        if not request.form.get("hard"):
            return apology("must provide a difficult value", 400)

        # Get inputs, sympify them, and check to see if valid
        f = request.form.get("function")
        a = request.form.get("easy")
        h = request.form.get("hard")
        # Setup our symbols for SymPy
        f = setup_symbols(f)

        # Make sure a and h are numbers
        a = sympify(a)
        h = sympify(h)
        if not a.is_number:
            return apology("easy value must be a number", 400)
        if not h.is_number:
            return apology("difficult value must be a number", 400)

        a = round(a, 3)
        h = round(h, 3)
        # Run through Linearization algorithm
        fprime = f.diff(x)
        fa = round(f.subs(x, a), 3)
        fprimea = round(fprime.subs(x, a), 3)
        lh = round(fa + fprimea*(float(h)-float(a)), 3)

        # Convert to latex for MathJax reading
        value = latex(f)
        fprime = latex(fprime)
        fa = latex(fa)
        lh = latex(lh)

        return render_template("aproxd.html", value=value, fprime=fprime, a=a, h=h, fa=fa, fprimea=fprimea, lh=lh)
    else:
        return render_template("aprox.html")

def errorhandler(e):
    """Handle error"""
    if not isinstance(e, HTTPException):
        e = InternalServerError()
    return apology(e.name, e.code)

def setup_symbols(f):
    f = sympify(f)

    # replace commonly used variables with x
    for letter in [x, y, z, t, X, Y, Z, T]:
        f = f.subs(letter, x)
    return f



if __name__ == '__main__':
    app.run()
