import json
from surveyapp import mongo
from flask import Flask, render_template, url_for, request, Blueprint, flash, redirect, abort
from flask_login import login_required, current_user
from surveyapp.graphs.forms import BarPieForm, ScatterchartForm, HistogramForm, MapForm, BoxForm
from bson.objectid import ObjectId

from surveyapp.graphs.utils import save_image, delete_image
from surveyapp.surveys.utils import parse_data, read_file


graphs = Blueprint("graphs", __name__)


class JSONEncoder(json.JSONEncoder):
    def default(self, o):
        if isinstance(o, ObjectId):
            return str(o)
        return json.JSONEncoder.default(self, o)


# Page where user chooses the type of graph they would like to create for their survey
@graphs.route('/choosegraph/<survey_id>', methods=['GET'])
@login_required
def choose_graph(survey_id):
    file_obj = mongo.db.surveys.find_one_or_404({"_id":ObjectId(survey_id)})
    if file_obj["user"] != current_user._id:
        flash("You do not have access to that page", "danger")
        return redirect(url_for("main.index"))
    return render_template("graphs/choosegraph.html", title="Select Graph", survey_id=survey_id)



@graphs.route('/graph/<survey_id>', methods=['GET', 'POST'])
@login_required
def graph(survey_id):
    # Get the file object so that we can load the data
    file_obj = mongo.db.surveys.find_one_or_404({"_id":ObjectId(survey_id)})
    if file_obj["user"] != current_user._id:
        flash("You do not have access to that page", "danger")
        return redirect(url_for("main.index"))
    # Get the id of the graph (if it exists yet)
    graph_id = request.args.get("graph_id")
    graph_obj = mongo.db.graphs.find_one({"_id":ObjectId(graph_id)})
    # i.e. if user is choosing to edit an existing graph then it already has a type
    if graph_obj:
        chart_type = graph_obj["type"]
    # Else user is creating a new graph of a chosen type
    else:
        chart_type = request.args.get("chart_type")
    # Read the csv file in
    df = read_file(file_obj["fileName"])
    # parse the columns to get information regarding type of data
    column_info = parse_data(df)
    # Convert the dataframe to a dict of records to be handled by D3.js on the client side.
    chart_data = df.to_dict(orient='records')
    # ----------SAME ROUTE USED FOR BAR AND PIE CHART----------
    if chart_type == "Bar chart" or chart_type == "Pie chart":
        return pie_bar_chart(survey_id, column_info, chart_data, graph_id, file_obj["title"], chart_type)
    # ----------SCATTER CHART----------
    elif chart_type == "Scatter chart":
        return scatter_chart(survey_id, column_info, chart_data, graph_id, file_obj["title"])
    # ----------HISTOGRAM----------
    elif chart_type == "Histogram":
        return histogram(survey_id, column_info, chart_data, graph_id, file_obj["title"])
    # ----------MAP CHART----------
    elif chart_type == "Map":
        return map_chart(survey_id, column_info, chart_data, graph_id, file_obj["title"])
    # ----------Box and whisker CHART----------
    elif chart_type == "Box and whisker":
        return box_chart(survey_id, column_info, chart_data, graph_id, file_obj["title"])
    else:
        flash("something went wrong", "danger")
        abort(404)




# Function that renders the box-chart page
def box_chart(survey_id, column_info, chart_data, graph_id, title):
    form = BoxForm()
    # Populate the form options. A box chart can take any data type for x-axis but y-axis must be numerical
    for column in column_info:
        form.x_axis.choices.append((column["title"], column["title"]))
        if column["data_type"] == "numerical":
            # We insert a tuple, The first is the 'value' of the select, the second is the text displayed
            form.y_axis.choices.append((column["title"], column["title"]))
    # Now we have specified the 'select' options for the form, we can check 'form.validate_on_submit'
    if form.validate_on_submit():
        image_data = request.form["image"]
        file_name = save_image(image_data, graph_id)
        # setting upsert=true in the update will create the entry if it doesn't yet exist, else it updates
        mongo.db.graphs.update_one({"_id": ObjectId(graph_id)},
        {"$set": {"title" : form.title.data,
                "surveyId": survey_id,
                "user" : current_user._id,
                "type" : "Box and whisker",
                "xAxis" : form.x_axis.data,
                "yAxis": form.y_axis.data,
                "image": file_name}}, upsert=True)
    # If we are editing the graph instead of creating new, we want to prepopulate the fields
    graph_obj = mongo.db.graphs.find_one({"_id":ObjectId(graph_id)})
    if graph_obj:
        form.x_axis.data = graph_obj["xAxis"]
        form.y_axis.data = graph_obj["yAxis"]
        form.title.data = graph_obj["title"]
    data = {"chart_data": chart_data, "title": title, "column_info" : column_info}
    return render_template("graphs/boxchart.html", data=data, form=form, survey_id=survey_id, graph_id=graph_id, chart_type="Box and whisker")



