# Copyright 2015 Théo Friberg under GNU GPL 3

import json

def readNodes(file):
    with open(file, "r") as data_file:
        return json.load(data_file)

def inflateFile(mat, fileToInflate, x=0, y=0):

    """
This method sets up a material saved to a JSON file. It returns a dictionary of
nodes with their labels as keys. The nodes are added to the specified material.

_Usage_: inflateFile(<reference to material the nodes are added to>,
<path of the file to load>, [optional <translation of the final node setup on the x axis>, <translation of the final node setup on the y axis>])

The syntax of the JSON file must be as follows:

1. Opening declaration (this may later be extended to create operators automatically and contain operator metadata):

{
"NodeSetup":[

2. Nodes as follows

{
    "label": "<label for the node>", - the node's mandatory identifier
    "type": "<the type of the node>, - the node's type (mandatory)
    "location": [<x>, <y>], - the node's mandatory location
    "in": [
              [<node>, <output>, (mandatoy) <input>],
                              <etc.>
          ] - (optional) Links going in to the node. Parameters:

            * The node whose output we take
            * Which output we take (can either be the display name, or
              the index of the output. In the former case, must be written
              as such: "\"<name>\"". In the later case, can be written as
              either <index> or "<index>".)
            * (optional) The input the connection goes into. The syntax is
              as per above. If omitted, links go into inputs in numerical
              order from top to bottom.
},

<etc.>

Note: Nodes can be accessed in more depth. For accessing the blend_mode of
a MixRGB, for example, one would write eg. (python):

    <node>.blend_mode = "MULTIPLY"

From JSON, this can be written as:

    "blend_mode": "\"MULTIPLY\""

For setting default values of inputs, use the following syntaxes:

    "inputs[0].default_value": 1
or

    "inputs[\"Color\"].default_value": [0, 1, 0, 1]

3. Closing declaration as follows:

]
}

The file is read using Python's built-in JSON parser, so indentation or line breaks are optional."""

    # Variables for the parsed JSON array, the nodes added and all the
    # nodes in the material, respectively.

    nodes_JSON = readNodes(fileToInflate)
    nodes_dict = {}
    nodes = mat.node_tree.nodes

    # We iterate a first time to create the nodes with their settings 

    for node in nodes_JSON["NodeSetup"]:
        technical_name = node["type"]
        location = node["location"]
        nodes_dict[node["label"]] = nodes.new(technical_name)
        nodes_dict[node["label"]].location = (node["location"][0]+x,
                                              node["location"][1]+y)
        nodes_dict[node["label"]].name = node["label"]
        nodes_dict[node["label"]].label = node["label"]

        # The nodes' parameters can be generic, runnable Python.
        # This requires us to actually execute part of the files.

        for attribute in node.keys():
            if attribute not in ("in", "label", "type", "location"):
                exec("nodes_dict[node[\"label\"]]."+ attribute + " = " +
                     str(node[attribute]))

    # We create the links between the nodes
    # The syntax in the json is the following
    # "in": [
# ["<what node is plugged in>", <which of the nodes outputs is used, can be
# either string, as in "\"Color\"" or number, eg. 0 for the first output.>,
# <what input is this plugged to. Can be omitted for sequentially filling all
# inputs. If this has a value, it works like the previous value.>],
# <next inputs etc.>
#               ]

    links = mat.node_tree.links

    for node in nodes_JSON["NodeSetup"]:
        if "in" in node.keys(): # Does the node have links?
            i = 0
            while i < len(node["in"]): # We iterate over the links

                # Is a specific input specified?

                if len(node["in"][i]) == 3:

                    # Contruct and execute the line adding a link
           
                    exec ("links.new(nodes_dict[\"" + node["in"][i][0] +
                          "\"].outputs[" + str(node["in"][i][1]) +
                          "], nodes_dict[\"" + node["label"] + "\"].inputs["
                          + str(node["in"][i][2]) + "])")
                else:

                    # We don't have a specific input to hook up to

                    exec ("links.new(nodes_dict[\"" + node["in"][i][0] +
                          "\"].outputs[" + str(node["in"][i][1]) +
                          "], nodes_dict[\"" + node["label"] + "\"].inputs["
                           + str(i) + "])")
                i += 1

    # We return the nodes for purposes of further access to them

    return nodes_dict