

#include "toonzqt/keyframenavigator.h"
#include "toonzqt/styleselection.h"

#include "toonzqt/gutil.h"
#include "toonz/txsheet.h"
#include "toonz/txshcolumn.h"
#include "toonz/tstageobjectkeyframe.h"
#include "toonz/stageobjectutil.h"
#include "toonz/tapplication.h"

#include "tpixelutils.h"
#include "tfx.h"
#include "tparamcontainer.h"
#include "tspectrumparam.h"
#include "ttonecurveparam.h"

#include <QPainter>
#include <QPixmap>
#include <QIcon>

#ifdef _MSC_VER
#pragma warning(disable : 4251)
#endif

#include <memory>

#include <QAction>
#include <QMouseEvent>

using namespace std;

//=============================================================================
// KeyframeNavigator
//-----------------------------------------------------------------------------

KeyframeNavigator::KeyframeNavigator(QWidget *parent, TFrameHandle *frameHandle)
    : QToolBar(parent), m_frameHandle(frameHandle), m_panel(0) {
  setLayoutDirection(Qt::LeftToRight);
  setIconSize(QSize(20, 20));
  setObjectName("keyFrameNavigator");

  // Previous key button
  QIcon prevKeyIcon = createQIcon("prevkey");
  m_actPreviewKey   = new QAction(prevKeyIcon, tr("Previous Key"), this);
  connect(m_actPreviewKey, SIGNAL(triggered()), SLOT(togglePrevKeyAct()));
  addAction(m_actPreviewKey);
  QWidget *prevWidget = widgetForAction(m_actPreviewKey);
  prevWidget->setObjectName("PreviousKey");

  // Key buttons with proper icon states
  // Note: We're using one createQIcon call and making the actions checkable
  QIcon keyIcon = createQIcon("keyframe");
  m_actKeyNo    = new QAction(keyIcon, tr("Set Key"), this);
  m_actKeyNo->setCheckable(true);
  connect(m_actKeyNo, SIGNAL(triggered()), SLOT(toggleKeyAct()));
  addAction(m_actKeyNo);
  QWidget *keyNoWidget = widgetForAction(m_actKeyNo);
  keyNoWidget->setObjectName("KeyNo");

  QIcon keyPartialIcon = createQIcon("keyframe_partial");
  m_actKeyPartial      = new QAction(keyPartialIcon, tr("Set Key"), this);
  m_actKeyPartial->setCheckable(true);
  connect(m_actKeyPartial, SIGNAL(triggered()), SLOT(toggleKeyAct()));
  addAction(m_actKeyPartial);
  QWidget *keyPartialWidget = widgetForAction(m_actKeyPartial);
  keyPartialWidget->setObjectName("KeyPartial");

  QIcon keyTotalIcon = createQIcon(
      "keyframe");  // Same as keyNo but will be set to checked state
  m_actKeyTotal = new QAction(keyTotalIcon, tr("Set Key"), this);
  m_actKeyTotal->setCheckable(true);
  connect(m_actKeyTotal, SIGNAL(triggered()), SLOT(toggleKeyAct()));
  addAction(m_actKeyTotal);
  QWidget *keyTotalWidget = widgetForAction(m_actKeyTotal);
  keyTotalWidget->setObjectName("KeyTotal");

  // Next key button
  QIcon nextKeyIcon = createQIcon("nextkey");
  m_actNextKey      = new QAction(nextKeyIcon, tr("Next Key"), this);
  connect(m_actNextKey, SIGNAL(triggered()), SLOT(toggleNextKeyAct()));
  addAction(m_actNextKey);
  QWidget *nextWidget = widgetForAction(m_actNextKey);
  nextWidget->setObjectName("NextKey");
}

//-----------------------------------------------------------------------------

