/***

  Olive - Non-Linear Video Editor
  Copyright (C) 2022 Olive Team

  This program is free software: you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation, either version 3 of the License, or
  (at your option) any later version.

  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with this program.  If not, see <http://www.gnu.org/licenses/>.

***/

#ifndef NODE_H
#define NODE_H

#include <map>
#include <QMutex>
#include <QObject>
#include <QPainter>
#include <QPointF>
#include <QXmlStreamWriter>

#include "codec/frame.h"
#include "common/xmlutils.h"
#include "node/gizmo/draggable.h"
#include "node/globals.h"
#include "node/keyframe.h"
#include "node/inputimmediate.h"
#include "node/param.h"
#include "render/audioplaybackcache.h"
#include "render/audiowaveformcache.h"
#include "render/framehashcache.h"
#include "render/job/generatejob.h"
#include "render/job/samplejob.h"
#include "render/job/shaderjob.h"
#include "render/shadercode.h"
#include "splitvalue.h"

namespace olive {

#define NODE_DEFAULT_FUNCTIONS(x) \
  NODE_DEFAULT_DESTRUCTOR(x) \
  NODE_COPY_FUNCTION(x)

#define NODE_DEFAULT_DESTRUCTOR(x) \
  virtual ~x() override {DisconnectAll();}

#define NODE_COPY_FUNCTION(x) \
  virtual Node *copy() const override {return new x();}

class Folder;
class Project;
struct SerializedData;

/**
 * @brief A single processing unit that can be connected with others to create intricate processing systems
 *
 * A cornerstone of "visual programming", a node is a single "function" that takes input and returns an output that can
 * be connected to other nodes. Inputs can be either user-set or retrieved from the output of another node. By joining
 * several nodes together, intricate, highly customizable, and infinitely extensible systems can be made for processing
 * data. It can also all be exposed to the user without forcing them to write code or compile anything.
 *
 * A major example in Olive is the entire rendering workflow. To render a frame, Olive will work through a node graph
 * that can be infinitely customized by the user to create images.
 *
 * This is a simple base class designed to contain all the functionality for this kind of processing connective unit.
 * It is an abstract class intended to be subclassed to create nodes with actual functionality.
 */
class Node : public QObject
{
  Q_OBJECT
public:
  enum CategoryID {
    kCategoryUnknown = -1,

    kCategoryOutput,
    kCategoryGenerator,
    kCategoryMath,
    kCategoryKeying,
    kCategoryFilter,
    kCategoryColor,
    kCategoryTime,
    kCategoryTimeline,
    kCategoryTransition,
    kCategoryDistort,
    kCategoryProject,

    kCategoryCount
  };

  enum Flag {
    kNone = 0,
    kDontShowInParamView = 0x1,
    kVideoEffect = 0x2,
    kAudioEffect = 0x4,
    kDontShowInCreateMenu = 0x8,
    kIsItem = 0x10
  };

  struct ContextPair {
    Node *node;
    Node *context;
  };

  Node();

  virtual ~Node() override;

  /**
   * @brief Creates a clone of the Node
   *
   * By default, the clone will NOT have the values and connections of the original node. The caller is responsible for
   * copying that data with functions like CopyInputs() as copies may be done for different reasons.
   */
  virtual Node* copy() const = 0;

  /**
   * @brief Convenience function - assumes parent is a NodeGraph
   */
  Project *parent() const;

  Project* project() const;

  const uint64_t &GetFlags() const
  {
    return flags_;
  }

  /**
   * @brief Return the name of the node
   *
   * This is the node's name shown to the user. This must be overridden by subclasses, and preferably run through the
   * translator.
   */
  virtual QString Name() const = 0;

  /**
   * @brief Returns a shortened name of this node if applicable
   *
   * Defaults to returning Name() but can be overridden.
   */
  virtual QString ShortName() const;

  /**
   * @brief Return the unique identifier of the node
   *
   * This is used in save files and any other times a specific node must be picked out at runtime. This must be an ID
   * completely unique to this node, and preferably in bundle identifier format (e.g. "org.company.Name"). This string
   * should NOT be translated.
   */
  virtual QString id() const = 0;

  /**
   * @brief Return the category this node is in (optional for subclassing, but recommended)
   *
   * In any organized node menus, show the node in this category. If this node should be in a subfolder of a subfolder,
   * use a "/" to separate categories (e.g. "Distort/Noise"). The string should not start with a "/" as this will be
   * interpreted as an empty string category. This value should be run through a translator as its largely user
   * oriented.
   */
  virtual QVector<CategoryID> Category() const = 0;

  /**
   * @brief Return a description of this node's purpose (optional for subclassing, but recommended)
   *
   * A short (1-2 sentence) description of what this node should do to help the user understand its purpose. This should
   * be run through a translator.
   */
  virtual QString Description() const;

  Folder* folder() const
  {
    return folder_;
  }

  bool IsItem() const { return flags_ & kIsItem; }

  /**
   * @brief Function called to retranslate parameter names (should be overridden in derivatives)
   */
  virtual void Retranslate();

  enum DataType
  {
    ICON,
    DURATION,
    CREATED_TIME,
    MODIFIED_TIME,
    FREQUENCY_RATE,
    TOOLTIP
  };