# Function that renders the map page
def map_chart(survey_id, column_info, chart_data, graph_id, title):
    form = MapForm()
    # Populate the form options.
    for column in column_info:
        form.variable.choices.append((column["title"], column["title"]))
    # Now we have specified the 'select' options for the form, we can check 'form.validate_on_submit'
    if form.validate_on_submit():
        image_data = request.form["image"]
        file_name = save_image(image_data, graph_id)
        # setting upsert=true in the update will create the entry if it doesn't yet exist, else it updates
        mongo.db.graphs.update_one({"_id": ObjectId(graph_id)},
        {"$set": {"title" : form.title.data,
                "surveyId": survey_id,
                "user" : current_user._id,
                "type" : "Map",
                "variable" : form.variable.data,
                "scope" : form.scope.data,
                "image": file_name}}, upsert=True)

    # If we are editing the graph instead of creating new, we want to prepopulate the fields
    graph_obj = mongo.db.graphs.find_one({"_id":ObjectId(graph_id)})
    if graph_obj:
        form.variable.data = graph_obj["variable"]
        form.scope.data = graph_obj["scope"]
        form.title.data = graph_obj["title"]
    data = {"chart_data": chart_data, "title": title, "column_info" : column_info}
    return render_template("graphs/map.html", data=data, form=form, survey_id=survey_id, graph_id=graph_id, chart_type="Map")



# Function that renders the bar-chart and pie chart pages
def pie_bar_chart(survey_id, column_info, chart_data, graph_id, title, chart_type):
    form = BarPieForm()
    # Populate the form options. A bar/pie chart can take any data type for x-axis but y-axis must be numerical
    for column in column_info:
        form.x_axis.choices.append((column["title"], column["title"]))
        if column["data_type"] == "numerical":
            # We insert a tuple, The first is the 'value' of the select, the second is the text displayed
            form.y_axis.choices.append((column["title"], column["title"]))
    # Now we have specified the 'select' options for the form, we can check 'form.validate_on_submit'
    if form.validate_on_submit():
        image_data = request.form["image"]
        file_name = save_image(image_data, graph_id)
        # setting upsert=true in the update will create the entry if it doesn't yet exist, else it updates
        mongo.db.graphs.update_one({"_id": ObjectId(graph_id)},
        {"$set": {"title" : form.title.data,
                "surveyId": survey_id,
                "user" : current_user._id,
                "type" : chart_type,
                "xAxis" : form.x_axis.data,
                "yAxis": form.y_axis.data,
                "yAggregation": form.y_axis_agg.data,
                "image": file_name}}, upsert=True)

    # If we are editing the graph instead of creating new, we want to prepopulate the fields
    graph_obj = mongo.db.graphs.find_one({"_id":ObjectId(graph_id)})
    if graph_obj:
        form.x_axis.data = graph_obj["xAxis"]
        form.y_axis.data = graph_obj["yAxis"]
        form.y_axis_agg.data = graph_obj["yAggregation"]
        form.title.data = graph_obj["title"]
    data = {"chart_data": chart_data, "title": title, "column_info" : column_info}
    if chart_type == "Bar chart":
        return render_template("graphs/barchart.html", data=data, form=form, survey_id=survey_id, graph_id=graph_id, chart_type="Bar chart")
    else:
        return render_template("graphs/piechart.html", data=data, form=form, survey_id=survey_id, graph_id=graph_id, chart_type="Pie chart")