void KeyframeNavigator::update() {
  // Prev button
  m_actPreviewKey->setDisabled(!hasPrev());

  bool isFullKey = isFullKeyframe();
  bool isKey     = isKeyframe();

  // Hide all key buttons first
  m_actKeyNo->setVisible(false);
  m_actKeyPartial->setVisible(false);
  m_actKeyTotal->setVisible(false);

  // Show the appropriate button based on the current state
  if (isKey && !isFullKey) {
    m_actKeyPartial->setVisible(true);
    // Set the action to checked to use the "On" icon state
    m_actKeyPartial->setChecked(true);
  } else if (isFullKey) {
    m_actKeyTotal->setVisible(true);
    // Set the action to checked to use the "On" icon state
    m_actKeyTotal->setChecked(true);
  } else {  // !isKey && !isFullKey
    m_actKeyNo->setVisible(true);
    // Make sure it's unchecked to use the "Off" icon state
    m_actKeyNo->setChecked(false);
  }

  // Next button
  m_actNextKey->setDisabled(!hasNext());
}

//-----------------------------------------------------------------------------

void KeyframeNavigator::showEvent(QShowEvent *e) {
  update();
  if (!m_frameHandle) return;
  connect(m_frameHandle, SIGNAL(frameSwitched()), this, SLOT(update()));

  connect(m_frameHandle, SIGNAL(triggerNextKeyframe(QWidget *)), this,
          SLOT(onNextKeyframe(QWidget *)));
  connect(m_frameHandle, SIGNAL(triggerPrevKeyframe(QWidget *)), this,
          SLOT(onPrevKeyframe(QWidget *)));
  if (!m_panel || m_panel == nullptr) {
    QWidget *panel = this->parentWidget();
    while (panel) {
      if (panel->windowType() == Qt::WindowType::SubWindow ||
          panel->windowType() == Qt::WindowType::Tool) {
        m_panel = panel;
        break;
      }
      panel = panel->parentWidget();
    }
  }
}

//-----------------------------------------------------------------------------

void KeyframeNavigator::hideEvent(QHideEvent *e) {
  if (!m_frameHandle) return;
  disconnect(m_frameHandle);

  disconnect(m_frameHandle, SIGNAL(triggerNextKeyframe(QWidget *)), this,
             SLOT(onNextKeyframe(QWidget *)));
  disconnect(m_frameHandle, SIGNAL(triggerPrevKeyframe(QWidget *)), this,
             SLOT(onPrevKeyframe(QWidget *)));
  m_panel = nullptr;
}

void KeyframeNavigator::onNextKeyframe(QWidget *panel) {
  if (!m_panel || m_panel != panel) return;
  toggleNextKeyAct();
}

void KeyframeNavigator::onPrevKeyframe(QWidget *panel) {
  if (!m_panel || m_panel != panel) return;
  togglePrevKeyAct();
}

//=============================================================================
// ViewerKeyframeNavigator
//-----------------------------------------------------------------------------

TStageObject *ViewerKeyframeNavigator::getStageObject() const {
  if (!m_xsheetHandle || !m_objectHandle) return 0;

  TStageObjectId objectId = m_objectHandle->getObjectId();
  TXsheet *xsh            = m_xsheetHandle->getXsheet();
  // Se e' una colonna sound non posso settare chiavi
  if (objectId.isColumn()) {
    TXshColumn *column = xsh->getColumn(objectId.getIndex());
    if (column && column->getSoundColumn()) return 0;
  }
  return xsh->getStageObject(objectId);
}

//-----------------------------------------------------------------------------

bool ViewerKeyframeNavigator::hasNext() const {
  TStageObject *pegbar = getStageObject();
  if (!pegbar) return false;
  int r0, r1;
  pegbar->getKeyframeRange(r0, r1);
  return r0 <= r1 && getCurrentFrame() < r1;
}

//-------------------------------------------------------------------

bool ViewerKeyframeNavigator::hasPrev() const {
  TStageObject *pegbar = getStageObject();
  if (!pegbar) return false;
  int r0, r1;
  pegbar->getKeyframeRange(r0, r1);
  return r0 <= r1 && getCurrentFrame() > r0;
}