  virtual QVariant data(const DataType &d) const;

  const QVector<QString>& inputs() const
  {
    return input_ids_;
  }

  virtual QVector<QString> IgnoreInputsForRendering() const
  {
    return QVector<QString>();
  }

  class ActiveElements
  {
  public:
    enum Mode {
      kAllElements,
      kSpecified,
      kNoElements
    };

    ActiveElements(Mode m = kAllElements)
    {
      mode_ = m;
    }

    Mode mode() const { return mode_; }
    std::list<int> elements() const { return elements_; }

    void add(int e)
    {
      elements_.push_back(e);
      mode_ = kSpecified;
    }

  private:
    Mode mode_;
    std::list<int> elements_;

  };

  virtual ActiveElements GetActiveElementsAtTime(const QString &input, const TimeRange &r) const
  {
    return ActiveElements::kAllElements;
  }

  bool HasInputWithID(const QString& id) const
  {
    return input_ids_.contains(id);
  }

  bool HasParamWithID(const QString& id) const
  {
    return HasInputWithID(id);
  }

  FrameHashCache* video_frame_cache() const
  {
    return video_cache_;
  }

  ThumbnailCache* thumbnail_cache() const
  {
    return thumbnail_cache_;
  }

  AudioPlaybackCache* audio_playback_cache() const
  {
    return audio_cache_;
  }

  AudioWaveformCache* waveform_cache() const
  {
    return waveform_cache_;
  }

  virtual TimeRange GetVideoCacheRange() const { return TimeRange(); }
  virtual TimeRange GetAudioCacheRange() const { return TimeRange(); }

  struct Position
  {
    Position(const QPointF &p = QPointF(0, 0), bool e = false)
    {
      position = p;
      expanded = e;
    }

    bool load(QXmlStreamReader *reader);
    void save(QXmlStreamWriter *writer) const;

    QPointF position;
    bool expanded;

    inline Position &operator+=(const Position &p)
    {
      position += p.position;
      return *this;
    }

    inline Position &operator-=(const Position &p)
    {
      position -= p.position;
      return *this;
    }

    friend inline const Position operator+(Position a, const Position &b)
    {
      a += b;
      return a;
    }

    friend inline const Position operator-(Position a, const Position &b)
    {
      a -= b;
      return a;
    }
  };

  using PositionMap = QHash<Node*, Position>;
  const PositionMap &GetContextPositions() const
  {
    return context_positions_;
  }

  bool IsNodeExpandedInContext(Node *node) const
  {
    return context_positions_.value(node).expanded;
  }

  bool ContextContainsNode(Node *node) const
  {
    return context_positions_.contains(node);
  }

  Position GetNodePositionDataInContext(Node *node)
  {
    return context_positions_.value(node);
  }

  QPointF GetNodePositionInContext(Node *node)
  {
    return GetNodePositionDataInContext(node).position;
  }

  bool SetNodePositionInContext(Node *node, const QPointF &pos);

  bool SetNodePositionInContext(Node *node, const Position &pos);

  void SetNodeExpandedInContext(Node *node, bool e)
  {
    context_positions_[node].expanded = e;
  }

  bool RemoveNodeFromContext(Node *node);

  /**
   * @brief Retrieve the color of this node
   */
  Color color() const;

  /**
   * @brief Same as color() but return a pretty gradient version
   */
  QLinearGradient gradient_color(qreal top, qreal bottom) const;

  /**
   * @brief Uses config and returns either color() for flat shading or gradient for gradient
   */
  QBrush brush(qreal top, qreal bottom) const;

  int GetOverrideColor() const
  {
    return override_color_;
  }

  /**
   * @brief Sets the override color. Set to -1 for no override color.
   */
  void SetOverrideColor(int index)
  {
    if (override_color_ != index) {
      override_color_ = index;
      emit ColorChanged();
    }
  }

  static void ConnectEdge(Node *output, const NodeInput& input);

  static void DisconnectEdge(Node *output, const NodeInput& input);

  void CopyCacheUuidsFrom(Node *n);

  bool AreCachesEnabled() const { return caches_enabled_; }
  void SetCachesEnabled(bool e) { caches_enabled_ = e; }

  virtual QString GetInputName(const QString& id) const;

  void SetInputName(const QString& id, const QString& name);

  bool IsInputHidden(const QString& input) const;
  bool IsInputConnectable(const QString& input) const;
  bool IsInputKeyframable(const QString& input) const;

  bool IsInputKeyframing(const QString& input, int element = -1) const;
  bool IsInputKeyframing(const NodeInput& input) const
  {
    return IsInputKeyframing(input.input(), input.element());
  }

  void SetInputIsKeyframing(const QString& input, bool e, int element = -1);
  void SetInputIsKeyframing(const NodeInput& input, bool e)
  {
    SetInputIsKeyframing(input.input(), e, input.element());
  }

  bool IsInputConnected(const QString& input, int element = -1) const;
  bool IsInputConnected(const NodeInput& input) const
  {
    return IsInputConnected(input.input(), input.element());
  }

  virtual bool IsInputConnectedForRender(const QString& input, int element = -1) const
  {
    return IsInputConnected(input, element);
  }
  bool IsInputConnectedForRender(const NodeInput& input) const
  {
    return IsInputConnectedForRender(input.input(), input.element());
  }

