#ifndef __COMPUTATION_GRAPH_H__
#define __COMPUTATION_GRAPH_H__

#include <stdio.h>
#include <math.h>

typedef void (*execute_node_t)(void *state,
                               const double* params,
                               const double *inputs,
                               double *outputs);

typedef void (*reset_node_t)(void *state);

enum node_processed_state {
    UNPROCESSED,
    DISCOVERED,
    PROCESSED
};

struct computation_graph {
    int n_nodes;
    int size;
    struct graph_node *nodes;
};

// Declares a node type
struct graph_node_type {
    const char* const* input_names; // Array of strings corresponding to the inputs
    const char* const* output_names; // Array of strings corresponding to the outputs
    const char* const* param_names; // Array of strings corresponding to the parameters
    int n_inputs; // Number of inputs
    int n_outputs; // Number of outputs
    int n_params; // Number of parameters
    execute_node_t execute; // Function describing how to produce outputs
    reset_node_t reset; // Reset this node. Called upon (re)connection
    size_t state_size; // Size of the state struct for this type
    int type_id; // A unique ID for this node type
};

// Holds a tuple for defining the source of a node. Includes the node ID and its output ID
struct node_src { 
    int controller_id;
    int controller_output;
};

// Declares an instance of a node
struct graph_node {
    const char *name; // Name of this node instance
    const struct graph_node_type* type; // Type of this node
    double *output_values; // Computed output values
    double *param_values; // Values of parameters set for this node
    int n_children; // The number of connected children
    void *state; // Pointer to the state instance
    int processed_state; // State of the node with respecct to the graph traversal
    struct node_src *input_srcs; // Array of tuples indicating the source for each input to this node
    int updated; // 1 if this node has had an input or parameter change
};

/*
 * Creates an empty computation graph
 * May return NULL on failure
 */
struct computation_graph *create_graph();

/*
 * Defines which node's output gets its value passed into the input of a different node.
 * Will call reset for each node which was previously orphaned, but is now connected to the graph
 * dest_node_id: The ID of the node to which the input belongs
 * dest_input: The ID of the input for node dest_cntl to pass the value into
 * src_node_id: The node ID where the value is coming from
 * src_output: The ID of the output on <src_node_id> where the value comes from
 * Returns 0 for success
 */
int graph_set_source(struct computation_graph *graph, int dest_node_id, int dest_input, int src_node_id, int src_output);

/*
 * Returns the source node/output pair of a node's input. The mirror of graph_set_source.
 * Returns a node ID of -1 if the requested node or input does not exist
 */
struct node_src graph_get_source(struct computation_graph *graph, int node_id, int input_id);

/*
 * Creates a new node with the given data, and adds it to the graph.
 * Returns a negative integer upon failure.
 * Otherwise returns a positive integer which is the ID of the new node
 */
int graph_add_node(struct computation_graph *graph,
                   const char *name,
                   const struct graph_node_type *type,
                   void *state);

/*
 * Returns the value at the output of the requested node for the requested output.
 * Returns 0 if the given node or output IDs are invalid
 */
double graph_get_output(const struct computation_graph *graph, int node_id, int output_id);

/*
 * Sets a parameter given by param_id on node node_id to the given value
 * Returns 0 upon success
 */
int graph_set_param_val(struct computation_graph *graph, int node_id, int param_id, double value);

/*
 * Returns the value of the param at param_id for the given node
 * Will return NaN if the given node or parameter IDs are not valid
 */
double graph_get_param_val(const struct computation_graph *graph, int node_id, int param_id);

/*
 * Computes the nodes given by node_id.
 * To do so, computes all nodes which are ancestors of each given node_id in topological order, with
 * the final computation being node_id itself.
 */
void graph_compute_nodes(struct computation_graph *graph, int* node_ids, int n_nodes);

/*
 * Writes a graphical representation of the given graph to <of> in the DOT language
 */
int export_dot(const struct computation_graph* graph, FILE* of, int print_outputs);

#endif // __COMPUTATION_GRAPH_H__