//-------------------------------------------------------------------

bool ViewerKeyframeNavigator::hasKeyframes() const {
  TStageObject *pegbar = getStageObject();
  if (!pegbar) return false;
  int r0, r1;
  pegbar->getKeyframeRange(r0, r1);
  return r0 <= r1;
}

//-------------------------------------------------------------------

bool ViewerKeyframeNavigator::isKeyframe() const {
  TStageObject *pegbar = getStageObject();
  if (!pegbar) return false;
  return pegbar->isKeyframe(getCurrentFrame());
}

//-------------------------------------------------------------------

bool ViewerKeyframeNavigator::isFullKeyframe() const {
  TStageObject *pegbar = getStageObject();
  if (!pegbar) return false;
  return pegbar->isFullKeyframe(getCurrentFrame());
}

//-------------------------------------------------------------------

void ViewerKeyframeNavigator::toggle() {
  TStageObject *pegbar = getStageObject();
  if (!pegbar) return;
  int frame = getCurrentFrame();

  if (pegbar->isFullKeyframe(frame)) {
    TStageObject::Keyframe key = pegbar->getKeyframe(frame);
    pegbar->removeKeyframeWithoutUndo(frame);
    UndoRemoveKeyFrame *undo =
        new UndoRemoveKeyFrame(pegbar->getId(), frame, key, m_xsheetHandle);
    undo->setObjectHandle(m_objectHandle);
    TUndoManager::manager()->add(undo);
  } else {
    UndoSetKeyFrame *undo =
        new UndoSetKeyFrame(pegbar->getId(), frame, m_xsheetHandle);
    pegbar->setKeyframeWithoutUndo(frame);
    undo->setObjectHandle(m_objectHandle);
    TUndoManager::manager()->add(undo);
  }
  m_objectHandle->notifyObjectIdChanged(false);
}

//-------------------------------------------------------------------

void ViewerKeyframeNavigator::goNext() {
  TStageObject *pegbar = getStageObject();
  if (!pegbar) return;
  int frame = getCurrentFrame();
  TStageObject::KeyframeMap keyframes;
  pegbar->getKeyframes(keyframes);
  TStageObject::KeyframeMap::iterator it;
  for (it = keyframes.begin(); it != keyframes.end(); ++it)
    if (it->first > frame) {
      setCurrentFrame(it->first);
      return;
    }
}

//-------------------------------------------------------------------

void ViewerKeyframeNavigator::goPrev() {
  TStageObject *pegbar = getStageObject();
  if (!pegbar) return;
  int frame = getCurrentFrame();
  TStageObject::KeyframeMap keyframes;
  pegbar->getKeyframes(keyframes);
  TStageObject::KeyframeMap::reverse_iterator it;
  for (it = keyframes.rbegin(); it != keyframes.rend(); ++it)
    if (it->first < frame) {
      setCurrentFrame(it->first);
      return;
    }
}

//-----------------------------------------------------------------------------

void ViewerKeyframeNavigator::showEvent(QShowEvent *e) {
  if (!m_objectHandle) return;
  connect(m_objectHandle, SIGNAL(objectSwitched()), this, SLOT(update()));
  connect(m_objectHandle, SIGNAL(objectChanged(bool)), this, SLOT(update()));
  KeyframeNavigator::showEvent(e);
}

//-----------------------------------------------------------------------------

void ViewerKeyframeNavigator::hideEvent(QHideEvent *e) {
  if (!m_objectHandle) return;
  disconnect(m_objectHandle);
  KeyframeNavigator::hideEvent(e);
}

//=============================================================================
namespace {
//-----------------------------------------------------------------------------

class UndoPaletteSetKeyFrame final : public TUndo {
  int m_frame;
  int m_styleId;
  TPaletteHandle *m_paletteHandle;

public:
  UndoPaletteSetKeyFrame(int styleId, int frame, TPaletteHandle *paletteHandle)
      : m_frame(frame), m_styleId(styleId), m_paletteHandle(paletteHandle) {}