  bool IsInputStatic(const QString& input, int element = -1) const
  {
    return !IsInputConnected(input, element) && !IsInputKeyframing(input, element);
  }

  bool IsInputStatic(const NodeInput& input) const
  {
    return IsInputStatic(input.input(), input.element());
  }

  Node *GetConnectedOutput(const QString& input, int element = -1) const;

  Node *GetConnectedOutput(const NodeInput& input) const
  {
    return GetConnectedOutput(input.input(), input.element());
  }

  virtual Node *GetConnectedRenderOutput(const QString& input, int element = -1) const
  {
    return GetConnectedOutput(input, element);
  }

  Node *GetConnectedRenderOutput(const NodeInput& input) const
  {
    return GetConnectedRenderOutput(input.input(), input.element());
  }

  bool IsUsingStandardValue(const QString& input, int track, int element = -1) const;

  NodeValue::Type GetInputDataType(const QString& id) const;
  void SetInputDataType(const QString& id, const NodeValue::Type& type);

  bool HasInputProperty(const QString& id, const QString& name) const;
  QHash<QString, QVariant> GetInputProperties(const QString& id) const;
  QVariant GetInputProperty(const QString& id, const QString& name) const;
  void SetInputProperty(const QString& id, const QString& name, const QVariant& value);

  QVariant GetValueAtTime(const QString& input, const rational& time, int element = -1) const
  {
    NodeValue::Type type = GetInputDataType(input);

    return NodeValue::combine_track_values_into_normal_value(type, GetSplitValueAtTime(input, time, element));
  }

  QVariant GetValueAtTime(const NodeInput& input, const rational& time)
  {
    return GetValueAtTime(input.input(), time, input.element());
  }

  SplitValue GetSplitValueAtTime(const QString& input, const rational& time, int element = -1) const;

  SplitValue GetSplitValueAtTime(const NodeInput& input, const rational& time)
  {
    return GetSplitValueAtTime(input.input(), time, input.element());
  }

  QVariant GetSplitValueAtTimeOnTrack(const QString& input, const rational& time, int track, int element = -1) const;
  QVariant GetSplitValueAtTimeOnTrack(const NodeInput& input, const rational& time, int track) const
  {
    return GetSplitValueAtTimeOnTrack(input.input(), time, track, input.element());
  }

  QVariant GetSplitValueAtTimeOnTrack(const NodeKeyframeTrackReference& input, const rational& time) const
  {
    return GetSplitValueAtTimeOnTrack(input.input(), time, input.track());
  }

  QVariant GetDefaultValue(const QString& input) const;
  SplitValue GetSplitDefaultValue(const QString& input) const;
  QVariant GetSplitDefaultValueOnTrack(const QString& input, int track) const;

  void SetDefaultValue(const QString& input, const QVariant &val);
  void SetSplitDefaultValue(const QString& input, const SplitValue &val);
  void SetSplitDefaultValueOnTrack(const QString& input, const QVariant &val, int track);

  const QVector<NodeKeyframeTrack>& GetKeyframeTracks(const QString& input, int element) const;
  const QVector<NodeKeyframeTrack>& GetKeyframeTracks(const NodeInput& input) const
  {
    return GetKeyframeTracks(input.input(), input.element());
  }

  QVector<NodeKeyframe*> GetKeyframesAtTime(const QString& input, const rational& time, int element = -1) const;
  QVector<NodeKeyframe*> GetKeyframesAtTime(const NodeInput& input, const rational& time) const
  {
    return GetKeyframesAtTime(input.input(), time, input.element());
  }

  NodeKeyframe* GetKeyframeAtTimeOnTrack(const QString& input, const rational& time, int track, int element = -1) const;
  NodeKeyframe* GetKeyframeAtTimeOnTrack(const NodeInput& input, const rational& time, int track) const
  {
    return GetKeyframeAtTimeOnTrack(input.input(), time, track, input.element());
  }

  NodeKeyframe* GetKeyframeAtTimeOnTrack(const NodeKeyframeTrackReference& input, const rational& time) const
  {
    return GetKeyframeAtTimeOnTrack(input.input(), time, input.track());
  }

  NodeKeyframe::Type GetBestKeyframeTypeForTimeOnTrack(const QString& input, const rational& time, int track, int element = -1) const;

  NodeKeyframe::Type GetBestKeyframeTypeForTimeOnTrack(const NodeInput& input, const rational& time, int track) const
  {
    return GetBestKeyframeTypeForTimeOnTrack(input.input(), time, track, input.element());
  }

  NodeKeyframe::Type GetBestKeyframeTypeForTimeOnTrack(const NodeKeyframeTrackReference& input, const rational& time) const
  {
    return GetBestKeyframeTypeForTimeOnTrack(input.input(), time, input.track());
  }

  int GetNumberOfKeyframeTracks(const QString& id) const;
  int GetNumberOfKeyframeTracks(const NodeInput& id) const
  {
    return GetNumberOfKeyframeTracks(id.input());
  }

