import csv
import json
import numpy as np

# Import the LOD library. This is always necessary.
from lod import lod

def main(lod_manager):
    # LOD preparations
    # The object_manager contains every object that has been created by or loaded into this Scenario so far.
    object_manager = lod.get_object_manager()
    # Get the arguments that were given to this Program.
    # Each element of 'arguments' is a named object that was passed to this Program.
    # If the object is a file, that file can be opened.
    # The parameters selected by the user have been put into a single file in JSON format, so we can just read them out that way.
    # (Note that if your requirements are more complicated, you can use several input files from different sources.
    # For example, if you have two different Options that each require parameters from users, or several files that have been uploaded by the user or generated by other programs)
    arguments = lod.get_arguments()
    with open(arguments['user_parameters_file'].file, 'r') as f:
        user_parameters = json.load(f)
        timeseries_as_text = user_parameters['timeseries']
        c = user_parameters['c']
        use_inbuilt_visualization = user_parameters['use_inbuilt_visualization']
    # Try to parse the timeseries entered by the user.
    # If this doesn't work, show the Option again with an error message, then return.
    # The Option we create here is almost identical to the original one that was created by the Rule Demo-a-simple-timeseries-prediction,
    # but we set the default values to what the user entered before.
    try:
        timeseries = [float(a) for a in timeseries_as_text.split(',')]
        if len(timeseries) == 0:
            raise Exception()
    except:
        description = None
        trigger = {}
        display = {
            'must_always_be_shown' : True,
            'parameter_file_name' : 'userParametersFile',
            'message_components' : [
                {
                    "text" : "The timeseries you entered could not be parsed. Please try again."
                },
                {
                    'type' : 'option_parameter_selector',
                    'name' : 'timeseries',
                    'title' : "Timeseries to predict",
                    'description' : "The timeseries, as a string of comma-separated values.",
                    'optional' : False,
                    'value' : {
                        'type' : 'string',
                        'default' : "1.0, 2.0, 3.0",
                    }
                },
                {
                    'type' : 'option_parameter_selector',
                    'name' : 'c',
                    'title' : "Number of elements to predict",
                    'description' : "",
                    'optional' : False,
                    'value' : {
                        'type' : 'int',
                        'min' : 1,
                        'default' : c,
                    }
                },
                {
                    'type' : 'option_parameter_selector',
                    'name' : 'use_inbuilt_visualization',
                    'title' : "Use inbuilt visualization?",
                    'description' : "If selected, the predicted timeseries will be visualized with a simple in-built program. Otherwise it will be left to the Programs of other users to visualize the result (this is not part of the tutorial and may not work!)",
                    'optional' : True,
                    'value' : {
                        'type' : 'bool',
                        'default' : use_inbuilt_visualization,
                    }
                }
            ]
        }
        actions = [
            {
                'type' : 'execute_program',
                'program' : "Demo_a_simple_timeseries_prediction",
                'arguments' : {
                    'user_parameters_file' : 'userParametersFile',
                }
            },
            {
                'type' : 'deactivate_self',
            }
        ]
        existing_variables = {}
        new_option = lod.create_option('demo-a-simple-timeseries-prediction-try-again', description, trigger, display, actions, existing_variables)
        # Don't forget: set the confidence of the option, using a special Tag.
        # If you don't do this, Elody won't know that you want to display the option right away.
        # It will instead just keep it around for other Rules to interact with.
        lod.create_tag('!set_option_confidence').arguments(new_option).weight(1000)
        return

    # Make a prediction
    # If this was a real Program, this is where the important parts of your program would go.
    # Since this is just a tutorial, we just use simple linear regression for the prediction.
    xs = list(range(len(timeseries)))
    ys = timeseries
    res = np.polyfit(xs, ys, 1)
    # Predict c more values
    prediction = ys + [a * res[0] + res[1] for a in range(len(ys), len(ys) + c)]

    # Output the result

    if use_inbuilt_visualization:
        # If the user chose the inbuilt visualization, create a simple graph using the graphing library used by Elody,
        # then add a button to download the file as well as a message asking the user to rate the results.
        # ---
        # Create the file that we want to offer for download
        prediction_file = lod.add_output_file("prediction.csv")
        with open(prediction_file.file, 'w') as f:
            wr = csv.writer(f, dialect='excel')
            wr.writerow(prediction)
        # Create the plot
        original_series = {
            'color' : 'blue',
            'data' : [[i, a] for i,a in enumerate(timeseries)],
            'label' : 'original',
        }
        prediction_series = {
            'color' : 'red',
            'data' : [[i, a] for i,a in enumerate(prediction)][len(timeseries):],
            'label' : 'prediction',
        }
        plot_options = {
            'lines' : {
                'show' : True,
            },
            'points' : {
                'show' : True,
            }
        }
        # Create the Message, and attach both the plot and the file to it
        message = lod.display_message()
        message.add_plot([original_series, prediction_series], options=plot_options)
        message.add_downloadable_file("Download the prediction", prediction_file)
        # Create a second message to ask the user to rate the prediction.
        program_identifier, program_name, program_version = lod.get_own_program_details()
        lod.display_message().add_text("Rate this Program!").add_request_for_rating(program_identifier, event=None)
    else:
        # If the user chose not to use the inbuilt visualization, output a file containing the original and the prediction as well as some tags
        # Other Rules, written by other deveopers, can now react to these tags and display the prediction for you.
        # ---
        # Write the original to a properly formatted file in CSV
        original_file = lod.add_output_file("original.csv")
        with open(original_file.file, 'w') as f:
            wr = csv.writer(f, dialect='excel')
            wr.writerow(timeseries)
        # Write the prediction to a properly formatted file in CSV
        prediction_file = lod.add_output_file("prediction.csv")
        with open(prediction_file.file, 'w') as f:
            wr = csv.writer(f, dialect='excel')
            wr.writerow(prediction)
        # Create a simple tag connecting these two files.
        # It is up to others how they want to react to this Tag.
        lod.create_tag('demo_a_simple_timeseries_prediction_need_visualization').arguments(original_file, prediction_file)


# Execute the main() function defined above.
# We wrap the whole thing in an lod.manager().
# This ensures that the objects created above (Options, Files, Messages, Tags) are made available to Elody.
#     (The objects are not created immediately, since the program runs in an isolated environment.
#     Instead, they are all given to Elody once the Program has finished running.)
# It also creates log files from any errors or print() statements that occur, which is useful for debugging.
# To inspect these log files, you need to use the lod-executor to run the Programs locally.
with lod.manager(suppress_exceptions_after_logging_them=False, redirect_stdout_to_log=True) as lod_manager:
    main(lod_manager)