  void undo() const override { setKeyFrame(); }
  void redo() const override { setKeyFrame(); }
  int getSize() const override { return sizeof(*this); }

protected:
  void setKeyFrame() const {
    TPalette *palette = m_paletteHandle->getPalette();
    if (palette->isKeyframe(m_styleId, m_frame))
      palette->clearKeyframe(m_styleId, m_frame);
    else
      palette->setKeyframe(m_styleId, m_frame);
    m_paletteHandle->notifyPaletteChanged();
  }
};
//-----------------------------------------------------------------------------
}  // namespace
//=============================================================================

//=============================================================================
// PaletteKeyframeNavigator
//-----------------------------------------------------------------------------

bool PaletteKeyframeNavigator::hasNext() const {
  TPalette *palette = getPalette();
  if (!palette) return false;
  int styleId = getStyleIndex();
  int frame   = getCurrentFrame();
  int n       = palette->getKeyframeCount(styleId);
  for (int i = n - 1; i >= 0; i--) {
    int f = palette->getKeyframe(styleId, i);
    if (f > frame)
      return true;
    else if (f <= frame)
      return false;
  }
  return false;
}

//-----------------------------------------------------------------------------

bool PaletteKeyframeNavigator::hasPrev() const {
  TPalette *palette = getPalette();
  if (!palette) return false;
  int styleId = getStyleIndex();
  int frame   = getCurrentFrame();
  int n       = palette->getKeyframeCount(styleId);
  for (int i = 0; i < n; i++) {
    int f = palette->getKeyframe(styleId, i);
    if (f < frame)
      return true;
    else if (f >= frame)
      return false;
  }
  return false;
}

//-----------------------------------------------------------------------------

bool PaletteKeyframeNavigator::hasKeyframes() const {
  TPalette *palette = getPalette();
  if (!palette) return false;
  return palette->getKeyframeCount(getStyleIndex()) > 0;
}

//-----------------------------------------------------------------------------

bool PaletteKeyframeNavigator::isKeyframe() const {
  TPalette *palette = getPalette();
  if (!palette) return false;
  int frame = getCurrentFrame();
  return palette->isKeyframe(getStyleIndex(), frame);
}

//-----------------------------------------------------------------------------

void PaletteKeyframeNavigator::toggle() {
  TPalette *palette = getPalette();
  if (!palette) return;

  int styleId = getStyleIndex(), frame = getCurrentFrame();

  std::unique_ptr<UndoPaletteSetKeyFrame> undo(
      new UndoPaletteSetKeyFrame(styleId, frame, m_paletteHandle));
  undo->redo();

  TUndoManager::manager()->add(undo.release());
}

//-----------------------------------------------------------------------------

void PaletteKeyframeNavigator::goNext() {
  TPalette *palette = getPalette();
  if (!palette) return;
  int styleId = getStyleIndex();
  int frame   = getCurrentFrame();
  int n       = palette->getKeyframeCount(styleId);
  for (int i = 0; i < n; i++) {
    int f = palette->getKeyframe(styleId, i);
    if (f > frame) {
      setCurrentFrame(f);
      break;
    }
  }
}

//-----------------------------------------------------------------------------

void PaletteKeyframeNavigator::goPrev() {
  TPalette *palette = getPalette();
  if (!palette) return;
  int styleId = getStyleIndex();
  int frame   = getCurrentFrame();
  int n       = palette->getKeyframeCount(styleId);
  for (int i = n - 1; i >= 0; i--) {
    int f = palette->getKeyframe(styleId, i);
    if (f < frame) {
      setCurrentFrame(f);
      break;
    }
  }
}

//-----------------------------------------------------------------------------