  NodeKeyframe* GetEarliestKeyframe(const QString& id, int element = -1) const;
  NodeKeyframe* GetEarliestKeyframe(const NodeInput& id) const
  {
    return GetEarliestKeyframe(id.input(), id.element());
  }

  NodeKeyframe* GetLatestKeyframe(const QString& id, int element = -1) const;
  NodeKeyframe* GetLatestKeyframe(const NodeInput& id) const
  {
    return GetLatestKeyframe(id.input(), id.element());
  }

  NodeKeyframe* GetClosestKeyframeBeforeTime(const QString& id, const rational& time, int element = -1) const;
  NodeKeyframe* GetClosestKeyframeBeforeTime(const NodeInput& id, const rational& time) const
  {
    return GetClosestKeyframeBeforeTime(id.input(), time, id.element());
  }

  NodeKeyframe* GetClosestKeyframeAfterTime(const QString& id, const rational& time, int element = -1) const;
  NodeKeyframe* GetClosestKeyframeAfterTime(const NodeInput& id, const rational& time) const
  {
    return GetClosestKeyframeAfterTime(id.input(), time, id.element());
  }

  bool HasKeyframeAtTime(const QString& id, const rational& time, int element = -1) const;
  bool HasKeyframeAtTime(const NodeInput& id, const rational& time) const
  {
    return HasKeyframeAtTime(id.input(), time, id.element());
  }

  QStringList GetComboBoxStrings(const QString& id) const;

  QVariant GetStandardValue(const QString& id, int element = -1) const;
  QVariant GetStandardValue(const NodeInput& id) const
  {
    return GetStandardValue(id.input(), id.element());
  }

  SplitValue GetSplitStandardValue(const QString& id, int element = -1) const;
  SplitValue GetSplitStandardValue(const NodeInput& id) const
  {
    return GetSplitStandardValue(id.input(), id.element());
  }

  QVariant GetSplitStandardValueOnTrack(const QString& input, int track, int element = -1) const;
  QVariant GetSplitStandardValueOnTrack(const NodeKeyframeTrackReference& id) const
  {
    return GetSplitStandardValueOnTrack(id.input().input(), id.track(), id.input().element());
  }

  void SetStandardValue(const QString& id, const QVariant& value, int element = -1);
  void SetStandardValue(const NodeInput& id, const QVariant& value)
  {
    SetStandardValue(id.input(), value, id.element());
  }

  void SetSplitStandardValue(const QString& id, const SplitValue& value, int element = -1);
  void SetSplitStandardValue(const NodeInput& id, const SplitValue& value)
  {
    SetSplitStandardValue(id.input(), value, id.element());
  }

  void SetSplitStandardValueOnTrack(const QString& id, int track, const QVariant& value, int element = -1);
  void SetSplitStandardValueOnTrack(const NodeKeyframeTrackReference& id, const QVariant& value)
  {
    SetSplitStandardValueOnTrack(id.input().input(), id.track(), value, id.input().element());
  }

  bool InputIsArray(const QString& id) const;

  void InputArrayInsert(const QString& id, int index);
  void InputArrayResize(const QString& id, int size);
  void InputArrayRemove(const QString& id, int index);

  void InputArrayAppend(const QString& id)
  {
    InputArrayResize(id, InputArraySize(id) + 1);
  }

  void InputArrayPrepend(const QString& id)
  {
    InputArrayInsert(id, 0);
  }

  void InputArrayRemoveLast(const QString& id)
  {
    InputArrayResize(id, InputArraySize(id) - 1);
  }

  int InputArraySize(const QString& id) const;

  NodeInputImmediate* GetImmediate(const QString& input, int element) const;

  NodeInput GetEffectInput()
  {
    return effect_input_.isEmpty() ? NodeInput() : NodeInput(this, effect_input_);
  }

  const QString &GetEffectInputID() const
  {
    return effect_input_;
  }

  class ValueHint {
  public:
    explicit ValueHint(const QVector<NodeValue::Type> &types = QVector<NodeValue::Type>(), int index = -1, const QString &tag = QString()) :
      type_(types),
      index_(index),
      tag_(tag)
    {
    }

    explicit ValueHint(const QVector<NodeValue::Type> &types, const QString &tag) :
      type_(types),
      index_(-1),
      tag_(tag)
    {
    }

    explicit ValueHint(int index) :
      index_(index)
    {
    }

    explicit ValueHint(const QString &tag) :
      index_(-1),
      tag_(tag)
    {
    }

    const QVector<NodeValue::Type> &types() const { return type_; }
    const int &index() const { return index_; }
    const QString& tag() const { return tag_; }

    void set_type(const QVector<NodeValue::Type> &type) { type_ = type; }
    void set_index(const int &index) { index_ = index; }
    void set_tag(const QString &tag) { tag_ = tag; }

    bool load(QXmlStreamReader *reader);
    void save(QXmlStreamWriter *writer) const;

  private:
    QVector<NodeValue::Type> type_;
    int index_;
    QString tag_;

  };

  const QMap<InputElementPair, ValueHint> &GetValueHints() const
  {
    return value_hints_;
  }

  ValueHint GetValueHintForInput(const QString &input, int element = -1) const
  {
    return value_hints_.value({input, element});
  }

  void SetValueHintForInput(const QString &input, const ValueHint &hint, int element = -1);