def scatter_chart(survey_id, column_info, chart_data, graph_id, title):
    form = ScatterchartForm()
    for column in column_info:
        # Scatter charts require both x and y axis to have some numerical value (i.e. ordinal/interval but not categorical)
        if column["data_type"] == "numerical":
            # We insert a tuple, The first is the 'value' of the select, the second is the text displayed
            form.x_axis.choices.append((column["title"], column["title"]))
            form.y_axis.choices.append((column["title"], column["title"]))
    # Now we have specified the 'select' options for the form, we can prevalidate for 'form.validate_on_submit'
    if form.validate_on_submit():
        image_data = request.form["image"]
        file_name = save_image(image_data, graph_id)
        # setting upsert=true in the update will create the entry if it doesn't yet exist, else it updates
        mongo.db.graphs.update_one({"_id": ObjectId(graph_id)},
        {"$set": {"title" : form.title.data,
                "surveyId": survey_id,
                "user" : current_user._id,
                "type" : "Scatter chart",
                "xAxis" : form.x_axis.data,
                "xAxisFrom" : form.x_axis_from.data,
                "xAxisTo" : form.x_axis_to.data,
                "yAxis": form.y_axis.data,
                "yAxisFrom": form.y_axis_from.data,
                "yAxisTo": form.y_axis_to.data,
                "line": form.line.data,
                "image": file_name}}, upsert=True)

    # If we are editing the graph instead of creating new, we want to prepopulate the fields
    graph_obj = mongo.db.graphs.find_one({"_id":ObjectId(graph_id)})
    if graph_obj:
        form.x_axis.data = graph_obj["xAxis"]
        form.x_axis_from.data = graph_obj["xAxisFrom"]
        form.x_axis_to.data = graph_obj["xAxisTo"]
        form.y_axis.data = graph_obj["yAxis"]
        form.y_axis_from.data = graph_obj["yAxisFrom"]
        form.y_axis_to.data = graph_obj["yAxisTo"]
        form.line.data = graph_obj["line"]
        form.title.data = graph_obj["title"]
    data = {"chart_data": chart_data, "title": title, "column_info" : column_info}
    return render_template("graphs/scatterchart.html", data=data, form=form, survey_id=survey_id, graph_id=graph_id, chart_type="Scatter chart")




def histogram(survey_id, column_info, chart_data, graph_id, title):
    form = HistogramForm()
    for column in column_info:
        # Scatter charts require both x and y axis to have some numerical value (i.e. ordinal/interval but not categorical)
        if column["data_type"] == "numerical":
            # We insert a tuple, The first is the 'value' of the select, the second is the text displayed
            form.x_axis.choices.append((column["title"], column["title"]))
    # Now we have specified the 'select' options for the form, we can prevalidate for 'form.validate_on_submit'
    if form.validate_on_submit():
        image_data = request.form["image"]
        file_name = save_image(image_data, graph_id)
        # setting upsert=true in the update will create the entry if it doesn't yet exist, else it updates
        mongo.db.graphs.update_one({"_id": ObjectId(graph_id)},
        {"$set": {"title" : form.title.data,
                "surveyId": survey_id,
                "user" : current_user._id,
                "type" : "Histogram",
                "xAxis" : form.x_axis.data,
                "xAxisFrom" : form.x_axis_from.data,
                "xAxisTo" : form.x_axis_to.data,
                "groupSize" : form.group_count.data,
                "image": file_name}}, upsert=True)

    # If we are editing the graph instead of creating new, we want to prepopulate the fields
    graph_obj = mongo.db.graphs.find_one({"_id":ObjectId(graph_id)})
    if graph_obj:
        form.x_axis.data = graph_obj["xAxis"]
        form.x_axis_from.data = graph_obj["xAxisFrom"]
        form.x_axis_to.data = graph_obj["xAxisTo"]
        form.group_count.data = graph_obj["groupSize"]
        form.title.data = graph_obj["title"]
    data = {"chart_data": chart_data, "title": title, "column_info" : column_info}
    return render_template("graphs/histogram.html", data=data, form=form, survey_id=survey_id, graph_id=graph_id, chart_type="Histogram")


# DELETE A Graph
@graphs.route("/home/<survey_id>/<graph_id>/delete", methods=['POST'])
@login_required
def delete_graph(graph_id, survey_id):
    graph_obj = mongo.db.graphs.find_one_or_404({"_id":ObjectId(graph_id)})
    if graph_obj["user"] != current_user._id:
        flash("You do not have access to that page", "danger")
        abort(403)
    delete_image(graph_obj["image"])
    mongo.db.graphs.delete_one(graph_obj)
    return redirect(url_for('surveys.dashboard', survey_id=survey_id))
