"""
Implementation of a Python node that uses internal state information to compute outputs.

There are two types of state information in use here:
    - OgnTutorialStatePy.step (per-class state information)
      This is inherently dangerous in a multi-threaded multi-hardware evaluation so
      it must be used with care. In this case the value is only used when a node is created, which for now is a safe
      single-threaded operation

    - per-node state information.
"""


class OgnTutorialStatePyInternalState:
    """Convenience class for maintaining per-node state information"""

    def __init__(self):
        """Instantiate the per-node state information.

        Note: For convenience, per-node state data is maintained as members of this class, imposing the minor
              restriction of having no parameters allowed in this constructor.

        The presence of the "state" section in the .ogn node has flagged to omnigraph the fact that this node will
        be managing some per-node state data.
        """
        # Start all nodes with a monotinic increment value of 0
        self.increment_value = 0
        # Get this node's internal step value from the per-class state information
        self.node_step = OgnTutorialStatePy.step
        # Update the per-class state information for the next node
        OgnTutorialStatePy.step += 1

    def update_state(self):
        """Helper function to update the node's internal state based on the previous values and the per-class state"""
        self.increment_value += self.node_step


class OgnTutorialStatePy:
    """Use internal node state information in addition to inputs"""

    # This is a simplified bit of internal per-class state information. In real applications this would be a complex
    # structure, potentially keyed off of combinations of inputs or real time information.
    #
    # This value increases for each node and indicates the value at which a node's own internal state value increments.
    # e.g. the first instance of this node type will increment its state value by 1, the second instance of it by 2,
    # and so on...
    step = 1

    # Defining this method, in conjunction with adding the "state" section in the .ogn file, tells OmniGraph that you
    # intend to maintain opaque internal state information on your node. OmniGraph will ensure that your node is not
    # scheduled for evaluation in such a way that it would compromise the thread-safety of your node due to this state
    # information, however you are responsible for updating the values and/or maintaining your own dirty bits when
    # required.
    @staticmethod
    def internal_state():
        """Returns an object that will contain per-node state information"""
        return OgnTutorialStatePyInternalState()

    @staticmethod
    def compute(db) -> bool:
        """Compute the output based on inputs and internal state"""

        # This illustrates how internal state and inputs can be used in conjunction. The inputs can be used
        # to divert to a different computation path.
        if db.inputs.override:
            db.outputs.monotonic = db.inputs.overrideValue
        else:
            # OmniGraph ensures that the database contains the correct internal state information for the node
            # being evaluated. Beyond that it has no knowledge of the data within that state.
            db.outputs.monotonic = db.internal_state.increment_value

            # Update the node's internal state data for the next evaluation.
            db.internal_state.update_state()

        return True