  const NodeKeyframeTrack& GetTrackFromKeyframe(NodeKeyframe* key) const;

  using InputConnections = std::map<NodeInput, Node*>;

  /**
   * @brief Return map of input connections
   *
   * Inputs can only have one connection, so the key is the input connected and the value is the
   * output that it's connected to.
   */
  const InputConnections& input_connections() const
  {
    return input_connections_;
  }

  using OutputConnection = std::pair<Node*, NodeInput>;
  using OutputConnections = std::vector<OutputConnection>;

  /**
   * @brief Return list of output connections
   *
   * An output can connect an infinite amount of inputs, so in this map, the key is the output and
   * the value is a vector of inputs.
   */
  const OutputConnections& output_connections() const
  {
    return output_connections_;
  }

  /**
   * @brief Return a list of all Nodes that this Node's inputs are connected to (does not include this Node)
   */
  QVector<Node *> GetDependencies() const;

  /**
   * @brief Returns a list of Nodes that this Node is dependent on, provided no other Nodes are dependent on them
   * outside of this hierarchy.
   *
   * Similar to GetDependencies(), but excludes any Nodes that are used outside the dependency graph of this Node.
   */
  QVector<Node *> GetExclusiveDependencies() const;

  /**
   * @brief Retrieve immediate dependencies (only nodes that are directly connected to the inputs of this one)
   */
  QVector<Node *> GetImmediateDependencies() const;

  struct ShaderRequest
  {
    ShaderRequest(const QString &shader_id)
    {
      id = shader_id;
    }

    ShaderRequest(const QString &shader_id, const QString &shader_stub)
    {
      id = shader_id;
      stub = shader_stub;
    }

    QString id;
    QString stub;
  };

  /**
   * @brief Generate hardware accelerated code for this Node
   */
  virtual ShaderCode GetShaderCode(const ShaderRequest &request) const;

  /**
   * @brief If Value() pushes a ShaderJob, this is the function that will process them.
   */
  virtual void ProcessSamples(const NodeValueRow &values, const SampleBuffer &input, SampleBuffer &output, int index) const;

  /**
   * @brief If Value() pushes a GenerateJob, override this function for the image to create
   *
   * @param frame
   *
   * The destination buffer. It will already be allocated and ready for writing to.
   */
  virtual void GenerateFrame(FramePtr frame, const GenerateJob &job) const;

  /**
   * @brief Returns whether this node ever receives an input from a particular node instance
   */
  bool InputsFrom(Node* n, bool recursively) const;

  /**
   * @brief Returns whether this node ever receives an input from a node with a particular ID
   */
  bool InputsFrom(const QString& id, bool recursively) const;

  /**
   * @brief Find inputs that `output` outputs to in order to arrive at this node
   *
   * Traverse this node's inputs recursively looking for `output`, and return a list of
   * edges that `output` uses to get to `this` node.
   */
  QVector<NodeInput> FindWaysNodeArrivesHere(const Node *output) const;

  /**
   * @brief Severs all input and output connections
   */
  void DisconnectAll();

  /**
   * @brief Get the human-readable name for any category
   */
  static QString GetCategoryName(const CategoryID &c);

  enum TransformTimeDirection {
    kTransformTowardsInput,
    kTransformTowardsOutput
  };

  /**
   * @brief Transforms time from this node through the connections it takes to get to the specified node
   */
  TimeRange TransformTimeTo(TimeRange time, Node* target, TransformTimeDirection dir, int path_index);

  /**
   * @brief Find nodes of a certain type that this Node takes inputs from
   */
  template<class T>
  QVector<T*> FindInputNodes(int maximum = 0) const;

  /**
   * @brief Find nodes of a certain type that this Node takes inputs from
   */
  template<class T>
  static QVector<T*> FindInputNodesConnectedToInput(const NodeInput &input, int maximum = 0);

  using InvalidateCacheOptions = QHash<QString, QVariant>;

  /**
   * @brief Signal all dependent Nodes that anything cached between start_range and end_range is now invalid and
   *        requires re-rendering
   *
   * Override this if your Node subclass keeps a cache, but call this base function at the end of the subclass function.
   * Default behavior is to relay this signal to all connected outputs, which will need to be done as to not break
   * the DAG. Even if the time needs to be transformed somehow (e.g. converting media time to sequence time), you can
   * call this function with transformed time and relay the signal that way.
   */
  virtual void InvalidateCache(const TimeRange& range, const QString& from, int element = -1, InvalidateCacheOptions options = InvalidateCacheOptions());

  void InvalidateCache(const TimeRange& range, const NodeInput& from, const InvalidateCacheOptions &options = InvalidateCacheOptions())
  {
    InvalidateCache(range, from.input(), from.element(), options);
  }

  /**
   * @brief Adjusts time that should be sent to nodes connected to certain inputs.
   *
   * If this node modifies the `time` (i.e. a clip converting sequence time to media time), this function should be
   * overridden to do so. Also make sure to override OutputTimeAdjustment() to provide the inverse function.
   */
  virtual TimeRange InputTimeAdjustment(const QString& input, int element, const TimeRange& input_time, bool clamp) const;

