/*------------------------------------------------------------------------------
 src/bin/djview/windowPlayback.cxx

 Copyright (c) 2004-2006 Darby Johnston.
 All rights reserved.
 Use is subject to license terms.
------------------------------------------------------------------------------*/

//#define DJ_DBG
#include "window.h"
#include "widget.h"
#include <djButtonWidget.h>
#include <djNumWidget.h>
#include <FL/Fl.H>

namespace djview {

void Window::playback(PLAYBACK in)
{
  if (in == _playback) return;
  
  DJ_DBG_FNC("Window::playback");
  DJ_DBG_PRN("in = " << in);
  
  _playback = in;
  playbackUpdate();
  menuUpdate();
}

void Window::playbackUpdate()
{
  DJ_DBG_FNC("Window::playbackUpdate");

  _callbacks = false;
  
  for (int i = 0; i < 3; ++i)
    _playbackButton[i]->set(i == _playback);
  
  Fl::remove_idle(timerCallback, this);
  switch (_playback) {
    case PLAYBACK_REVERSE:
    case PLAYBACK_FORWARD:
      _playbackTimerInit = true;
      Fl::add_idle(timerCallback, this);
      break;
    case PLAYBACK_STOP: break;
  }

  _callbacks = true;
}

void Window::playbackFrame(int in)
{
  const int tmp = djview::playbackFrame(
    in,
    _playbackFrameStart, _playbackFrameEnd,
    _playbackIn, _playbackOut,
    _playbackInFrame, _playbackOutFrame
  );
  if (tmp == _playbackFrame) return;
  
  DJ_DBG_FNC("Window::playbackFrame");
  DJ_DBG_PRN("in = " << in);
  
  _playbackFrame = tmp;
  
  playbackFrameUpdate();
  imageUpdate();
}

void Window::playbackFrameStart()
{
  DJ_DBG_FNC("Window::playbackFrameStart");
  
  playback(PLAYBACK_STOP);
  playbackFrame(_playbackIn ? _playbackInFrame : _playbackFrameStart);
}

void Window::playbackFrameEnd()
{
  DJ_DBG_FNC("Window::playbackFrameEnd");
  
  playback(PLAYBACK_STOP);
  playbackFrame(_playbackOut ? _playbackOutFrame : _playbackFrameEnd);
}

void Window::playbackFramePrev(int in)
{
  DJ_DBG_FNC("Window::playbackFramePrev");
  DJ_DBG_PRN("in = " << in);
  
  playback(PLAYBACK_STOP);
  playbackFrame(_playbackFrame - in);
}

void Window::playbackFrameNext(int in)
{
  DJ_DBG_FNC("Window::playbackFrameNext");
  DJ_DBG_PRN("in = " << in);
  
  playback(PLAYBACK_STOP);
  playbackFrame(_playbackFrame + in);
}

void Window::playbackStopCallback(bool)
{
  if (! _callbacks) return;
  playback(PLAYBACK_STOP);
}

void Window::playbackForwardCallback(bool)
{
  if (! _callbacks) return;
  playback(PLAYBACK_FORWARD);
}

void Window::playbackReverseCallback(bool)
{
  if (! _callbacks) return;
  playback(PLAYBACK_REVERSE);
}

void Window::playbackStopCallback() { playbackStopCallback(true); }
void Window::playbackForwardCallback() { playbackForwardCallback(true); }
void Window::playbackReverseCallback() { playbackReverseCallback(true); }

void Window::playbackShuttleStartCallback(bool)
{
  if (! _callbacks) return;
  
  playback(PLAYBACK_STOP);
  
  _playbackShuttle = true;
  
  _playbackShuttleSpeed = 0.0;
  _playbackTimerInit = true;
  Fl::remove_idle(timerCallback, this);
  Fl::add_idle(timerCallback, this);
}

void Window::playbackShuttleStopCallback(bool)
{
  if (! _callbacks) return;
  
  _playbackShuttle = false;
  
  Fl::remove_idle(timerCallback, this);
}

void Window::playbackShuttleValueCallback(int in)
{
  if (! _callbacks) return;
  //_playbackShuttleSpeed = pow(double(abs(in)), 1.2) * (in >= 0 ? 1.0 : -1.0);
  _playbackShuttleSpeed = double(in);
  _playbackTimerFrame =
    uint64_t(_playbackTimer.seconds() * _playbackShuttleSpeed);
}

void Window::playbackFrameStartCallback(bool)
{
  if (! _callbacks) return;
  playbackFrameStart();
}

void Window::playbackFrameStartCallback() { playbackFrameStartCallback(true); }

void Window::playbackFrameEndCallback(bool)
{
  if (! _callbacks) return;
  playbackFrameEnd();
}

void Window::playbackFrameEndCallback() { playbackFrameEndCallback(true); }

void Window::playbackFramePrevCallback(bool)
{
  if (! _callbacks) return;
  DJ_DBG_FNC("Window::playbackFramePrevCallback");
  playbackFramePrev();
}

void Window::playbackFramePrevCallback() { playbackFramePrevCallback(true); }

void Window::playbackFramePrev10Callback()
{
  if (! _callbacks) return;
  DJ_DBG_FNC("Window::playbackFramePrev10Callback");
  playbackFramePrev(10);
}

void Window::playbackFrameNextCallback(bool)
{
  if (! _callbacks) return;
  DJ_DBG_FNC("Window::playbackFrameNextCallback");
  playbackFrameNext();
}

void Window::playbackFrameNext10Callback()
{
  if (! _callbacks) return;
  DJ_DBG_FNC("Window::playbackFrameNext10Callback");
  playbackFrameNext(10);
}

void Window::playbackFrameNextCallback() { playbackFrameNextCallback(true); }

void Window::playbackFrameCallback(int in)
{
  if (! _callbacks) return;
  playback(PLAYBACK_STOP);
  playbackFrame(in);
}

void Window::playbackFrameShuttleStartCallback(bool)
{
  if (! _callbacks) return;
  playback(PLAYBACK_STOP);
  _playbackFrameTmp = _playbackFrame;
}

void Window::playbackFrameShuttleValueCallback(int in)
{
  if (! _callbacks) return;
  playbackFrame(_playbackFrameTmp + in);
}

void Window::playbackSliderCallback(int in)
{
  if (! _callbacks) return;
  playback(PLAYBACK_STOP);
  playbackFrame(in);
}

void Window::playbackFrameUpdate()
{
  DJ_DBG_FNC("Window::playbackFrameUpdate");
  
  _callbacks = false;
  
  _playbackFrameWidget->set(_playbackFrame);
  _playbackSlider->set(_playbackFrame);
  
  _callbacks = true;
}

void Window::playbackIn(bool in)
{
  if (in == _playbackIn) return;
  
  DJ_DBG_FNC("Window::playbackIn");
  DJ_DBG_PRN("in = " << in);
  
  _playbackIn = in;
  playbackFrame(_playbackFrame);
  playbackTimeUpdate();
  menuUpdate();
}

void Window::playbackInCallback(bool in)
{
  if (! _callbacks) return;
  playbackIn(in);
}

void Window::playbackInCallback()
{
  playbackInCallback(! _playbackIn);
}

void Window::playbackInFrame(int in)
{
  if (in == _playbackInFrame) return;
  
  DJ_DBG_FNC("Window::playbackInFrame");
  DJ_DBG_PRN("in = " << in);
  
  _playbackInFrame = in;
  playbackFrame(_playbackFrame);
  playbackTimeUpdate();
  menuUpdate();
}

void Window::playbackInFrameCallback(int in)
{
  if (! _callbacks) return;
  playbackInFrame(in);
}

void Window::playbackInMark() { playbackInFrame(_playbackFrame); }

void Window::playbackInMarkCallback(bool)
{
  if (! _callbacks) return;
  playbackInMark();
}

void Window::playbackInMarkCallback() { playbackInMarkCallback(true); }

void Window::playbackOut(bool in)
{
  if (in == _playbackOut) return;
  
  DJ_DBG_FNC("Window::playbackOut");
  DJ_DBG_PRN("in = " << in);
  
  _playbackOut = in;
  playbackFrame(_playbackFrame);
  playbackTimeUpdate();
  menuUpdate();
}

void Window::playbackOutCallback(bool in)
{
  if (! _callbacks) return;
  playbackOut(in);
}

void Window::playbackOutCallback()
{
  playbackOutCallback(! _playbackOut);
}

void Window::playbackOutFrame(int in)
{
  if (in == _playbackOutFrame) return;
  
  DJ_DBG_FNC("Window::playbackOutFrame");
  DJ_DBG_PRN("in = " << in);
  
  _playbackOutFrame = in;
  playbackFrame(_playbackFrame);
  playbackTimeUpdate();
  menuUpdate();
}

void Window::playbackOutFrameCallback(int in)
{
  if (! _callbacks) return;
  playbackOutFrame(in);
}

void Window::playbackOutMark() { playbackOutFrame(_playbackFrame); }

void Window::playbackOutMarkCallback(bool)
{
  if (! _callbacks) return;
  playbackOutMark();
}

void Window::playbackOutMarkCallback() { playbackOutMarkCallback(true); }

void Window::playbackTimeUpdate()
{
  DJ_DBG_FNC("Window::playbackTimeUpdate");
  
  _callbacks = false;

  _playbackSlider->range(_playbackFrameStart, _playbackFrameEnd);
  _playbackSlider->inOutPoint(_playbackIn, _playbackOut);
  _playbackSlider->inOutFrame(_playbackInFrame, _playbackOutFrame);
  _playbackFrameStartWidget->set(toStr(_playbackFrameStart));
  _playbackFrameEndWidget->set(toStr(_playbackFrameEnd));
  _playbackInButton->set(_playbackIn);
  _playbackInFrameWidget->set(_playbackInFrame);
  _playbackOutButton->set(_playbackOut);
  _playbackOutFrameWidget->set(_playbackOutFrame);

  _callbacks = true;
}

void Window::playbackSpeed(double in)
{
  if (compare(in, _playbackSpeed)) return;
  
  DJ_DBG_FNC("Window::playbackSpeed");
  DJ_DBG_PRN("in = " << in);
  
  _playbackSpeed = in;
  playbackSpeedUpdate();
  playbackUpdate();
  menuUpdate();
}

void Window::playbackSpeedCallback(double in)
{
  if (! _callbacks) return;
  playbackSpeed(in);
}

void Window::playbackEveryFrame(bool in)
{
  if (in == _playbackEveryFrame) return;
  
  DJ_DBG_FNC("Window::playbackEveryFrame");
  DJ_DBG_PRN("in = " << in);
  
  _playbackEveryFrame = in;
  playbackSpeedUpdate();
  menuUpdate();
}

void Window::playbackEveryFrameCallback(bool in)
{
  if (! _callbacks) return;
  playbackEveryFrame(in);
}

void Window::playbackEveryFrameCallback()
{
  playbackEveryFrameCallback(! _playbackEveryFrame);
}

void Window::playbackSpeedUpdate()
{
  DJ_DBG_FNC("Window::playbackSpeedUpdate");
  
  _callbacks = false;

  _playbackSpeedWidget->set(_playbackSpeed);
  _playbackSpeedRealWidget->set(0.0);
  _playbackEveryFrameWidget->set(_playbackEveryFrame);
  
  _callbacks = true;
}

void Window::timerCallback()
{
  DJ_DBG_FNC("Window::timerEvent");
  DJ_DBG_PRN("playback = " << _playback);
  DJ_DBG_PRN("every frame = " << _playbackEveryFrame);

  if (_playbackTimerInit) {
    _playbackTimer.start();
    _playbackTimerFrame = 0;
    _playbackSpeedTimer = _playbackTimer;
    _playbackSpeedCounter = 0;
    _playbackDroppedFrames = 0;
    _playbackTimerInit = false;
  } else
    _playbackTimer.check();

  const double speed =
    _playbackShuttle ? _playbackShuttleSpeed : _playbackSpeed;
  DJ_DBG_PRN("speed = " << speed);

  // XXX Overflow?
  //
  const uint64_t absoluteFrame = uint64_t(_playbackTimer.seconds() * speed);
  int inc = absoluteFrame - _playbackTimerFrame;
  DJ_DBG_PRN("inc = " << inc);
  if (! inc) return;

  // Calculate current frame.
  //
  _playbackTimerFrame = absoluteFrame;
  if (PLAYBACK_REVERSE == _playback) inc = -inc;
  if (_playbackEveryFrame) inc = inc > 0 ? 1 : (inc < 0 ? -1 : 0);
  else _playbackDroppedFrames |= inc > 1;
  _playbackSpeedCounter += abs(inc);
  playbackFrame(_playbackFrame + inc);
  
  // Calculate real playback speed.
  //
  _playbackSpeedTimer.check();
  if (_playbackSpeedTimer.seconds() >= 1.0) {
    _playbackSpeedReal =
      _playbackSpeedCounter / _playbackSpeedTimer.seconds();

    _playbackSpeedRealWidget->set(_playbackSpeedReal);
    _playbackSpeedRealWidget->droppedFrames(
      _playbackEveryFrame ? false : _playbackDroppedFrames
    );

    _playbackSpeedTimer.start();
    _playbackSpeedCounter = 0;
    _playbackDroppedFrames = 0;
  }
}

}