void PaletteKeyframeNavigator::showEvent(QShowEvent *e) {
  if (!m_paletteHandle) return;
  connect(m_paletteHandle, SIGNAL(paletteSwitched()), this, SLOT(update()));
  connect(m_paletteHandle, SIGNAL(paletteChanged()), this, SLOT(update()));
  connect(m_paletteHandle, SIGNAL(colorStyleSwitched()), this, SLOT(update()));
  KeyframeNavigator::showEvent(e);
}

//-----------------------------------------------------------------------------

void PaletteKeyframeNavigator::hideEvent(QHideEvent *e) {
  if (!m_paletteHandle) return;
  disconnect(m_paletteHandle);
  KeyframeNavigator::hideEvent(e);
}

//=============================================================================
namespace {
//-----------------------------------------------------------------------------

//! Se non c'e' un keyframe successivo ritorna frame
int getNextKeyframe(TFxP fx, int frame) {
  if (!fx) return frame;
  int targetFrame = frame;
  for (int i = 0; i < fx->getParams()->getParamCount(); i++) {
    TParamP param = fx->getParams()->getParam(i);
    int j         = param->getNextKeyframe(frame);
    if (j < 0) continue;
    int f = (int)param->keyframeIndexToFrame(j);
    if (targetFrame == frame || f < targetFrame) targetFrame = f;
  }
  return targetFrame;
}

//-----------------------------------------------------------------------------

//! Se non c'e' un keyframe precedente ritorna frame
int getPrevKeyframe(TFxP fx, int frame) {
  if (!fx) return frame;
  int targetFrame = frame;
  for (int i = 0; i < fx->getParams()->getParamCount(); i++) {
    TParamP param = fx->getParams()->getParam(i);
    int j         = param->getPrevKeyframe(frame);
    if (j < 0) continue;
    int f = (int)param->keyframeIndexToFrame(j);
    if (targetFrame == frame || f > targetFrame) targetFrame = f;
  }
  return targetFrame;
}

//-----------------------------------------------------------------------------

void setKeyframe(TFxP fx, int frame, bool on) {
  if (fx)
    for (int i = 0; i < fx->getParams()->getParamCount(); i++) {
      TParamP param = fx->getParams()->getParam(i);
      if (TDoubleParamP dp = param) {
        if (on)
          dp->setValue(frame, dp->getValue(frame));
        else
          dp->deleteKeyframe(frame);
      }
    }
}

//-----------------------------------------------------------------------------
}  // anonymous namespace
//=============================================================================

//=============================================================================
// FxKeyframeNavigator
//-----------------------------------------------------------------------------

bool FxKeyframeNavigator::hasNext() const {
  TFx *fx = getFx();
  if (!fx)
    return false;
  else
    return getNextKeyframe(fx, getCurrentFrame()) > getCurrentFrame();
}

//-----------------------------------------------------------------------------

bool FxKeyframeNavigator::hasPrev() const {
  TFx *fx = getFx();
  if (!fx)
    return false;
  else
    return getPrevKeyframe(fx, getCurrentFrame()) < getCurrentFrame();
}

//-----------------------------------------------------------------------------

bool FxKeyframeNavigator::hasKeyframes() const {
  TFx *fx = getFx();
  if (!fx) return false;
  for (int i = 0; i < fx->getParams()->getParamCount(); i++) {
    TParamP param = fx->getParams()->getParam(i);
    if (param->hasKeyframes()) return true;
  }
  return false;
}

//-----------------------------------------------------------------------------

bool FxKeyframeNavigator::isKeyframe() const {
  TFx *fx = getFx();
  if (!fx) return false;
  for (int i = 0; i < fx->getParams()->getParamCount(); i++) {
    TParamP param = fx->getParams()->getParam(i);
    if (param->isKeyframe(getCurrentFrame())) return true;
  }
  return false;
}

//-----------------------------------------------------------------------------