  /**
   * @brief The inverse of InputTimeAdjustment()
   */
  virtual TimeRange OutputTimeAdjustment(const QString& input, int element, const TimeRange& input_time) const;

  /**
   * @brief Copies inputs from from Node to another including connections
   *
   * Nodes must be of the same types (i.e. have the same ID)
   */
  static void CopyInputs(const Node *source, Node* destination, bool include_connections = true, MultiUndoCommand *command = nullptr);

  static void CopyInput(const Node *src, Node* dst, const QString& input, bool include_connections, bool traverse_arrays, MultiUndoCommand *command);

  static void CopyValuesOfElement(const Node* src, Node* dst, const QString& input, int src_element, int dst_element, MultiUndoCommand *command = nullptr);
  static void CopyValuesOfElement(const Node* src, Node* dst, const QString& input, int element, MultiUndoCommand *command = nullptr)
  {
    return CopyValuesOfElement(src, dst, input, element, element, command);
  }

  /**
   * @brief Clones a set of nodes and connects the new ones the way the old ones were
   */
  static QVector<Node*> CopyDependencyGraph(const QVector<Node*>& nodes, MultiUndoCommand *command);
  static void CopyDependencyGraph(const QVector<Node*>& src, const QVector<Node*>& dst, MultiUndoCommand *command);

  static Node* CopyNodeAndDependencyGraphMinusItems(Node* node, MultiUndoCommand* command);

  static Node* CopyNodeInGraph(Node *node, MultiUndoCommand* command);

  /**
   * @brief The main processing function
   *
   * The node's main purpose is to take values from inputs to set values in outputs. For whatever subclass node you
   * create, this is where the code for that goes.
   *
   * Note that as a video editor, the node graph has to work across time. Depending on the purpose of your node, it may
   * output different values depending on the time, and even if not, it will likely be receiving different input
   * depending on the time. Most of the difficult work here is handled by NodeInput::get_value() which you should pass
   * the `time` parameter to. It will return its value (at that time, if it's keyframed), or pass the time to a
   * corresponding output if it's connected to one. If your node doesn't directly deal with time, the default behavior
   * of the NodeParam objects will handle everything related to it automatically.
   */
  virtual void Value(const NodeValueRow& value, const NodeGlobals &globals, NodeValueTable *table) const;

  bool HasGizmos() const
  {
    return !gizmos_.isEmpty();
  }

  const QVector<NodeGizmo*> &GetGizmos() const
  {
    return gizmos_;
  }

  virtual QTransform GizmoTransformation(const NodeValueRow &row, const NodeGlobals &globals) const { return QTransform(); }

  virtual void UpdateGizmoPositions(const NodeValueRow &row, const NodeGlobals &globals){}

  const QString& GetLabel() const;
  void SetLabel(const QString& s);

  QString GetLabelAndName() const;
  QString GetLabelOrName() const;

  void InvalidateAll(const QString& input, int element = -1);

  bool HasLinks() const
  {
    return !links_.isEmpty();
  }

  const QVector<Node*>& links() const
  {
    return links_;
  }

  static bool Link(Node* a, Node* b);
  static bool Unlink(Node* a, Node* b);
  static bool AreLinked(Node* a, Node* b);

  bool Load(QXmlStreamReader *reader, SerializedData *data);
  void Save(QXmlStreamWriter *writer) const;

  virtual bool LoadCustom(QXmlStreamReader *reader, SerializedData *data);
  virtual void SaveCustom(QXmlStreamWriter *writer) const {}
  virtual void PostLoadEvent(SerializedData *data);

  bool LoadInput(QXmlStreamReader *reader, SerializedData *data);
  void SaveInput(QXmlStreamWriter *writer, const QString &id) const;

  bool LoadImmediate(QXmlStreamReader *reader, const QString &input, int element, SerializedData *data);
  void SaveImmediate(QXmlStreamWriter *writer, const QString &input, int element) const;

  void SetFolder(Folder* folder)
  {
    folder_ = folder;
  }

  InputFlags GetInputFlags(const QString& input) const;
  void SetInputFlag(const QString &input, InputFlag f, bool on = true);

  virtual void LoadFinishedEvent(){}
  virtual void ConnectedToPreviewEvent(){}

  static void SetValueAtTime(const NodeInput &input, const rational &time, const QVariant &value, int track, MultiUndoCommand *command, bool insert_on_all_tracks_if_no_key);

  /**
   * @brief Find path starting at `from` that outputs to arrive at `to`
   */
  static std::list<NodeInput> FindPath(Node *from, Node *to, int path_index);

  void ArrayResizeInternal(const QString& id, int size);

  virtual void AddedToGraphEvent(Project *p){}
  virtual void RemovedFromGraphEvent(Project *p){}

  static QString GetConnectCommandString(Node *output, const NodeInput &input);
  static QString GetDisconnectCommandString(Node *output, const NodeInput &input);

  static const QString kEnabledInput;

protected:
  void InsertInput(const QString& id, NodeValue::Type type, const QVariant& default_value, InputFlags flags, int index);

  void PrependInput(const QString& id, NodeValue::Type type, const QVariant& default_value, InputFlags flags = InputFlags(kInputFlagNormal))
  {
    InsertInput(id, type, default_value, flags, 0);
  }

