/***

  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/>.

***/

#include "node.h"

#include <QApplication>
#include <QGuiApplication>
#include <QDebug>
#include <QFile>

#include "common/lerp.h"
#include "core.h"
#include "config/config.h"
#include "node/group/group.h"
#include "node/project/serializer/typeserializer.h"
#include "nodeundo.h"
#include "project.h"
#include "serializeddata.h"
#include "ui/colorcoding.h"
#include "ui/icons/icons.h"

namespace olive {

#define super QObject

const QString Node::kEnabledInput = QStringLiteral("enabled_in");

Node::Node() :
  override_color_(-1),
  folder_(nullptr),
  flags_(kNone),
  caches_enabled_(true)
{
  AddInput(kEnabledInput, NodeValue::kBoolean, true);

  video_cache_ = new FrameHashCache(this);
  thumbnail_cache_ = new ThumbnailCache(this);
  audio_cache_ = new AudioPlaybackCache(this);
  waveform_cache_ = new AudioWaveformCache(this);

  waveform_cache_->SetSavingEnabled(false);
}

Node::~Node()
{
  // Disconnect all edges
  DisconnectAll();

  // Remove self from anything while we're still a node rather than a base QObject
  setParent(nullptr);

  // Remove all immediates
  foreach (NodeInputImmediate* i, standard_immediates_) {
    delete i;
  }
  for (auto it=array_immediates_.cbegin(); it!=array_immediates_.cend(); it++) {
    foreach (NodeInputImmediate* i, it.value()) {
      delete i;
    }
  }
}

Project *Node::parent() const
{
  return static_cast<Project*>(QObject::parent());
}

Project *Node::project() const
{
  return Project::GetProjectFromObject(this);
}

QString Node::ShortName() const
{
  return Name();
}

QString Node::Description() const
{
  // Return an empty string by default
  return QString();
}

void Node::Retranslate()
{
  SetInputName(kEnabledInput, tr("Enabled"));
}

QVariant Node::data(const DataType &d) const
{
  if (d == ICON) {
    // Just a meaningless default icon to be used where necessary
    return icon::New;
  }

  return QVariant();
}

bool Node::SetNodePositionInContext(Node *node, const QPointF &pos)
{
  Position p = context_positions_.value(node);

  p.position = pos;

  return SetNodePositionInContext(node, p);
}

bool Node::SetNodePositionInContext(Node *node, const Position &pos)
{
  bool added = !ContextContainsNode(node);
  context_positions_.insert(node, pos);

  if (added) {
    emit NodeAddedToContext(node);
  }

  emit NodePositionInContextChanged(node, pos.position);

  return added;
}

bool Node::RemoveNodeFromContext(Node *node)
{
  if (ContextContainsNode(node)) {
    context_positions_.remove(node);
    emit NodeRemovedFromContext(node);
    return true;
  } else {
    return false;
  }
}

Color Node::color() const
{
  int c;

  if (override_color_ >= 0) {
    c = override_color_;
  } else {
    c = OLIVE_CONFIG_STR(QStringLiteral("CatColor%1").arg(this->Category().first())).toInt();
  }

  return ColorCoding::GetColor(c);
}

QLinearGradient Node::gradient_color(qreal top, qreal bottom) const
{
  QLinearGradient grad;

  grad.setStart(0, top);
  grad.setFinalStop(0, bottom);

  QColor c = QtUtils::toQColor(color());

  grad.setColorAt(0.0, c.lighter());
  grad.setColorAt(1.0, c);

  return grad;
}

QBrush Node::brush(qreal top, qreal bottom) const
{
  if (OLIVE_CONFIG("UseGradients").toBool()) {
    return gradient_color(top, bottom);
  } else {
    return QtUtils::toQColor(color());
  }
}

void Node::ConnectEdge(Node *output, const NodeInput &input)
{
  // Ensure graph is the same
  Q_ASSERT(input.node()->parent() == output->parent());

  // Ensure a connection isn't getting overwritten
  Q_ASSERT(input.node()->input_connections().find(input) == input.node()->input_connections().end());

  // Insert connection on both sides
  input.node()->input_connections_[input] = output;
  output->output_connections_.push_back(std::pair<Node*, NodeInput>({output, input}));

  // Call internal events
  input.node()->InputConnectedEvent(input.input(), input.element(), output);
  output->OutputConnectedEvent(input);

  // Emit signals
  emit input.node()->InputConnected(output, input);
  emit output->OutputConnected(output, input);

  // Invalidate all if this node isn't ignoring this input
  if (!(input.node()->GetInputFlags(input.input()) & kInputFlagIgnoreInvalidations)) {
    input.node()->InvalidateAll(input.input(), input.element());
  }
}

void Node::DisconnectEdge(Node *output, const NodeInput &input)
{
  // Ensure graph is the same
  Q_ASSERT(input.node()->parent() == output->parent());

  // Ensure connection exists
  Q_ASSERT(input.node()->input_connections().at(input) == output);

  // Remove connection from both sides
  InputConnections& inputs = input.node()->input_connections_;
  inputs.erase(inputs.find(input));

  OutputConnections& outputs = output->output_connections_;
  outputs.erase(std::find(outputs.begin(), outputs.end(), std::pair<Node*, NodeInput>({output, input})));

  // Call internal events
  input.node()->InputDisconnectedEvent(input.input(), input.element(), output);
  output->OutputDisconnectedEvent(input);

  emit input.node()->InputDisconnected(output, input);
  emit output->OutputDisconnected(output, input);

  if (!(input.node()->GetInputFlags(input.input()) & kInputFlagIgnoreInvalidations)) {
    input.node()->InvalidateAll(input.input(), input.element());
  }
}

void Node::CopyCacheUuidsFrom(Node *n)
{
  video_cache_->SetUuid(n->video_cache_->GetUuid());
  audio_cache_->SetUuid(n->audio_cache_->GetUuid());
  thumbnail_cache_->SetUuid(n->thumbnail_cache_->GetUuid());
  waveform_cache_->SetUuid(n->waveform_cache_->GetUuid());
}

QString Node::GetInputName(const QString &id) const
{
  const Input* i = GetInternalInputData(id);

  if (i) {
    return i->human_name;
  } else {
    ReportInvalidInput("get name of", id, -1);
    return QString();
  }
}

bool Node::IsInputHidden(const QString &input) const
{
  return (GetInputFlags(input) & kInputFlagHidden);
}

bool Node::IsInputConnectable(const QString &input) const
{
  return !(GetInputFlags(input) & kInputFlagNotConnectable);
}

bool Node::IsInputKeyframable(const QString &input) const
{
  return !(GetInputFlags(input) & kInputFlagNotKeyframable);
}

bool Node::IsInputKeyframing(const QString &input, int element) const
{
  NodeInputImmediate* imm = GetImmediate(input, element);

  if (imm) {
    return imm->is_keyframing();
  } else {
    ReportInvalidInput("get keyframing state of", input, element);
    return false;
  }
}

void Node::SetInputIsKeyframing(const QString &input, bool e, int element)
{
  if (!IsInputKeyframable(input)) {
    qDebug() << "Ignored set keyframing of" << input << "because this input is not keyframable";
    return;
  }

  NodeInputImmediate* imm = GetImmediate(input, element);

  if (imm) {
    imm->set_is_keyframing(e);

    emit KeyframeEnableChanged(NodeInput(this, input, element), e);
  } else {
    ReportInvalidInput("set keyframing state of", input, element);
  }
}

bool Node::IsInputConnected(const QString &input, int element) const
{
  return GetConnectedOutput(input, element);
}

Node *Node::GetConnectedOutput(const QString &input, int element) const
{
  for (auto it=input_connections_.cbegin(); it!=input_connections_.cend(); it++) {
    if (it->first.input() == input && it->first.element() == element) {
      return it->second;
    }
  }

  return nullptr;
}

bool Node::IsUsingStandardValue(const QString &input, int track, int element) const
{
  NodeInputImmediate* imm = GetImmediate(input, element);

  if (imm) {
    return imm->is_using_standard_value(track);
  } else {
    ReportInvalidInput("determine whether using standard value in", input, element);
    return true;
  }
}

NodeValue::Type Node::GetInputDataType(const QString &id) const
{
  const Input* i = GetInternalInputData(id);

  if (i) {
    return i->type;
  } else {
    ReportInvalidInput("get data type of", id, -1);
    return NodeValue::kNone;
  }
}

void Node::SetInputDataType(const QString &id, const NodeValue::Type &type)
{
  Input* input_meta = GetInternalInputData(id);

  if (input_meta) {
    input_meta->type = type;

    int array_sz = InputArraySize(id);
    for (int i=-1; i<array_sz; i++) {
      GetImmediate(id, i)->set_data_type(type);
    }

    emit InputDataTypeChanged(id, type);
  } else {
    ReportInvalidInput("set data type of", id, -1);
  }
}

bool Node::HasInputProperty(const QString &id, const QString &name) const
{
  const Input* i = GetInternalInputData(id);

  if (i) {
    return i->properties.contains(name);
  } else {
    ReportInvalidInput("get property of", id, -1);
    return false;
  }
}

QHash<QString, QVariant> Node::GetInputProperties(const QString &id) const
{
  const Input* i = GetInternalInputData(id);

  if (i) {
    return i->properties;
  } else {
    ReportInvalidInput("get property table of", id, -1);
    return QHash<QString, QVariant>();
  }
}

QVariant Node::GetInputProperty(const QString &id, const QString &name) const
{
  const Input* i = GetInternalInputData(id);

  if (i) {
    return i->properties.value(name);
  } else {
    ReportInvalidInput("get property of", id, -1);
    return QVariant();
  }
}

void Node::SetInputProperty(const QString &id, const QString &name, const QVariant &value)
{
  Input* i = GetInternalInputData(id);

  if (i) {
    i->properties.insert(name, value);

    emit InputPropertyChanged(id, name, value);
  } else {
    ReportInvalidInput("set property of", id, -1);
  }
}

SplitValue Node::GetSplitValueAtTime(const QString &input, const rational &time, int element) const
{
  SplitValue vals;

  int nb_tracks = GetNumberOfKeyframeTracks(input);

  for (int i=0;i<nb_tracks;i++) {
    vals.append(GetSplitValueAtTimeOnTrack(input, time, i, element));
  }

  return vals;
}

QVariant Node::GetSplitValueAtTimeOnTrack(const QString &input, const rational &time, int track, int element) const
{
  if (!IsUsingStandardValue(input, track, element)) {
    const NodeKeyframeTrack& key_track = GetKeyframeTracks(input, element).at(track);

    if (key_track.first()->time() >= time) {
      // This time precedes any keyframe, so we just return the first value
      return key_track.first()->value();
    }

    if (key_track.last()->time() <= time) {
      // This time is after any keyframes so we return the last value
      return key_track.last()->value();
    }

    NodeValue::Type type = GetInputDataType(input);

    // If we're here, the time must be somewhere in between the keyframes
    NodeKeyframe *before = nullptr, *after = nullptr;

    int low = 0;
    int high = key_track.size()-1;
    while (low <= high) {
      int mid = low + (high - low) / 2;
      NodeKeyframe *mid_key = key_track.at(mid);
      NodeKeyframe *next_key = key_track.at(mid + 1);

      if (mid_key->time() <= time && next_key->time() > time) {
        before = mid_key;
        after = next_key;
        break;
      } else if (mid_key->time() < time) {
        low = mid + 1;
      } else {
        high = mid - 1;
      }
    }

    if (before) {
      if (before->time() == time
          || ((!NodeValue::type_can_be_interpolated(type) || before->type() == NodeKeyframe::kHold) && after->time() > time)) {

        // Time == keyframe time, so value is precise
        return before->value();

      } else if (after->time() == time) {

        // Time == keyframe time, so value is precise
        return after->value();

      } else if (before->time() < time && after->time() > time) {
        // We must interpolate between these keyframes

        double before_val, after_val, interpolated;
        if (type == NodeValue::kRational) {
          before_val = before->value().value<rational>().toDouble();
          after_val = after->value().value<rational>().toDouble();
        } else {
          before_val = before->value().toDouble();
          after_val = after->value().toDouble();
        }

        if (before->type() == NodeKeyframe::kBezier && after->type() == NodeKeyframe::kBezier) {

          // Perform a cubic bezier with two control points
          interpolated = Bezier::CubicXtoY(time.toDouble(),
                                           Imath::V2d(before->time().toDouble(), before_val),
                                           Imath::V2d(before->time().toDouble() + before->valid_bezier_control_out().x(), before_val + before->valid_bezier_control_out().y()),
                                           Imath::V2d(after->time().toDouble() + after->valid_bezier_control_in().x(), after_val + after->valid_bezier_control_in().y()),
                                           Imath::V2d(after->time().toDouble(), after_val));

        } else if (before->type() == NodeKeyframe::kBezier || after->type() == NodeKeyframe::kBezier) {
          // Perform a quadratic bezier with only one control point

          Imath::V2d control_point;

          if (before->type() == NodeKeyframe::kBezier) {
            control_point.x = (before->valid_bezier_control_out().x() + before->time().toDouble());
            control_point.y = (before->valid_bezier_control_out().y() + before_val);
          } else {
            control_point.x = (after->valid_bezier_control_in().x() + after->time().toDouble());
            control_point.y = (after->valid_bezier_control_in().y() + after_val);
          }

          // Interpolate value using quadratic beziers
          interpolated = Bezier::QuadraticXtoY(time.toDouble(),
                                               Imath::V2d(before->time().toDouble(), before_val),
                                               control_point,
                                               Imath::V2d(after->time().toDouble(), after_val));

        } else {
          // To have arrived here, the keyframes must both be linear
          qreal period_progress = (time.toDouble() - before->time().toDouble()) / (after->time().toDouble() - before->time().toDouble());

          interpolated = lerp(before_val, after_val, period_progress);
        }

        if (type == NodeValue::kRational) {
          return QVariant::fromValue(rational::fromDouble(interpolated));
        } else {
          return interpolated;
        }
      }
    } else {
      qWarning() << "Binary search for keyframes failed";
    }
  }

  return GetSplitStandardValueOnTrack(input, track, element);
}

QVariant Node::GetDefaultValue(const QString &input) const
{
  NodeValue::Type type = GetInputDataType(input);

  return NodeValue::combine_track_values_into_normal_value(type, GetSplitDefaultValue(input));
}

SplitValue Node::GetSplitDefaultValue(const QString &input) const
{
  const Input* i = GetInternalInputData(input);

  if (i) {
    return i->default_value;
  } else {
    ReportInvalidInput("retrieve default value of", input, -1);
    return SplitValue();
  }
}

QVariant Node::GetSplitDefaultValueOnTrack(const QString &input, int track) const
{
  SplitValue val = GetSplitDefaultValue(input);
  if (track < val.size()) {
    return val.at(track);
  } else {
    return QVariant();
  }
}

void Node::SetDefaultValue(const QString &input, const QVariant &val)
{
  NodeValue::Type type = GetInputDataType(input);

  SetSplitDefaultValue(input, NodeValue::split_normal_value_into_track_values(type, val));
}

void Node::SetSplitDefaultValue(const QString &input, const SplitValue &val)
{
  Input* i = GetInternalInputData(input);

  if (i) {
    i->default_value = val;
  } else {
    ReportInvalidInput("set default value of", input, -1);
  }
}

void Node::SetSplitDefaultValueOnTrack(const QString &input, const QVariant &val, int track)
{
  Input* i = GetInternalInputData(input);

  if (i) {
    if (track < i->default_value.size()) {
      i->default_value[track] = val;
    }
  } else {
    ReportInvalidInput("set default value on track of", input, -1);
  }
}

const QVector<NodeKeyframeTrack> &Node::GetKeyframeTracks(const QString &input, int element) const
{
  return GetImmediate(input, element)->keyframe_tracks();
}

QVector<NodeKeyframe *> Node::GetKeyframesAtTime(const QString &input, const rational &time, int element) const
{
  NodeInputImmediate* imm = GetImmediate(input, element);

  if (imm) {
    return imm->get_keyframe_at_time(time);
  } else {
    ReportInvalidInput("get keyframes at time from", input, element);
    return QVector<NodeKeyframe*>();
  }
}

NodeKeyframe *Node::GetKeyframeAtTimeOnTrack(const QString &input, const rational &time, int track, int element) const
{
  NodeInputImmediate* imm = GetImmediate(input, element);

  if (imm) {
    return imm->get_keyframe_at_time_on_track(time, track);
  } else {
    ReportInvalidInput("get keyframe at time on track from", input, element);
    return nullptr;
  }
}

NodeKeyframe::Type Node::GetBestKeyframeTypeForTimeOnTrack(const QString &input, const rational &time, int track, int element) const
{
  NodeInputImmediate* imm = GetImmediate(input, element);

  if (imm) {
    return imm->get_best_keyframe_type_for_time(time, track);
  } else {
    ReportInvalidInput("get closest keyframe before a time from", input, element);
    return NodeKeyframe::kDefaultType;
  }
}

int Node::GetNumberOfKeyframeTracks(const QString &id) const
{
  return NodeValue::get_number_of_keyframe_tracks(GetInputDataType(id));
}

NodeKeyframe *Node::GetEarliestKeyframe(const QString &id, int element) const
{
  NodeInputImmediate* imm = GetImmediate(id, element);

  if (imm) {
    return imm->get_earliest_keyframe();
  } else {
    ReportInvalidInput("get earliest keyframe from", id, element);
    return nullptr;
  }
}

NodeKeyframe *Node::GetLatestKeyframe(const QString &id, int element) const
{
  NodeInputImmediate* imm = GetImmediate(id, element);

  if (imm) {
    return imm->get_latest_keyframe();
  } else {
    ReportInvalidInput("get latest keyframe from", id, element);
    return nullptr;
  }
}

NodeKeyframe *Node::GetClosestKeyframeBeforeTime(const QString &id, const rational &time, int element) const
{
  NodeInputImmediate* imm = GetImmediate(id, element);

  if (imm) {
    return imm->get_closest_keyframe_before_time(time);
  } else {
    ReportInvalidInput("get closest keyframe before a time from", id, element);
    return nullptr;
  }
}

NodeKeyframe *Node::GetClosestKeyframeAfterTime(const QString &id, const rational &time, int element) const
{
  NodeInputImmediate* imm = GetImmediate(id, element);

  if (imm) {
    return imm->get_closest_keyframe_after_time(time);
  } else {
    ReportInvalidInput("get closest keyframe after a time from", id, element);
    return nullptr;
  }
}

bool Node::HasKeyframeAtTime(const QString &id, const rational &time, int element) const
{
  NodeInputImmediate* imm = GetImmediate(id, element);

  if (imm) {
    return imm->has_keyframe_at_time(time);
  } else {
    ReportInvalidInput("determine if it has a keyframe at a time from", id, element);
    return false;
  }
}

QStringList Node::GetComboBoxStrings(const QString &id) const
{
  return GetInputProperty(id, QStringLiteral("combo_str")).toStringList();
}

QVariant Node::GetStandardValue(const QString &id, int element) const
{
  NodeValue::Type type = GetInputDataType(id);

  return NodeValue::combine_track_values_into_normal_value(type, GetSplitStandardValue(id, element));
}

SplitValue Node::GetSplitStandardValue(const QString &id, int element) const
{
  NodeInputImmediate* imm = GetImmediate(id, element);

  if (imm) {
    return imm->get_split_standard_value();
  } else {
    ReportInvalidInput("get standard value of", id, element);
    return SplitValue();
  }
}

QVariant Node::GetSplitStandardValueOnTrack(const QString &input, int track, int element) const
{
  NodeInputImmediate* imm = GetImmediate(input, element);

  if (imm) {
    return imm->get_split_standard_value_on_track(track);
  } else {
    ReportInvalidInput("get standard value of", input, element);
    return QVariant();
  }
}

void Node::SetStandardValue(const QString &id, const QVariant &value, int element)
{
  NodeValue::Type type = GetInputDataType(id);

  SetSplitStandardValue(id, NodeValue::split_normal_value_into_track_values(type, value), element);
}

void Node::SetSplitStandardValue(const QString &id, const SplitValue &value, int element)
{
  NodeInputImmediate* imm = GetImmediate(id, element);

  if (imm) {
    imm->set_split_standard_value(value);

    for (int i=0; i<value.size(); i++) {
      if (IsUsingStandardValue(id, i, element)) {
        // If this standard value is being used, we need to send a value changed signal
        ParameterValueChanged(id, element, TimeRange(RATIONAL_MIN, RATIONAL_MAX));
        break;
      }
    }
  } else {
    ReportInvalidInput("set standard value of", id, element);
  }
}

void Node::SetSplitStandardValueOnTrack(const QString &id, int track, const QVariant &value, int element)
{
  NodeInputImmediate* imm = GetImmediate(id, element);

  if (imm) {
    imm->set_standard_value_on_track(value, track);

    if (IsUsingStandardValue(id, track, element)) {
      // If this standard value is being used, we need to send a value changed signal
      ParameterValueChanged(id, element, TimeRange(RATIONAL_MIN, RATIONAL_MAX));
    }
  } else {
    ReportInvalidInput("set standard value of", id, element);
  }
}

bool Node::InputIsArray(const QString &id) const
{
  return GetInputFlags(id) & kInputFlagArray;
}

void Node::InputArrayInsert(const QString &id, int index)
{
  // Add new input
  ArrayResizeInternal(id, InputArraySize(id) + 1);

  // Move connections down
  InputConnections copied_edges = input_connections();
  for (auto it=copied_edges.crbegin(); it!=copied_edges.crend(); it++) {
    if (it->first.input() == id && it->first.element() >= index) {
      // Disconnect this and reconnect it one element down
      NodeInput new_edge = it->first;
      new_edge.set_element(new_edge.element() + 1);

      DisconnectEdge(it->second, it->first);
      ConnectEdge(it->second, new_edge);
    }
  }

  // Shift values and keyframes up one element
  for (int i=InputArraySize(id)-1; i>index; i--) {
    CopyValuesOfElement(this, this, id, i-1, i);
  }

  // Reset value of element we just "inserted"
  ClearElement(id, index);
}

void Node::InputArrayResize(const QString &id, int size)
{
  if (InputArraySize(id) == size) {
    return;
  }

  NodeArrayResizeCommand* c = new NodeArrayResizeCommand(this, id, size);
  c->redo_now();
  delete c;
}

void Node::InputArrayRemove(const QString &id, int index)
{
  // Remove input
  ArrayResizeInternal(id, InputArraySize(id) - 1);

  // Move connections up
  InputConnections copied_edges = input_connections();
  for (auto it=copied_edges.cbegin(); it!=copied_edges.cend(); it++) {
    if (it->first.input() == id && it->first.element() >= index) {
      // Disconnect this and reconnect it one element up if it's not the element being removed
      DisconnectEdge(it->second, it->first);

      if (it->first.element() > index) {
        NodeInput new_edge = it->first;
        new_edge.set_element(new_edge.element() - 1);

        ConnectEdge(it->second, new_edge);
      }
    }
  }

  // Shift values and keyframes down one element
  int arr_sz = InputArraySize(id);
  for (int i=index; i<arr_sz; i++) {
    // Copying ArraySize()+1 is actually legal because immediates are never deleted
    CopyValuesOfElement(this, this, id, i+1, i);
  }

  // Reset value of last element
  ClearElement(id, arr_sz);
}

int Node::InputArraySize(const QString &id) const
{
  const Input* i = GetInternalInputData(id);

  if (i) {
    return i->array_size;
  } else {
    ReportInvalidInput("retrieve array size of", id, -1);
    return 0;
  }
}

void Node::SetValueHintForInput(const QString &input, const ValueHint &hint, int element)
{
  value_hints_.insert({input, element}, hint);

  emit InputValueHintChanged(NodeInput(this, input, element));

  InvalidateAll(input, element);
}

const NodeKeyframeTrack &Node::GetTrackFromKeyframe(NodeKeyframe *key) const
{
  return GetImmediate(key->input(), key->element())->keyframe_tracks().at(key->track());
}

NodeInputImmediate *Node::GetImmediate(const QString &input, int element) const
{
  if (element == -1) {
    return standard_immediates_.value(input, nullptr);
  } else if (array_immediates_.contains(input)) {
    const QVector<NodeInputImmediate*>& imm_arr = array_immediates_.value(input);

    if (element >= 0 && element < imm_arr.size()) {
      return imm_arr.at(element);
    }
  }

  return nullptr;
}

InputFlags Node::GetInputFlags(const QString &input) const
{
  const Input* i = GetInternalInputData(input);

  if (i) {
    return i->flags;
  } else {
    ReportInvalidInput("retrieve flags of", input, -1);
    return InputFlags(kInputFlagNormal);
  }
}

void Node::SetInputFlag(const QString &input, InputFlag f, bool on)
{
  Input* i = GetInternalInputData(input);

  if (i) {
    if (on) {
      i->flags |= f;
    } else {
      i->flags &= ~f;
    }
    emit InputFlagsChanged(input, i->flags);
  } else {
    ReportInvalidInput("set flags of", input, -1);
  }
}

void Node::Value(const NodeValueRow& value, const NodeGlobals &globals, NodeValueTable *table) const
{
  // Do nothing
  Q_UNUSED(value)
  Q_UNUSED(globals)
  Q_UNUSED(table)
}

void Node::InvalidateCache(const TimeRange &range, const QString &from, int element, InvalidateCacheOptions options)
{
  Q_UNUSED(from)
  Q_UNUSED(element)

  if (AreCachesEnabled()) {
    if (range.in() != range.out()) {
      TimeRange vr = range.Intersected(GetVideoCacheRange());
      if (vr.length() != 0) {
        video_frame_cache()->Invalidate(vr);
        thumbnail_cache()->Invalidate(vr);
      }
      TimeRange ar = range.Intersected(GetAudioCacheRange());
      if (ar.length() != 0) {
        audio_playback_cache()->Invalidate(ar);
        waveform_cache()->Invalidate(ar);
      }
    }
  }

  SendInvalidateCache(range, options);
}

TimeRange Node::InputTimeAdjustment(const QString &, int, const TimeRange &input_time, bool clamp) const
{
  // Default behavior is no time adjustment at all
  return input_time;
}

TimeRange Node::OutputTimeAdjustment(const QString &, int, const TimeRange &input_time) const
{
  // Default behavior is no time adjustment at all
  return input_time;
}

QVector<Node *> Node::CopyDependencyGraph(const QVector<Node *> &nodes, MultiUndoCommand *command)
{
  int nb_nodes = nodes.size();

  QVector<Node*> copies(nb_nodes);

  for (int i=0; i<nb_nodes; i++) {
    // Create another of the same node
    Node* c = nodes.at(i)->copy();

    // Copy the values, but NOT the connections, since we'll be connecting to our own clones later
    Node::CopyInputs(nodes.at(i), c, false);

    // Add to graph
    Project* graph = nodes.at(i)->parent();
    if (command) {
      command->add_child(new NodeAddCommand(graph, c));
    } else {
      c->setParent(graph);
    }

    // Store in array at the same index as source
    copies[i] = c;
  }

  CopyDependencyGraph(nodes, copies, command);

  return copies;
}

void Node::CopyDependencyGraph(const QVector<Node *> &src, const QVector<Node *> &dst, MultiUndoCommand *command)
{
  for (int i=0; i<src.size(); i++) {
    Node* src_node = src.at(i);
    Node* dst_node = dst.at(i);

    for (auto it=src_node->input_connections_.cbegin(); it!=src_node->input_connections_.cend(); it++) {
      // Determine if the connected node is in our src list
      int connection_index = src.indexOf(it->second);

      if (connection_index > -1) {
        // Find the equivalent node in the dst list
        Node *copied_output = dst.at(connection_index);
        NodeInput copied_input = NodeInput(dst_node, it->first.input(), it->first.element());

        if (command) {
          command->add_child(new NodeEdgeAddCommand(copied_output, copied_input));
          command->add_child(new NodeSetValueHintCommand(copied_input, src_node->GetValueHintForInput(copied_input.input(), copied_input.element())));
        } else {
          ConnectEdge(copied_output, copied_input);
          copied_input.node()->SetValueHintForInput(copied_input.input(), src_node->GetValueHintForInput(copied_input.input(), copied_input.element()), copied_input.element());
        }
      }
    }
  }
}

Node *Node::CopyNodeAndDependencyGraphMinusItemsInternal(QMap<Node*, Node*>& created, Node *node, MultiUndoCommand *command)
{
  // Make a new node of the same type
  Node* copy = node->copy();

  // Add to map
  created.insert(node, copy);

  // Add it to the same graph
  command->add_child(new NodeAddCommand(node->parent(), copy));

  // Copy context children
  const PositionMap &map = node->GetContextPositions();
  for (auto it=map.cbegin(); it!=map.cend(); it++) {
    // Add either the copy (if it exists) or the original node to the context
    Node *child;

    if (it.key()->IsItem()) {
      child = it.key();
    } else {
      child = created.value(it.key());
      if (!child) {
        child = CopyNodeAndDependencyGraphMinusItemsInternal(created, it.key(), command);
      }
    }

    command->add_child(new NodeSetPositionCommand(child, copy, it.value()));
  }

  // If this is a group, copy input and output passthroughs
  if (NodeGroup *src_group = dynamic_cast<NodeGroup*>(node)) {
    NodeGroup *dst_group = static_cast<NodeGroup*>(copy);

    for (auto it=src_group->GetInputPassthroughs().cbegin(); it!=src_group->GetInputPassthroughs().cend(); it++) {
      // This node should have been created by the context loop above
      NodeInput input = it->second;
      input.set_node(created.value(input.node()));
      command->add_child(new NodeGroupAddInputPassthrough(dst_group, input, it->first));
    }

    command->add_child(new NodeGroupSetOutputPassthrough(dst_group, created.value(src_group->GetOutputPassthrough())));
  }

  // Copy values to the clone
  CopyInputs(node, copy, false, command);

  // Go through input connections and copy if non-item and connect if item
  for (auto it=node->input_connections_.cbegin(); it!=node->input_connections_.cend(); it++) {
    NodeInput input = it->first;
    Node* connected = it->second;
    Node* connected_copy;

    if (connected->IsItem()) {
      // This is an item and we avoid copying those and just connect to them directly
      connected_copy = connected;
    } else {
      // Non-item, we want to clone this too
      connected_copy = created.value(connected, nullptr);
      if (!connected_copy) {
        connected_copy = CopyNodeAndDependencyGraphMinusItemsInternal(created, connected, command);
      }
    }

    NodeInput copied_input = input;
    copied_input.set_node(copy);
    command->add_child(new NodeEdgeAddCommand(connected_copy, copied_input));
    command->add_child(new NodeSetValueHintCommand(copied_input, node->GetValueHintForInput(input.input(), input.element())));
  }

  return copy;
}

Node *Node::CopyNodeAndDependencyGraphMinusItems(Node *node, MultiUndoCommand *command)
{
  QMap<Node*, Node*> created;

  return CopyNodeAndDependencyGraphMinusItemsInternal(created, node, command);
}

Node *Node::CopyNodeInGraph(Node *node, MultiUndoCommand *command)
{
  Node* copy;

  if (OLIVE_CONFIG("SplitClipsCopyNodes").toBool()) {
    copy = Node::CopyNodeAndDependencyGraphMinusItems(node, command);
  } else {
    copy = node->copy();

    command->add_child(new NodeAddCommand(node->parent(), copy));

    CopyInputs(node, copy, true, command);

    const PositionMap &map = node->GetContextPositions();
    for (auto it=map.cbegin(); it!=map.cend(); it++) {
      // Add to the context
      command->add_child(new NodeSetPositionCommand(it.key(), copy, it.value()));
    }
  }

  return copy;
}

void Node::SendInvalidateCache(const TimeRange &range, const InvalidateCacheOptions &options)
{
  for (const OutputConnection& conn : output_connections_) {
    // Send clear cache signal to the Node
    const NodeInput& in = conn.second;

    in.node()->InvalidateCache(range, in.input(), in.element(), options);
  }
}

void Node::InvalidateAll(const QString &input, int element)
{
  InvalidateCache(TimeRange(RATIONAL_MIN, RATIONAL_MAX), input, element);
}

bool Node::Link(Node *a, Node *b)
{
  if (a == b || !a || !b) {
    return false;
  }

  if (AreLinked(a, b)) {
    return false;
  }

  a->links_.append(b);
  b->links_.append(a);

  a->LinkChangeEvent();
  b->LinkChangeEvent();

  emit a->LinksChanged();
  emit b->LinksChanged();

  return true;
}

bool Node::Unlink(Node *a, Node *b)
{
  if (!AreLinked(a, b)) {
    return false;
  }

  a->links_.removeOne(b);
  b->links_.removeOne(a);

  a->LinkChangeEvent();
  b->LinkChangeEvent();

  emit a->LinksChanged();
  emit b->LinksChanged();

  return true;
}

bool Node::AreLinked(Node *a, Node *b)
{
  return a->links_.contains(b);
}

bool Node::Load(QXmlStreamReader *reader, SerializedData *data)
{
  uint version = 0;

  XMLAttributeLoop(reader, attr) {
    if (attr.name() == QStringLiteral("ptr")) {
      quintptr ptr = attr.value().toULongLong();
      data->node_ptrs.insert(ptr, this);
    } else if (attr.name() == QStringLiteral("version")) {
      version = attr.value().toUInt();
    }
  }

  Q_UNUSED(version)

  while (XMLReadNextStartElement(reader)) {
    if (reader->name() == QStringLiteral("input")) {
      LoadInput(reader, data);
    } else if (reader->name() == QStringLiteral("label")) {
      this->SetLabel(reader->readElementText());
    } else if (reader->name() == QStringLiteral("color")) {
      this->SetOverrideColor(reader->readElementText().toInt());
    } else if (reader->name() == QStringLiteral("links")) {
      while (XMLReadNextStartElement(reader)) {
        if (reader->name() == QStringLiteral("link")) {
          data->block_links.append({this, reader->readElementText().toULongLong()});
        } else {
          reader->skipCurrentElement();
        }
      }
    } else if (reader->name() == QStringLiteral("custom")) {
      if (!LoadCustom(reader, data)) {
        return false;
      }
    } else if (reader->name() == QStringLiteral("connections")) {
      // Load connections
      while (XMLReadNextStartElement(reader)) {
        if (reader->name() == QStringLiteral("connection")) {
          QString param_id;
          int ele = -1;

          XMLAttributeLoop(reader, attr) {
            if (attr.name() == QStringLiteral("element")) {
              ele = attr.value().toInt();
            } else if (attr.name() == QStringLiteral("input")) {
              param_id = attr.value().toString();
            }
          }

          QString output_node_id;

          while (XMLReadNextStartElement(reader)) {
            if (reader->name() == QStringLiteral("output")) {
              output_node_id = reader->readElementText();
            } else {
              reader->skipCurrentElement();
            }
          }

          data->desired_connections.append({NodeInput(this, param_id, ele), output_node_id.toULongLong()});
        } else {
          reader->skipCurrentElement();
        }
      }
    } else if (reader->name() == QStringLiteral("hints")) {
      while (XMLReadNextStartElement(reader)) {
        if (reader->name() == QStringLiteral("hint")) {
          QString input;
          int element = -1;

          XMLAttributeLoop(reader, attr) {
            if (attr.name() == QStringLiteral("input")) {
              input = attr.value().toString();
            } else if (attr.name() == QStringLiteral("element")) {
              element = attr.value().toInt();
            }
          }

          Node::ValueHint vh;
          if (!vh.load(reader)) {
            return false;
          }
          this->SetValueHintForInput(input, vh, element);
        } else {
          reader->skipCurrentElement();
        }
      }
    } else if (reader->name() == QStringLiteral("context")) {
      while (XMLReadNextStartElement(reader)) {
        if (reader->name() == QStringLiteral("node")) {
          quintptr node_ptr = 0;

          XMLAttributeLoop(reader, attr) {
            if (attr.name() == QStringLiteral("ptr")) {
              node_ptr = attr.value().toULongLong();
            }
          }

          if (node_ptr) {
            Node::Position node_pos;
            if (!node_pos.load(reader)) {
              return false;
            }
            data->positions[this].insert(node_ptr, node_pos);
          } else {
            return false;
          }
        } else {
          reader->skipCurrentElement();
        }
      }
    } else if (reader->name() == QStringLiteral("caches")) {
      while (XMLReadNextStartElement(reader)) {
        if (reader->name() == QStringLiteral("audio")) {
          this->audio_playback_cache()->SetUuid(QUuid::fromString(reader->readElementText()));
        } else if (reader->name() == QStringLiteral("video")) {
          this->video_frame_cache()->SetUuid(QUuid::fromString(reader->readElementText()));
        } else if (reader->name() == QStringLiteral("thumb")) {
          this->thumbnail_cache()->SetUuid(QUuid::fromString(reader->readElementText()));
        } else if (reader->name() == QStringLiteral("waveform")) {
          this->waveform_cache()->SetUuid(QUuid::fromString(reader->readElementText()));
        } else {
          reader->skipCurrentElement();
        }
      }
    } else {
      reader->skipCurrentElement();
    }
  }

  this->LoadFinishedEvent();

  return true;
}

void Node::Save(QXmlStreamWriter *writer) const
{
  writer->writeAttribute(QStringLiteral("version"), QString::number(1));
  writer->writeAttribute(QStringLiteral("id"), this->id());
  writer->writeAttribute(QStringLiteral("ptr"), QString::number(reinterpret_cast<quintptr>(this)));

  if (!this->GetLabel().isEmpty()) {
    writer->writeTextElement(QStringLiteral("label"), this->GetLabel());
  }

  if (this->GetOverrideColor() != -1) {
    writer->writeTextElement(QStringLiteral("color"), QString::number(this->GetOverrideColor()));
  }

  foreach (const QString& input, this->inputs()) {
    writer->writeStartElement(QStringLiteral("input"));

    SaveInput(writer, input);

    writer->writeEndElement(); // input
  }

  if (!this->links().empty()) {
    writer->writeStartElement(QStringLiteral("links"));
    foreach (Node* link, this->links()) {
      writer->writeTextElement(QStringLiteral("link"), QString::number(reinterpret_cast<quintptr>(link)));
    }
    writer->writeEndElement(); // links
  }

  if (!this->input_connections().empty()) {
    writer->writeStartElement(QStringLiteral("connections"));
    for (auto it=this->input_connections().cbegin(); it!=this->input_connections().cend(); it++) {
      writer->writeStartElement(QStringLiteral("connection"));

      writer->writeAttribute(QStringLiteral("input"), it->first.input());
      writer->writeAttribute(QStringLiteral("element"), QString::number(it->first.element()));

      writer->writeTextElement(QStringLiteral("output"), QString::number(reinterpret_cast<quintptr>(it->second)));

      writer->writeEndElement(); // connection
    }
    writer->writeEndElement(); // connections
  }

  if (!this->GetValueHints().empty()) {
    writer->writeStartElement(QStringLiteral("hints"));
    for (auto it=this->GetValueHints().cbegin(); it!=this->GetValueHints().cend(); it++) {
      writer->writeStartElement(QStringLiteral("hint"));

      writer->writeAttribute(QStringLiteral("input"), it.key().input);
      writer->writeAttribute(QStringLiteral("element"), QString::number(it.key().element));

      it.value().save(writer);

      writer->writeEndElement(); // hint
    }
    writer->writeEndElement(); // hints
  }

  const Node::PositionMap &map = this->GetContextPositions();

  if (!map.isEmpty()) {
    writer->writeStartElement(QStringLiteral("context"));

    for (auto jt=map.cbegin(); jt!=map.cend(); jt++) {
      writer->writeStartElement(QStringLiteral("node"));
      writer->writeAttribute(QStringLiteral("ptr"), QString::number(reinterpret_cast<quintptr>(jt.key())));
      jt.value().save(writer);
      writer->writeEndElement(); // node
    }

    writer->writeEndElement(); // context
  }

  writer->writeStartElement(QStringLiteral("caches"));

  writer->writeTextElement(QStringLiteral("audio"), this->audio_playback_cache()->GetUuid().toString());
  writer->writeTextElement(QStringLiteral("video"), this->video_frame_cache()->GetUuid().toString());
  writer->writeTextElement(QStringLiteral("thumb"), this->thumbnail_cache()->GetUuid().toString());
  writer->writeTextElement(QStringLiteral("waveform"), this->waveform_cache()->GetUuid().toString());

  writer->writeEndElement(); // caches

  writer->writeStartElement(QStringLiteral("custom"));

  SaveCustom(writer);

  writer->writeEndElement(); // custom
}

bool Node::LoadCustom(QXmlStreamReader *reader, SerializedData *data)
{
  reader->skipCurrentElement();
  return true;
}

void Node::PostLoadEvent(SerializedData *data)
{
  // Resolve positions
  const QMap<quintptr, Node::Position> &positions = data->positions.value(this);

  for (auto jt=positions.cbegin(); jt!=positions.cend(); jt++) {
    Node *n = data->node_ptrs.value(jt.key());
    if (n) {
      this->SetNodePositionInContext(n, jt.value());
    }
  }
}

bool Node::LoadInput(QXmlStreamReader *reader, SerializedData *data)
{
  if (dynamic_cast<NodeGroup*>(this)) {
    // Ignore input of group
    reader->skipCurrentElement();
    return true;
  }

  QString param_id;

  XMLAttributeLoop(reader, attr) {
    if (attr.name() == QStringLiteral("id")) {
      param_id = attr.value().toString();

      break;
    }
  }

  if (param_id.isEmpty()) {
    qWarning() << "Failed to load parameter with missing ID";
    reader->skipCurrentElement();
    return false;
  }

  if (!this->HasInputWithID(param_id)) {
    qWarning() << "Failed to load parameter that didn't exist:" << param_id;
    reader->skipCurrentElement();
    return false;
  }

  while (XMLReadNextStartElement(reader)) {
    if (reader->name() == QStringLiteral("primary")) {
      // Load primary immediate
      if (!LoadImmediate(reader, param_id, -1, data)) {
        return false;
      }
    } else if (reader->name() == QStringLiteral("subelements")) {
      // Load subelements
      XMLAttributeLoop(reader, attr) {
        if (attr.name() == QStringLiteral("count")) {
          this->InputArrayResize(param_id, attr.value().toInt());
        }
      }

      int element_counter = 0;

      while (XMLReadNextStartElement(reader)) {
        if (reader->name() == QStringLiteral("element")) {
          if (!LoadImmediate(reader, param_id, element_counter, data)) {
            return false;
          }

          element_counter++;
        } else {
          reader->skipCurrentElement();
        }
      }
    } else {
      reader->skipCurrentElement();
    }
  }

  return true;
}

void Node::SaveInput(QXmlStreamWriter *writer, const QString &id) const
{
  writer->writeAttribute(QStringLiteral("id"), id);

  writer->writeStartElement(QStringLiteral("primary"));

  SaveImmediate(writer, id, -1);

  writer->writeEndElement(); // primary

  int arr_sz = this->InputArraySize(id);

  if (arr_sz > 0) {
    writer->writeStartElement(QStringLiteral("subelements"));

    writer->writeAttribute(QStringLiteral("count"), QString::number(arr_sz));

    for (int i=0; i<arr_sz; i++) {
      writer->writeStartElement(QStringLiteral("element"));

      SaveImmediate(writer, id, i);

      writer->writeEndElement(); // element
    }

    writer->writeEndElement(); // subelements
  }
}

bool Node::LoadImmediate(QXmlStreamReader *reader, const QString &input, int element, SerializedData *data)
{
  NodeValue::Type data_type = this->GetInputDataType(input);

  // HACK: SubtitleParams contain the actual subtitle data, so loading/replacing it will overwrite
  //       the valid subtitles. We hack around it by simply skipping loading subtitles, we'll see
  //       if this ends up being an issue in the future.
  if (data_type == NodeValue::kSubtitleParams) {
    reader->skipCurrentElement();
    return true;
  }

  while (XMLReadNextStartElement(reader)) {
    if (reader->name() == QStringLiteral("standard")) {
      // Load standard value
      int val_index = 0;

      while (XMLReadNextStartElement(reader)) {
        if (reader->name() == QStringLiteral("track")) {
          QVariant value_on_track;

          if (data_type == NodeValue::kVideoParams) {
            VideoParams vp;
            vp.Load(reader);
            value_on_track = QVariant::fromValue(vp);
          } else if (data_type == NodeValue::kAudioParams) {
            AudioParams ap = TypeSerializer::LoadAudioParams(reader);
            value_on_track = QVariant::fromValue(ap);
          } else {
            QString value_text = reader->readElementText();

            if (!value_text.isEmpty()) {
              value_on_track = NodeValue::StringToValue(data_type, value_text, true);
            }
          }

          this->SetSplitStandardValueOnTrack(input, val_index, value_on_track, element);

          val_index++;
        } else {
          reader->skipCurrentElement();
        }
      }
    } else if (reader->name() == QStringLiteral("keyframing")) {
      bool k = reader->readElementText().toInt();
      if (this->IsInputKeyframable(input)) {
        this->SetInputIsKeyframing(input, k, element);
      }
    } else if (reader->name() == QStringLiteral("keyframes")) {
      int track = 0;

      while (XMLReadNextStartElement(reader)) {
        if (reader->name() == QStringLiteral("track")) {
          while (XMLReadNextStartElement(reader)) {
            if (reader->name() == QStringLiteral("key")) {
              NodeKeyframe* key = new NodeKeyframe();
              key->set_input(input);
              key->set_element(element);
              key->set_track(track);

              key->load(reader, data_type);
              key->setParent(this);
            } else {
              reader->skipCurrentElement();
            }
          }

          track++;
        } else {
          reader->skipCurrentElement();
        }
      }
    } else if (reader->name() == QStringLiteral("csinput")) {
      this->SetInputProperty(input, QStringLiteral("col_input"), reader->readElementText());
    } else if (reader->name() == QStringLiteral("csdisplay")) {
      this->SetInputProperty(input, QStringLiteral("col_display"), reader->readElementText());
    } else if (reader->name() == QStringLiteral("csview")) {
      this->SetInputProperty(input, QStringLiteral("col_view"), reader->readElementText());
    } else if (reader->name() == QStringLiteral("cslook")) {
      this->SetInputProperty(input, QStringLiteral("col_look"), reader->readElementText());
    } else {
      reader->skipCurrentElement();
    }
  }

  return true;
}

void Node::SaveImmediate(QXmlStreamWriter *writer, const QString &input, int element) const
{
  bool is_keyframing = this->IsInputKeyframing(input, element);

  if (this->IsInputKeyframable(input)) {
    writer->writeTextElement(QStringLiteral("keyframing"), QString::number(is_keyframing));
  }

  NodeValue::Type data_type = this->GetInputDataType(input);

  // Write standard value
  writer->writeStartElement(QStringLiteral("standard"));

  foreach (const QVariant& v, this->GetSplitStandardValue(input, element)) {
    writer->writeStartElement(QStringLiteral("track"));

    if (data_type == NodeValue::kVideoParams) {
      v.value<VideoParams>().Save(writer);
    } else if (data_type == NodeValue::kAudioParams) {
      TypeSerializer::SaveAudioParams(writer, v.value<AudioParams>());
    } else {
      writer->writeCharacters(NodeValue::ValueToString(data_type, v, true));
    }

    writer->writeEndElement(); // track
  }

  writer->writeEndElement(); // standard

  // Write keyframes
  if (is_keyframing) {
    writer->writeStartElement(QStringLiteral("keyframes"));

    for (const NodeKeyframeTrack& track : this->GetKeyframeTracks(input, element)) {
      writer->writeStartElement(QStringLiteral("track"));

      for (NodeKeyframe* key : track) {
        writer->writeStartElement(QStringLiteral("key"));

        key->save(writer, data_type);

        writer->writeEndElement(); // key
      }

      writer->writeEndElement(); // track
    }

    writer->writeEndElement(); // keyframes
  }

  if (data_type == NodeValue::kColor) {
    // Save color management information
    writer->writeTextElement(QStringLiteral("csinput"), this->GetInputProperty(input, QStringLiteral("col_input")).toString());
    writer->writeTextElement(QStringLiteral("csdisplay"), this->GetInputProperty(input, QStringLiteral("col_display")).toString());
    writer->writeTextElement(QStringLiteral("csview"), this->GetInputProperty(input, QStringLiteral("col_view")).toString());
    writer->writeTextElement(QStringLiteral("cslook"), this->GetInputProperty(input, QStringLiteral("col_look")).toString());
  }
}

void Node::InsertInput(const QString &id, NodeValue::Type type, const QVariant &default_value, InputFlags flags, int index)
{
  if (id.isEmpty()) {
    qWarning() << "Rejected adding input with an empty ID on node" << this->id();
    return;
  }

  if (HasParamWithID(id)) {
    qWarning() << "Failed to add input to node" << this->id() << "- param with ID" << id << "already exists";
    return;
  }

  Node::Input i;

  i.type = type;
  i.default_value = NodeValue::split_normal_value_into_track_values(type, default_value);
  i.flags = flags;
  i.array_size = 0;

  input_ids_.insert(index, id);
  input_data_.insert(index, i);

  if (!standard_immediates_.value(id, nullptr)) {
    standard_immediates_.insert(id, CreateImmediate(id));
  }

  emit InputAdded(id);
}

void Node::RemoveInput(const QString &id)
{
  int index = input_ids_.indexOf(id);

  if (index == -1) {
    ReportInvalidInput("remove", id, -1);
    return;
  }

  input_ids_.removeAt(index);
  input_data_.removeAt(index);

  emit InputRemoved(id);
}

void Node::ReportInvalidInput(const char *attempted_action, const QString& id, int element) const
{
  qWarning() << "Failed to" << attempted_action << "parameter" << id << "element" << element
             << "in node" << this->id() << "- input doesn't exist";
}

NodeInputImmediate *Node::CreateImmediate(const QString &input)
{
  const Input* i = GetInternalInputData(input);

  if (i) {
    return new NodeInputImmediate(i->type, i->default_value);
  } else {
    ReportInvalidInput("create immediate", input, -1);
    return nullptr;
  }
}

void Node::ArrayResizeInternal(const QString &id, int size)
{
  Input* imm = GetInternalInputData(id);

  if (!imm) {
    ReportInvalidInput("set array size", id, -1);
    return;
  }

  if (imm->array_size != size) {
    // Update array size
    if (imm->array_size < size) {
      // Size is larger, create any immediates that don't exist
      QVector<NodeInputImmediate*>& subinputs = array_immediates_[id];
      for (int i=subinputs.size(); i<size; i++) {
        subinputs.append(CreateImmediate(id));
      }

      // Note that we do not delete any immediates when decreasing size since the user might still
      // want that data. Therefore it's important to note that array_size_ does NOT necessarily
      // equal subinputs_.size()
    }

    int old_sz = imm->array_size;
    imm->array_size = size;
    emit InputArraySizeChanged(id, old_sz, size);
    ParameterValueChanged(id, -1, TimeRange(RATIONAL_MIN, RATIONAL_MAX));
  }
}

QString Node::GetConnectCommandString(Node *output, const NodeInput &input)
{
  return tr("Connected %1 to %2 - %3").arg(output->GetLabelAndName(), input.node()->GetLabelAndName(), input.GetInputName());
}

QString Node::GetDisconnectCommandString(Node *output, const NodeInput &input)
{
  return tr("Disconnected %1 from %2 - %3").arg(output->GetLabelAndName(), input.node()->GetLabelAndName(), input.GetInputName());
}

int Node::GetInternalInputArraySize(const QString &input)
{
  return array_immediates_.value(input).size();
}

void FindWaysNodeArrivesHereRecursively(const Node *output, const Node *input, QVector<NodeInput> &v)
{
  for (auto it=input->input_connections().cbegin(); it!=input->input_connections().cend(); it++) {
    if (it->second == output) {
      v.append(it->first);
    } else {
      FindWaysNodeArrivesHereRecursively(output, it->second, v);
    }
  }
}

QVector<NodeInput> Node::FindWaysNodeArrivesHere(const Node *output) const
{
  QVector<NodeInput> v;

  FindWaysNodeArrivesHereRecursively(output, this, v);

  return v;
}

void Node::SetInputName(const QString &id, const QString &name)
{
  Input* i = GetInternalInputData(id);

  if (i) {
    i->human_name = name;

    emit InputNameChanged(id, name);
  } else {
    ReportInvalidInput("set name of", id, -1);
  }
}

const QString &Node::GetLabel() const
{
  return label_;
}

void Node::SetLabel(const QString &s)
{
  if (label_ != s) {
    label_ = s;

    emit LabelChanged(label_);
  }
}

QString Node::GetLabelAndName() const
{
  if (GetLabel().isEmpty()) {
    return Name();
  } else {
    return tr("%1 (%2)").arg(GetLabel(), Name());
  }
}

QString Node::GetLabelOrName() const
{
  if (GetLabel().isEmpty()) {
    return Name();
  }
  return GetLabel();
}

void Node::CopyInputs(const Node *source, Node *destination, bool include_connections, MultiUndoCommand *command)
{
  Q_ASSERT(source->id() == destination->id());

  foreach (const QString& input, source->inputs()) {
    // NOTE: This assert is to ensure that inputs in the source also exist in the destination, which
    //       they should. If they don't and you hit this assert, check if you're handling group
    //       passthroughs correctly.
    Q_ASSERT(destination->HasInputWithID(input));

    CopyInput(source, destination, input, include_connections, true, command);
  }

  if (command) {
    command->add_child(new NodeRenameCommand(destination, source->GetLabel()));
  } else {
    destination->SetLabel(source->GetLabel());
  }

  if (command) {
    command->add_child(new NodeOverrideColorCommand(destination, source->GetOverrideColor()));
  } else {
    destination->SetOverrideColor(source->GetOverrideColor());
  }
}

void Node::CopyInput(const Node *src, Node *dst, const QString &input, bool include_connections, bool traverse_arrays, MultiUndoCommand *command)
{
  Q_ASSERT(src->id() == dst->id());

  CopyValuesOfElement(src, dst, input, -1, command);

  // Copy array size
  if (src->InputIsArray(input) && traverse_arrays) {
    int src_array_sz = src->InputArraySize(input);

    for (int i=0; i<src_array_sz; i++) {
      CopyValuesOfElement(src, dst, input, i, command);
    }
  }

  // Copy connections
  if (include_connections) {
    // Copy all connections
    for (auto it=src->input_connections().cbegin(); it!=src->input_connections().cend(); it++) {
      if (!traverse_arrays && it->first.element() != -1) {
        continue;
      }

      auto conn_output = it->second;
      NodeInput conn_input(dst, input, it->first.element());

      if (command) {
        command->add_child(new NodeEdgeAddCommand(conn_output, conn_input));
      } else {
        ConnectEdge(conn_output, conn_input);
      }
    }
  }
}

void Node::CopyValuesOfElement(const Node *src, Node *dst, const QString &input, int src_element, int dst_element, MultiUndoCommand *command)
{
  if (dst_element >= dst->GetInternalInputArraySize(input)) {
    qDebug() << "Ignored destination element that was out of array bounds";
    return;
  }

  NodeInput dst_input(dst, input, dst_element);

  // Copy standard value
  SplitValue standard = src->GetSplitStandardValue(input, src_element);
  if (command) {
    command->add_child(new NodeParamSetSplitStandardValueCommand(dst_input, standard));
  } else {
    dst->SetSplitStandardValue(input, standard, dst_element);
  }

  // Copy keyframes
  if (NodeInputImmediate *immediate = dst->GetImmediate(input, dst_element)) {
    if (command) {
      command->add_child(new NodeImmediateRemoveAllKeyframesCommand(immediate));
    } else {
      immediate->delete_all_keyframes();
    }
  }

  for (const NodeKeyframeTrack& track : src->GetImmediate(input, src_element)->keyframe_tracks()) {
    for (NodeKeyframe* key : track) {
      NodeKeyframe *copy = key->copy(dst_element, command ? nullptr : dst);
      if (command) {
        command->add_child(new NodeParamInsertKeyframeCommand(dst, copy));
      }
    }
  }

  // Copy keyframing state
  if (src->IsInputKeyframable(input)) {
    bool is_keying = src->IsInputKeyframing(input, src_element);
    if (command) {
      command->add_child(new NodeParamSetKeyframingCommand(dst_input, is_keying));
    } else {
      dst->SetInputIsKeyframing(input, is_keying, dst_element);
    }
  }

  // If this is the root of an array, copy the array size
  if (src_element == -1 && dst_element == -1) {
    int array_sz = src->InputArraySize(input);
    if (command) {
      command->add_child(new NodeArrayResizeCommand(dst, input, array_sz));
    } else {
      dst->ArrayResizeInternal(input, array_sz);
    }
  }

  // Copy value hint
  Node::ValueHint vh = src->GetValueHintForInput(input, src_element);
  if (command) {
    command->add_child(new NodeSetValueHintCommand(dst_input, vh));
  } else {
    dst->SetValueHintForInput(input, vh, dst_element);
  }
}

void GetDependenciesRecursively(QVector<Node*>& list, const Node* node, bool traverse, bool exclusive_only)
{
  for (auto it=node->input_connections().cbegin(); it!=node->input_connections().cend(); it++) {
    Node* connected_node = it->second;

    if (!exclusive_only || !connected_node->IsItem()) {
      if (!list.contains(connected_node)) {
        list.append(connected_node);

        if (traverse) {
          GetDependenciesRecursively(list, connected_node, traverse, exclusive_only);
        }
      }
    }
  }
}

/**
 * @brief Recursively collects dependencies of Node `n` and appends them to QList `list`
 *
 * @param traverse
 *
 * TRUE to recursively traverse each node for a complete dependency graph. FALSE to return only the immediate
 * dependencies.
 */
QVector<Node *> Node::GetDependenciesInternal(bool traverse, bool exclusive_only) const
{
  QVector<Node*> list;

  GetDependenciesRecursively(list, this, traverse, exclusive_only);

  return list;
}

QVector<Node *> Node::GetDependencies() const
{
  return GetDependenciesInternal(true, false);
}

QVector<Node *> Node::GetExclusiveDependencies() const
{
  return GetDependenciesInternal(true, true);
}

QVector<Node *> Node::GetImmediateDependencies() const
{
  return GetDependenciesInternal(false, false);
}

ShaderCode Node::GetShaderCode(const ShaderRequest &request) const
{
  return ShaderCode(QString(), QString());
}

void Node::ProcessSamples(const NodeValueRow &, const SampleBuffer &, SampleBuffer &, int) const
{
}

void Node::GenerateFrame(FramePtr frame, const GenerateJob &job) const
{
  Q_UNUSED(frame)
  Q_UNUSED(job)
}

bool Node::InputsFrom(Node *n, bool recursively) const
{
  for (auto it=input_connections_.cbegin(); it!=input_connections_.cend(); it++) {
    Node *connected = it->second;

    if (connected == n) {
      return true;
    } else if (recursively && connected->InputsFrom(n, recursively)) {
      return true;
    }
  }

  return false;
}

bool Node::InputsFrom(const QString &id, bool recursively) const
{
  for (auto it=input_connections_.cbegin(); it!=input_connections_.cend(); it++) {
    Node *connected = it->second;

    if (connected->id() == id) {
      return true;
    } else if (recursively && connected->InputsFrom(id, recursively)) {
      return true;
    }
  }

  return false;
}

void Node::DisconnectAll()
{
  // Disconnect inputs (copy map since internal map will change as we disconnect)
  InputConnections copy = input_connections_;
  for (auto it=copy.cbegin(); it!=copy.cend(); it++) {
    DisconnectEdge(it->second, it->first);
  }

  while (!output_connections_.empty()) {
    OutputConnection conn = output_connections_.back();
    DisconnectEdge(conn.first, conn.second);
  }
}

QString Node::GetCategoryName(const CategoryID &c)
{
  switch (c) {
  case kCategoryOutput:
    return tr("Output");
  case kCategoryDistort:
    return tr("Distort");
  case kCategoryMath:
    return tr("Math");
  case kCategoryKeying:
    return tr("Keying");
  case kCategoryColor:
    return tr("Color");
  case kCategoryFilter:
    return tr("Filter");
  case kCategoryTimeline:
    return tr("Timeline");
  case kCategoryGenerator:
    return tr("Generator");
  case kCategoryTransition:
    return tr("Transition");
  case kCategoryProject:
    return tr("Project");
  case kCategoryTime:
    return tr("Time");
  case kCategoryUnknown:
  case kCategoryCount:
    break;
  }

  return tr("Uncategorized");
}

TimeRange Node::TransformTimeTo(TimeRange time, Node *target, TransformTimeDirection dir, int path_index)
{
  Node *from = this;
  Node *to = target;

  if (dir == kTransformTowardsInput) {
    std::swap(from, to);
  }

  std::list<NodeInput> path = FindPath(from, to, path_index);

  if (!path.empty()) {
    if (dir == kTransformTowardsInput) {
      for (auto it=path.crbegin(); it!=path.crend(); it++) {
        const NodeInput &i = (*it);
        time = i.node()->InputTimeAdjustment(i.input(), i.element(), time, false);
      }
    } else {
      // Traverse in output direction
      for (auto it=path.cbegin(); it!=path.cend(); it++) {
        const NodeInput &i = (*it);
        time = i.node()->OutputTimeAdjustment(i.input(), i.element(), time);
      }
    }
  }

  return time;
}

void Node::ParameterValueChanged(const QString& input, int element, const TimeRange& range)
{
  InputValueChangedEvent(input, element);

  emit ValueChanged(NodeInput(this, input, element), range);

  if (GetInputFlags(input) & kInputFlagIgnoreInvalidations) {
    return;
  }

  InvalidateCache(range, input, element);
}

TimeRange Node::GetRangeAffectedByKeyframe(NodeKeyframe *key) const
{
  const NodeKeyframeTrack& key_track = GetTrackFromKeyframe(key);
  int keyframe_index = key_track.indexOf(key);

  TimeRange range = GetRangeAroundIndex(key->input(), keyframe_index, key->track(), key->element());

  // If a previous key exists and it's a hold, we don't need to invalidate those frames
  if (key_track.size() > 1
      && keyframe_index > 0
      && key_track.at(keyframe_index - 1)->type() == NodeKeyframe::kHold) {
    range.set_in(key->time());
  }

  return range;
}

TimeRange Node::GetRangeAroundIndex(const QString &input, int index, int track, int element) const
{
  rational range_begin = RATIONAL_MIN;
  rational range_end = RATIONAL_MAX;

  const NodeKeyframeTrack& key_track = GetImmediate(input, element)->keyframe_tracks().at(track);

  if (key_track.size() > 1) {
    if (index > 0) {
      // If this is not the first key, we'll need to limit it to the key just before
      range_begin = key_track.at(index - 1)->time();
    }
    if (index < key_track.size() - 1) {
      // If this is not the last key, we'll need to limit it to the key just after
      range_end = key_track.at(index + 1)->time();
    }
  }

  return TimeRange(range_begin, range_end);
}

void Node::ClearElement(const QString& input, int index)
{
  GetImmediate(input, index)->delete_all_keyframes();

  if (IsInputKeyframable(input)) {
    SetInputIsKeyframing(input, false, index);
  }

  SetSplitStandardValue(input, GetSplitDefaultValue(input), index);
}

void Node::InputValueChangedEvent(const QString &input, int element)
{
  Q_UNUSED(input)
  Q_UNUSED(element)
}

void Node::InputConnectedEvent(const QString &input, int element, Node *output)
{
  Q_UNUSED(input)
  Q_UNUSED(element)
  Q_UNUSED(output)
}

void Node::InputDisconnectedEvent(const QString &input, int element, Node *output)
{
  Q_UNUSED(input)
  Q_UNUSED(element)
  Q_UNUSED(output)
}

void Node::OutputConnectedEvent(const NodeInput &input)
{
  Q_UNUSED(input)
}

void Node::OutputDisconnectedEvent(const NodeInput &input)
{
  Q_UNUSED(input)
}

void Node::childEvent(QChildEvent *event)
{
  super::childEvent(event);

  if (NodeKeyframe* key = dynamic_cast<NodeKeyframe*>(event->child())) {
    NodeInput i(this, key->input(), key->element());

    if (event->type() == QEvent::ChildAdded) {
      GetImmediate(key->input(), key->element())->insert_keyframe(key);

      connect(key, &NodeKeyframe::TimeChanged, this, &Node::InvalidateFromKeyframeTimeChange);
      connect(key, &NodeKeyframe::ValueChanged, this, &Node::InvalidateFromKeyframeValueChange);
      connect(key, &NodeKeyframe::TypeChanged, this, &Node::InvalidateFromKeyframeTypeChanged);
      connect(key, &NodeKeyframe::BezierControlInChanged, this, &Node::InvalidateFromKeyframeBezierInChange);
      connect(key, &NodeKeyframe::BezierControlOutChanged, this, &Node::InvalidateFromKeyframeBezierOutChange);

      emit KeyframeAdded(key);
      ParameterValueChanged(i, GetRangeAffectedByKeyframe(key));
    } else if (event->type() == QEvent::ChildRemoved) {
      TimeRange time_affected = GetRangeAffectedByKeyframe(key);

      disconnect(key, &NodeKeyframe::TimeChanged, this, &Node::InvalidateFromKeyframeTimeChange);
      disconnect(key, &NodeKeyframe::ValueChanged, this, &Node::InvalidateFromKeyframeValueChange);
      disconnect(key, &NodeKeyframe::TypeChanged, this, &Node::InvalidateFromKeyframeTypeChanged);
      disconnect(key, &NodeKeyframe::BezierControlInChanged, this, &Node::InvalidateFromKeyframeBezierInChange);
      disconnect(key, &NodeKeyframe::BezierControlOutChanged, this, &Node::InvalidateFromKeyframeBezierOutChange);

      emit KeyframeRemoved(key);

      GetImmediate(key->input(), key->element())->remove_keyframe(key);
      ParameterValueChanged(i, time_affected);
    }
  } else if (NodeGizmo *gizmo = dynamic_cast<NodeGizmo*>(event->child())) {
    if (event->type() == QEvent::ChildAdded) {
      gizmos_.append(gizmo);
    } else if (event->type() == QEvent::ChildRemoved) {
      gizmos_.removeOne(gizmo);
    }
  }
}

void Node::InvalidateFromKeyframeBezierInChange()
{
  NodeKeyframe* key = static_cast<NodeKeyframe*>(sender());
  const NodeKeyframeTrack& track = GetTrackFromKeyframe(key);
  int keyframe_index = track.indexOf(key);

  rational start = RATIONAL_MIN;
  rational end = key->time();

  if (keyframe_index > 0) {
    start = track.at(keyframe_index - 1)->time();
  }

  ParameterValueChanged(key->key_track_ref().input(), TimeRange(start, end));
}

void Node::InvalidateFromKeyframeBezierOutChange()
{
  NodeKeyframe* key = static_cast<NodeKeyframe*>(sender());
  const NodeKeyframeTrack& track = GetTrackFromKeyframe(key);
  int keyframe_index = track.indexOf(key);

  rational start = key->time();
  rational end = RATIONAL_MAX;

  if (keyframe_index < track.size() - 1) {
    end = track.at(keyframe_index + 1)->time();
  }

  ParameterValueChanged(key->key_track_ref().input(), TimeRange(start, end));
}

void Node::InvalidateFromKeyframeTimeChange()
{
  NodeKeyframe* key = static_cast<NodeKeyframe*>(sender());
  NodeInputImmediate* immediate = GetImmediate(key->input(), key->element());
  TimeRange original_range = GetRangeAffectedByKeyframe(key);

  TimeRangeList invalidate_range;
  invalidate_range.insert(original_range);

  if (!(original_range.in() < key->time() && original_range.out() > key->time())) {
    // This keyframe needs resorting, store it and remove it from the list
    immediate->remove_keyframe(key);

    // Automatically insertion sort
    immediate->insert_keyframe(key);

    // Invalidate new area that the keyframe has been moved to
    invalidate_range.insert(GetRangeAffectedByKeyframe(key));
  }

  // Invalidate entire area surrounding the keyframe (either where it currently is, or where it used to be before it
  // was resorted in the if block above)
  foreach (const TimeRange& r, invalidate_range) {
    ParameterValueChanged(key->key_track_ref().input(), r);
  }

  emit KeyframeTimeChanged(key);
}

void Node::InvalidateFromKeyframeValueChange()
{
  NodeKeyframe* key = static_cast<NodeKeyframe*>(sender());
  ParameterValueChanged(key->key_track_ref().input(), GetRangeAffectedByKeyframe(key));

  emit KeyframeValueChanged(key);
}

void Node::InvalidateFromKeyframeTypeChanged()
{
  NodeKeyframe* key = static_cast<NodeKeyframe*>(sender());
  const NodeKeyframeTrack& track = GetTrackFromKeyframe(key);

  if (track.size() == 1) {
    // If there are no other frames, the interpolation won't do anything
    return;
  }

  // Invalidate entire range
  ParameterValueChanged(key->key_track_ref().input(), GetRangeAroundIndex(key->input(), track.indexOf(key), key->track(), key->element()));

  emit KeyframeTypeChanged(key);
}

void Node::SetValueAtTime(const NodeInput &input, const rational &time, const QVariant &value, int track, MultiUndoCommand *command, bool insert_on_all_tracks_if_no_key)
{
  if (input.IsKeyframing()) {
    rational node_time = time;

    NodeKeyframe* existing_key = input.GetKeyframeAtTimeOnTrack(node_time, track);

    if (existing_key) {
      command->add_child(new NodeParamSetKeyframeValueCommand(existing_key, value));
    } else {
      // No existing key, create a new one
      int nb_tracks = NodeValue::get_number_of_keyframe_tracks(input.node()->GetInputDataType(input.input()));
      for (int i=0; i<nb_tracks; i++) {
        QVariant track_value;

        if (i == track) {
          track_value = value;
        } else if (!insert_on_all_tracks_if_no_key) {
          continue;
        } else {
          track_value = input.node()->GetSplitValueAtTimeOnTrack(input.input(), node_time, i, input.element());
        }

        NodeKeyframe* new_key = new NodeKeyframe(node_time,
                                                 track_value,
                                                 input.node()->GetBestKeyframeTypeForTimeOnTrack(NodeKeyframeTrackReference(input, i), node_time),
                                                 i,
                                                 input.element(),
                                                 input.input());

        command->add_child(new NodeParamInsertKeyframeCommand(input.node(), new_key));
      }
    }
  } else {
    command->add_child(new NodeParamSetStandardValueCommand(NodeKeyframeTrackReference(input, track), value));
  }
}

bool FindPathInternal(std::list<NodeInput> &vec, Node *from, Node *to, int &path_index)
{
  for (auto it=from->output_connections().cbegin(); it!=from->output_connections().cend(); it++) {
    const NodeInput &next = it->second;

    vec.push_back(next);

    if (next.node() == to) {
      // Found a path! Determine if it's the index we want
      if (path_index == 0) {
        // It is!
        return true;
      } else {
        // It isn't, keep looking...
        path_index--;
      }
    }

    if (FindPathInternal(vec, next.node(), to, path_index)) {
      return true;
    }

    vec.pop_back();
  }

  return false;
}

std::list<NodeInput> Node::FindPath(Node *from, Node *to, int path_index)
{
  std::list<NodeInput> v;

  FindPathInternal(v, from, to, path_index);

  return v;
}

bool Node::ValueHint::load(QXmlStreamReader *reader)
{
  uint version = 0;
  XMLAttributeLoop(reader, attr) {
    version = attr.value().toUInt();
  }

  Q_UNUSED(version)

  while (XMLReadNextStartElement(reader)) {
    if (reader->name() == QStringLiteral("types")) {
      QVector<NodeValue::Type> types;
      while (XMLReadNextStartElement(reader)) {
        if (reader->name() == QStringLiteral("type")) {
          types.append(static_cast<NodeValue::Type>(reader->readElementText().toInt()));
        } else {
          reader->skipCurrentElement();
        }
      }
      this->set_type(types);
    } else if (reader->name() == QStringLiteral("index")) {
      this->set_index(reader->readElementText().toInt());
    } else if (reader->name() == QStringLiteral("tag")) {
      this->set_tag(reader->readElementText());
    } else {
      reader->skipCurrentElement();
    }
  }

  return true;
}

void Node::ValueHint::save(QXmlStreamWriter *writer) const
{
  writer->writeAttribute(QStringLiteral("version"), QString::number(1));

  writer->writeStartElement(QStringLiteral("types"));

  for (auto it=this->types().cbegin(); it!=this->types().cend(); it++) {
    writer->writeTextElement(QStringLiteral("type"), QString::number(*it));
  }

  writer->writeEndElement(); // types

  writer->writeTextElement(QStringLiteral("index"), QString::number(this->index()));

  writer->writeTextElement(QStringLiteral("tag"), this->tag());
}

bool Node::Position::load(QXmlStreamReader *reader)
{
  bool got_pos_x = false;
  bool got_pos_y = false;

  while (XMLReadNextStartElement(reader)) {
    if (reader->name() == QStringLiteral("x")) {
      this->position.setX(reader->readElementText().toDouble());
      got_pos_x = true;
    } else if (reader->name() == QStringLiteral("y")) {
      this->position.setY(reader->readElementText().toDouble());
      got_pos_y = true;
    } else if (reader->name() == QStringLiteral("expanded")) {
      this->expanded = reader->readElementText().toInt();
    } else {
      reader->skipCurrentElement();
    }
  }

  return got_pos_x && got_pos_y;
}

void Node::Position::save(QXmlStreamWriter *writer) const
{
  writer->writeTextElement(QStringLiteral("x"), QString::number(this->position.x()));
  writer->writeTextElement(QStringLiteral("y"), QString::number(this->position.y()));
  writer->writeTextElement(QStringLiteral("expanded"), QString::number(this->expanded));
}

}
