/***

  Olive - Non-Linear Video Editor
  Copyright (C) 2019 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 "widget/timelinewidget/timelinewidget.h"

#include "node/block/gap/gap.h"
#include "widget/nodeview/nodeviewundo.h"

TimelineWidget::RippleTool::RippleTool(TimelineWidget* parent) :
  PointerTool(parent)
{
  SetMovementAllowed(false);
}

void TimelineWidget::RippleTool::MouseReleaseInternal(TimelineViewMouseEvent *event)
{
  Q_UNUSED(event)

  // For ripple operations, all ghosts will be moving the same way
  Timeline::MovementMode movement_mode = parent()->ghost_items_.first()->mode();

  QUndoCommand* command = new QUndoCommand();

  // Find earliest point to ripple around
  foreach (TimelineViewGhostItem* ghost, parent()->ghost_items_) {
    Block* b = Node::ValueToPtr<Block>(ghost->data(TimelineViewGhostItem::kAttachedBlock));

    if (b == nullptr) {
      // This is a gap we are creating

      // Make sure there's actually a gap being created
      if (ghost->AdjustedLength() > 0) {
        GapBlock* gap = new GapBlock();
        gap->set_length_and_media_out(ghost->AdjustedLength());
        new NodeAddCommand(static_cast<NodeGraph*>(parent()->GetConnectedNode()->parent()), gap, command);

        Block* block_to_append_gap_to = Node::ValueToPtr<Block>(ghost->data(TimelineViewGhostItem::kReferenceBlock));

        new TrackInsertBlockAfterCommand(parent()->GetTrackFromReference(ghost->Track()),
                                         gap,
                                         block_to_append_gap_to,
                                         command);
      }
    } else {
      // This was a Block that already existed
      if (ghost->AdjustedLength() > 0) {
        if (movement_mode == Timeline::kTrimIn) {
          // We'll need to shift the media in point too
          new BlockResizeWithMediaInCommand(b, ghost->AdjustedLength(), command);
        } else {
          new BlockResizeCommand(b, ghost->AdjustedLength(), command);
        }
      } else {
        // Assumed the Block was a Gap and it was reduced to zero length, remove it here
        new TrackRippleRemoveBlockCommand(parent()->GetTrackFromReference(ghost->Track()), b, command);

        new NodeRemoveWithExclusiveDeps(static_cast<NodeGraph*>(b->parent()), b, command);
      }
    }
  }

  Core::instance()->undo_stack()->pushIfHasChildren(command);
}

rational TimelineWidget::RippleTool::FrameValidateInternal(rational time_movement, const QVector<TimelineViewGhostItem *> &ghosts)
{
  // Only validate trimming, and we don't care about "overwriting" since the ripple tool is nondestructive
  time_movement = ValidateInTrimming(time_movement, ghosts, false);
  time_movement = ValidateOutTrimming(time_movement, ghosts, false);

  return time_movement;
}

void TimelineWidget::RippleTool::InitiateGhosts(TimelineViewBlockItem *clicked_item,
                                                Timeline::MovementMode trim_mode,
                                                bool allow_gap_trimming)
{
  Q_UNUSED(allow_gap_trimming)

  PointerTool::InitiateGhosts(clicked_item, trim_mode, true);

  if (parent()->ghost_items_.isEmpty()) {
    return;
  }

  // Find the earliest ripple
  rational earliest_ripple = RATIONAL_MAX;

  foreach (TimelineViewGhostItem* ghost, parent()->ghost_items_) {
    rational ghost_ripple_point;

    if (trim_mode == Timeline::kTrimIn) {
      ghost_ripple_point = ghost->In();
    } else {
      ghost_ripple_point = ghost->Out();
    }

    earliest_ripple = qMin(earliest_ripple, ghost_ripple_point);
  }

  // For each track that does NOT have a ghost, we need to make one for Gaps
  foreach (TrackOutput* track, parent()->GetConnectedNode()->Tracks()) {
    // Determine if we've already created a ghost on this track
    bool ghost_on_this_track_exists = false;

    foreach (TimelineViewGhostItem* ghost, parent()->ghost_items_) {
      if (parent()->GetTrackFromReference(ghost->Track()) == track) {
        ghost_on_this_track_exists = true;
        break;
      }
    }

    // If there's no ghost on this track, create one
    if (!ghost_on_this_track_exists) {
      // Find the block that starts just before the ripple point, and ends either on or just after it
      Block* block_before_ripple = track->NearestBlockBefore(earliest_ripple);

      // If block is null, there will be no blocks after to ripple
      if (block_before_ripple != nullptr) {
        TimelineViewGhostItem* ghost;

        TrackReference track_ref(track->track_type(), track->Index());

        if (block_before_ripple->type() == Block::kGap) {
          // If this Block is already a Gap, ghost it now
          ghost = AddGhostFromBlock(block_before_ripple, track_ref, trim_mode);
        } else {
          // If there's no gap here, we'll need to create one
          ghost = AddGhostFromNull(block_before_ripple->out(), block_before_ripple->out(), track_ref, trim_mode);
          ghost->setData(TimelineViewGhostItem::kReferenceBlock, Node::PtrToValue(block_before_ripple));
        }

        ghost->SetInvisible(true);
      }
    }
  }
}