  void PrependInput(const QString& id, NodeValue::Type type, InputFlags flags = InputFlags(kInputFlagNormal))
  {
    PrependInput(id, type, QVariant(), flags);
  }

  void AddInput(const QString& id, NodeValue::Type type, const QVariant& default_value, InputFlags flags = InputFlags(kInputFlagNormal))
  {
    InsertInput(id, type, default_value, flags, input_ids_.size());
  }

  void AddInput(const QString& id, NodeValue::Type type, InputFlags flags = InputFlags(kInputFlagNormal))
  {
    AddInput(id, type, QVariant(), flags);
  }

  void RemoveInput(const QString& id);

  void SetComboBoxStrings(const QString& id, const QStringList& strings)
  {
    SetInputProperty(id, QStringLiteral("combo_str"), strings);
  }

  void SendInvalidateCache(const TimeRange &range, const InvalidateCacheOptions &options);

  enum GizmoScaleHandles {
    kGizmoScaleTopLeft,
    kGizmoScaleTopCenter,
    kGizmoScaleTopRight,
    kGizmoScaleBottomLeft,
    kGizmoScaleBottomCenter,
    kGizmoScaleBottomRight,
    kGizmoScaleCenterLeft,
    kGizmoScaleCenterRight,
    kGizmoScaleCount,
  };

  virtual void LinkChangeEvent(){}

  virtual void InputValueChangedEvent(const QString& input, int element);

  virtual void InputConnectedEvent(const QString& input, int element, Node *output);

  virtual void InputDisconnectedEvent(const QString& input, int element, Node *output);

  virtual void OutputConnectedEvent(const NodeInput& input);

  virtual void OutputDisconnectedEvent(const NodeInput& input);

  virtual void childEvent(QChildEvent *event) override;

  void SetEffectInput(const QString &input)
  {
    effect_input_ = input;
  }

  void SetFlag(Flag f, bool on = true)
  {
    if (on) {
      flags_ |= f;
    } else {
      flags_ &= ~f;
    }
  }

  template<typename T>
  T *AddDraggableGizmo(const QVector<NodeKeyframeTrackReference> &inputs = QVector<NodeKeyframeTrackReference>(), DraggableGizmo::DragValueBehavior behavior = DraggableGizmo::kDeltaFromStart)
  {
    T *gizmo = new T(this);
    gizmo->SetDragValueBehavior(behavior);
    foreach (const NodeKeyframeTrackReference &input, inputs) {
      gizmo->AddInput(input);
    }
    connect(gizmo, &DraggableGizmo::HandleStart, this, &Node::GizmoDragStart);
    connect(gizmo, &DraggableGizmo::HandleMovement, this, &Node::GizmoDragMove);
    return gizmo;
  }

  template<typename T>
  T *AddDraggableGizmo(const QStringList &inputs, DraggableGizmo::DragValueBehavior behavior = DraggableGizmo::kDeltaFromStart)
  {
    QVector<NodeKeyframeTrackReference> refs(inputs.size());
    for (int i=0; i<refs.size(); i++) {
      refs[i] = NodeInput(this, inputs[i]);
    }
    return AddDraggableGizmo<T>(refs, behavior);
  }

protected slots:
  virtual void GizmoDragStart(const olive::NodeValueRow &row, double x, double y, const olive::core::rational &time){}

  virtual void GizmoDragMove(double x, double y, const Qt::KeyboardModifiers &modifiers){}

signals:
  /**
   * @brief Signal emitted when SetLabel() is called
   */
  void LabelChanged(const QString& s);

  void ColorChanged();

  void ValueChanged(const NodeInput& input, const TimeRange& range);

  void InputConnected(Node *output, const NodeInput& input);

  void InputDisconnected(Node *output, const NodeInput& input);

  void OutputConnected(Node *output, const NodeInput& input);

  void OutputDisconnected(Node *output, const NodeInput& input);

  void InputValueHintChanged(const NodeInput& input);

  void InputPropertyChanged(const QString& input, const QString& key, const QVariant& value);

  void LinksChanged();

  void InputArraySizeChanged(const QString& input, int old_size, int new_size);

  void KeyframeAdded(NodeKeyframe* key);

  void KeyframeRemoved(NodeKeyframe* key);

  void KeyframeTimeChanged(NodeKeyframe* key);

  void KeyframeTypeChanged(NodeKeyframe* key);

  void KeyframeValueChanged(NodeKeyframe* key);

  void KeyframeEnableChanged(const NodeInput& input, bool enabled);

  void InputAdded(const QString& id);

  void InputRemoved(const QString& id);

  void InputNameChanged(const QString& id, const QString& name);

  void InputDataTypeChanged(const QString& id, NodeValue::Type type);

  void AddedToGraph(Project* graph);

  void RemovedFromGraph(Project* graph);

  void NodeAddedToContext(Node *node);

  void NodePositionInContextChanged(Node *node, const QPointF &pos);

  void NodeRemovedFromContext(Node *node);

  void InputFlagsChanged(const QString &input, const InputFlags &flags);

private:
  struct Input {
    NodeValue::Type type;
    InputFlags flags;
    SplitValue default_value;
    QHash<QString, QVariant> properties;
    QString human_name;
    int array_size;
  };