bool FxKeyframeNavigator::isFullKeyframe() const {
  TFx *fx = getFx();
  if (!fx) return false;
  int keyFrameCount        = 0;
  int animatableParamCount = 0;
  for (int i = 0; i < fx->getParams()->getParamCount(); i++) {
    TParamP param = fx->getParams()->getParam(i);
    if (param->isAnimatable()) {
      animatableParamCount++;
      if (param->isKeyframe(getCurrentFrame())) keyFrameCount++;
    }
  }
  return animatableParamCount > 0 && keyFrameCount == animatableParamCount;
}

//-----------------------------------------------------------------------------

void FxKeyframeNavigator::toggle() {
  TFx *fx = getFx();
  if (!fx) return;
  int i, paramCount = fx->getParams()->getParamCount();

  // determino in quale caso ci troviamo:
  // il frame corrente non e' keyframe di nessun parametro (isKeyframe=false),
  // di qualche parametro o di tutti i parametri (isFullKeyframe=true)
  bool isFullKeyframe = true;
  bool isKeyframe     = false;
  int frame           = getCurrentFrame();
  for (i = 0; i < paramCount; i++) {
    TParamP param = fx->getParams()->getParam(i);
    if (!param->isAnimatable()) continue;
    if (param->isKeyframe(frame))
      isKeyframe = true;
    else
      isFullKeyframe = false;
  }
  if (!isKeyframe) isFullKeyframe = false;

  // modifico lo stato: nokeyframe->full, full->no, partial->full
  bool on = (!isKeyframe || (isKeyframe && !isFullKeyframe));
  for (i = 0; i < fx->getParams()->getParamCount();
       i++) {  // TODO. spostare questo codice in TParam
    TParamP param = fx->getParams()->getParam(i);
    if (TDoubleParamP dp = param) {
      if (on)
        dp->setValue(frame, dp->getValue(frame));
      else
        dp->deleteKeyframe(frame);
    } else if (TRangeParamP rp = param) {
      if (on)
        rp->setValue(frame, rp->getValue(frame));
      else
        rp->deleteKeyframe(frame);
    } else if (TPointParamP pp = param) {
      if (on)
        pp->setValue(frame, pp->getValue(frame));
      else
        pp->deleteKeyframe(frame);
    } else if (TPixelParamP pip = param) {
      if (on)
        pip->setValue(frame, pip->getValue(frame));
      else
        pip->deleteKeyframe(frame);
    } else if (TSpectrumParamP sp = param) {
      if (on)
        sp->setValue(frame, sp->getValue(frame), false);
      else
        sp->deleteKeyframe(frame);
    } else if (TToneCurveParamP tcp = param) {
      if (on)
        tcp->setValue(frame, tcp->getValue(frame), false);
      else
        tcp->deleteKeyframe(frame);
    }
  }
  m_fxHandle->notifyFxChanged();
}

//-----------------------------------------------------------------------------

void FxKeyframeNavigator::goNext() {
  TFx *fx = getFx();
  if (!fx) return;
  int frame = getNextKeyframe(fx, getCurrentFrame());
  if (frame > getCurrentFrame()) {
    setCurrentFrame(frame);
    //		m_fxHandle->notifyFxChanged();
  }
}

//-----------------------------------------------------------------------------

void FxKeyframeNavigator::goPrev() {
  TFx *fx = getFx();
  if (!fx) return;
  int frame = getPrevKeyframe(fx, getCurrentFrame());
  if (frame < getCurrentFrame()) {
    setCurrentFrame(frame);
    //		m_fxHandle->notifyFxChanged();
  }
}

//-----------------------------------------------------------------------------

void FxKeyframeNavigator::showEvent(QShowEvent *e) {
  if (!m_fxHandle) return;
  connect(m_fxHandle, SIGNAL(fxSwitched()), this, SLOT(update()));
  connect(m_fxHandle, SIGNAL(fxChanged()), this, SLOT(update()));
  KeyframeNavigator::showEvent(e);
}

//-----------------------------------------------------------------------------

void FxKeyframeNavigator::hideEvent(QHideEvent *e) {
  if (!m_fxHandle) return;
  disconnect(m_fxHandle);
  KeyframeNavigator::hideEvent(e);
}