  NodeInputImmediate* CreateImmediate(const QString& input);

  int GetInternalInputIndex(const QString& input) const
  {
    return input_ids_.indexOf(input);
  }

  Input* GetInternalInputData(const QString& input)
  {
    int i = GetInternalInputIndex(input);

    if (i == -1) {
      return nullptr;
    } else {
      return &input_data_[i];
    }
  }

  const Input* GetInternalInputData(const QString& input) const
  {
    int i = GetInternalInputIndex(input);

    if (i == -1) {
      return nullptr;
    } else {
      return &input_data_.at(i);
    }
  }

  void ReportInvalidInput(const char* attempted_action, const QString &id, int element) const;

  static Node *CopyNodeAndDependencyGraphMinusItemsInternal(QMap<Node *, Node *> &created, Node *node, MultiUndoCommand *command);

  /**
   * @brief Immediates aren't deleted, so the actual array size may be larger than ArraySize()
   */
  int GetInternalInputArraySize(const QString& input);

  /**
   * @brief Find nodes of a certain type that this Node takes inputs from
   */
  template<class T>
  static void FindInputNodesConnectedToInputInternal(const NodeInput &input, QVector<T *>& list, int maximum);

  template<class T>
  static void FindInputNodeInternal(const Node* n, QVector<T *>& list, int maximum);

  QVector<Node*> GetDependenciesInternal(bool traverse, bool exclusive_only) const;

  void ParameterValueChanged(const QString &input, int element, const olive::core::TimeRange &range);
  void ParameterValueChanged(const NodeInput& input, const olive::core::TimeRange &range)
  {
    ParameterValueChanged(input.input(), input.element(), range);
  }

  /**
   * @brief Intelligently determine how what time range is affected by a keyframe
   */
  TimeRange GetRangeAffectedByKeyframe(NodeKeyframe *key) const;

  /**
   * @brief Gets a time range between the previous and next keyframes of index
   */
  TimeRange GetRangeAroundIndex(const QString& input, int index, int track, int element) const;

  void ClearElement(const QString &input, int index);

  /**
   * @brief Custom user label for node
   */
  QString label_;

  /**
   * @brief -1 if the color should be based on the category, >=0 if the user has set a custom color
   */
  int override_color_;

  /**
   * @brief Nodes that are linked with this one
   */
  QVector<Node*> links_;

  QVector<QString> input_ids_;
  QVector<Input> input_data_;

  QMap<QString, NodeInputImmediate*> standard_immediates_;

  QMap<QString, QVector<NodeInputImmediate*> > array_immediates_;

  InputConnections input_connections_;

  OutputConnections output_connections_;

  Folder* folder_;

  QMap<InputElementPair, ValueHint> value_hints_;

  PositionMap context_positions_;

  uint64_t flags_;

  QVector<NodeGizmo*> gizmos_;

  QString effect_input_;

  FrameHashCache *video_cache_;
  ThumbnailCache *thumbnail_cache_;

  AudioPlaybackCache *audio_cache_;
  AudioWaveformCache *waveform_cache_;

  bool caches_enabled_;

private slots:
  /**
   * @brief Slot when a keyframe's time changes to keep the keyframes correctly sorted by time
   */
  void InvalidateFromKeyframeTimeChange();

  /**
   * @brief Slot when a keyframe's value changes to signal that the cache needs updating
   */
  void InvalidateFromKeyframeValueChange();

  /**
   * @brief Slot when a keyframe's type changes to signal that the cache needs updating
   */
  void InvalidateFromKeyframeTypeChanged();

  /**
   * @brief Slot when a keyframe's bezier in value changes to signal that the cache needs updating
   */
  void InvalidateFromKeyframeBezierInChange();

  /**
   * @brief Slot when a keyframe's bezier out value changes to signal that the cache needs updating
   */
  void InvalidateFromKeyframeBezierOutChange();

};

template<class T>
void Node::FindInputNodesConnectedToInputInternal(const NodeInput &input, QVector<T *> &list, int maximum)
{
  Node* edge = input.GetConnectedOutput();
  if (!edge) {
    return;
  }

  T* cast_test = dynamic_cast<T*>(edge);

  if (cast_test) {
    list.append(cast_test);
    if (maximum != 0 && list.size() == maximum) {
      return;
    }
  }

  FindInputNodeInternal<T>(edge, list, maximum);
}

template<class T>
QVector<T *> Node::FindInputNodesConnectedToInput(const NodeInput &input, int maximum)
{
  QVector<T *> list;

  FindInputNodesConnectedToInputInternal<T>(input, list, maximum);

  return list;
}

template<class T>
void Node::FindInputNodeInternal(const Node* n, QVector<T *> &list, int maximum)
{
  for (auto it=n->input_connections_.cbegin(); it!=n->input_connections_.cend(); it++) {
    FindInputNodesConnectedToInputInternal(it->first, list, maximum);
  }
}

template<class T>
QVector<T *> Node::FindInputNodes(int maximum) const
{
  QVector<T *> list;

  FindInputNodeInternal<T>(this, list, maximum);

  return list;
}

}

Q_DECLARE_METATYPE(olive::Node::ValueHint)

#endif // NODE_H
