/*------------------------------------------------------------------------------
 src/bin/djview/window.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 "magnifyTool.h"
#include "app.h"
#include <djImageOpStack.h>
#include <djButtonWidget.h>
#include <djTextWidget.h>
#include <djNumWidget.h>
#include <djShuttleWidget.h>
#include <djMenuWidget.h>
#include <FL/Fl.H>

namespace djview {

Window::Window() :
  dj::Window(djviewApp->name()),
  _imageLoad(0), _imageRef(0), _image(0),
    _imageMirror(ImageOpMirror::NONE), _imageRotate(ImageOpRotate90::ROTATE_0),
    _imageOp(0),
  _fileProxy(0), _filePixelFormat(djviewApp->filePixelFormat()),
    _filePixelType(djviewApp->filePixelType()),
  _windowControls(true),
  _viewHud(false),
  _playback(PLAYBACK_STOP), _playbackShuttle(false),
  _playbackFrame(0), _playbackFrameTmp(0),
  _playbackFrameStart(0), _playbackFrameEnd(0),
  _playbackIn(false), _playbackOut(false), _playbackInFrame(0),
    _playbackOutFrame(0),
  _playbackSpeed(24.0), _playbackShuttleSpeed(0.0), _playbackSpeedReal(0.0),
    _playbackEveryFrame(djviewApp->playbackEveryFrame()),
    _playbackDroppedFrames(false),
  _playbackTimerInit(true), _playbackTimerFrame(0), _playbackSpeedCounter(0),
  _toolMode(false),
  _menuItems(0),
  _viewWidget(0),
  _callbacks(true)
{
  DJ_DBG_FNC("Window::Window");
  
  // Widgets.
  //
  _menuItems = new MenuItems;
  _menubar = new MenubarWidget;
  _toolbar = new Widget;
  _toolbar->box(FL_THIN_UP_BOX);
  _viewWidget = new ViewWidget;
  _playbar = new Widget;
  _playbar->box(FL_THIN_UP_BOX);
  
  // File widgets.
  //
  ToolButtonWidget * fileOpenButton = new ToolButtonWidget("fileOpen");
  fileOpenButton->tooltip("Open an image. (Shortcut: Ctrl + O)");
  ToolButtonWidget * fileCloseButton = new ToolButtonWidget("fileClose");
  fileCloseButton->tooltip("Close current image. (Shortcut: Ctrl + E)");
  
  // Window widgets.
  //
  ToolButtonWidget * windowNewButton = new ToolButtonWidget("windowNew");
  windowNewButton->tooltip("Create a new window. (Shortcut: Ctrl + N)");
  ToolButtonWidget * windowCopyButton = new ToolButtonWidget("windowCopy");
  windowCopyButton->tooltip("Copy the window. (Shortcut: Ctrl + C)");
  ToolButtonWidget * windowDelButton = new ToolButtonWidget("windowClose");
  windowDelButton->tooltip("Close the window. (Shortcut: Ctrl + W)");
  ToolButtonWidget * windowFitButton = new ToolButtonWidget("windowFit");
  windowFitButton->tooltip("Fit window to image. (Shortcut: Ctrl + F)");
  ToolButtonWidget * windowFullscreenButton =
    new ToolButtonWidget("windowFullscreen");
  windowFullscreenButton->tooltip(
	  "Window fullscreen mode. (Shortcut: Ctrl + U)"
	);
  
  // View widgets.
  //
  ToolButtonWidget * viewZoomInButton = new ToolButtonWidget("viewZoomIn");
  viewZoomInButton->tooltip("Zoom view in. (Shortcut: =)");
  ToolButtonWidget * viewZoomOutButton = new ToolButtonWidget("viewZoomOut");
  viewZoomOutButton->tooltip("Zoom view out. (Shortcut: -)");
  ToolButtonWidget * viewZoomResetButton =
    new ToolButtonWidget("viewZoomReset");
  viewZoomResetButton->tooltip("Reset view zoom. (Shortcut: 0)");
  ToolButtonWidget * viewFitButton = new ToolButtonWidget("viewFit");
  viewFitButton->tooltip("Fit view to image. (Shortcut: Insert)");

  // Playback widgets.
  //
  _playbackButton[PLAYBACK_STOP] =
    new ToolButtonWidget("playbackStop");
  _playbackButton[PLAYBACK_STOP]->type(ToolButtonWidget::RADIO);
  //_playbackButton[PLAYBACK_STOP]->shortcut('.');
  _playbackButton[PLAYBACK_STOP]->tooltip("Stop playback. (Shortcut: .)");
  
  _playbackButton[PLAYBACK_FORWARD] =
    new ToolButtonWidget("playbackForward");
  _playbackButton[PLAYBACK_FORWARD]->type(ToolButtonWidget::RADIO);
  //_playbackButton[PLAYBACK_FORWARD]->shortcut('/');
  _playbackButton[PLAYBACK_FORWARD]->tooltip("Play forward. (Shortcut: /)");
  
  _playbackButton[PLAYBACK_REVERSE] =
    new ToolButtonWidget("playbackReverse");
  _playbackButton[PLAYBACK_REVERSE]->type(ToolButtonWidget::RADIO);
  //_playbackButton[PLAYBACK_REVERSE]->shortcut(',');
  _playbackButton[PLAYBACK_REVERSE]->tooltip("Play reverse. (Shortcut: ,)");

  ShuttleWidget * playbackShuttle = new ShuttleWidget;
  playbackShuttle->tooltip("Playback shuttle.");

  _playbackFrameStartWidget = new TextDisplayWidget;
  _playbackFrameStartWidget->tooltip("Start frame.");
  ToolButtonWidget * playbackFrameStartButton =
    new ToolButtonWidget("playbackFrameStart");
  //playbackFrameStartButton->shortcut(';');
  playbackFrameStartButton->tooltip("Go to start frame. (Shortcut: ;)");

  _playbackFrameEndWidget = new TextDisplayWidget;
  _playbackFrameEndWidget->tooltip("End frame.");
  ToolButtonWidget * playbackFrameEndButton =
    new ToolButtonWidget("playbackFrameEnd");
  //playbackFrameEndButton->shortcut('\'');
  playbackFrameEndButton->tooltip("Go to end frame. (Shortcut: ')");
  
  ToolButtonWidget * playbackFramePrevButton =
    new ToolButtonWidget("playbackFramePrev");
  //playbackFramePrevButton->shortcut('[');
  playbackFramePrevButton->type(ToolButtonWidget::REPEAT);
  playbackFramePrevButton->tooltip("Go backward one frame. (Shortcut: [)");
  
  ToolButtonWidget * playbackFrameNextButton =
    new ToolButtonWidget("playbackFrameNext");
  //playbackFrameNextButton->shortcut(']');
  playbackFrameNextButton->type(ToolButtonWidget::REPEAT);
  playbackFrameNextButton->tooltip("Go forward one frame. (Shortcut: ])");
  
  _playbackFrameWidget = new IntWidget;
  _playbackFrameWidget->tooltip("Current frame.");
  ShuttleWidget * playbackFrameShuttle = new ShuttleWidget;
  playbackFrameShuttle->tooltip("Frame shuttle.");
  _playbackSlider = new FrameSliderWidget;
  _playbackSlider->tooltip("Frame slider.");
  
  _playbackInFrameWidget = new IntWidget;
  _playbackInFrameWidget->tooltip("Playback in point.");
  _playbackInButton = new ToolButtonWidget("toolStart");
  _playbackInButton->type(ToolButtonWidget::TOGGLE);
  _playbackInButton->tooltip("Toggle playback in point. (Shortcut: I)");
  ToolButtonWidget * playbackInMarkButton = new ToolButtonWidget("toolX");
  playbackInMarkButton->tooltip(
	  "Mark playback in point. (Shortcut: Shift + I)"
  );
  
  _playbackOutFrameWidget = new IntWidget;
  _playbackOutFrameWidget->tooltip("Playback out point.");
  _playbackOutButton = new ToolButtonWidget("toolEnd");
  _playbackOutButton->type(ToolButtonWidget::TOGGLE);
  _playbackOutButton->tooltip("Toggle playback out point. (Shortcut: O)");
  ToolButtonWidget * playbackOutMarkButton = new ToolButtonWidget("toolX");
  playbackOutMarkButton->tooltip(
	  "Mark playback out point. (Shortcut: Shift + O)"
	);
  
  _playbackSpeedWidget = new FloatWidget;
  _playbackSpeedWidget->tooltip("Playback speed.");
  _playbackSpeedRealWidget = new PlaybackSpeedDisplayWidget;
  _playbackSpeedRealWidget->tooltip("Real playback speed.");
  _playbackEveryFrameWidget = new ToolButtonWidget("toolLock");
  _playbackEveryFrameWidget->type(ToolButtonWidget::TOGGLE);
  _playbackEveryFrameWidget->tooltip("Playback every frame.");
  
  // View widgets.
  //
  _toolModeButton = new ToolButtonWidget("toolMode");
  _toolModeButton->type(ToolButtonWidget::TOGGLE);
  _toolModeButton->tooltip("Tool mode. (Shortcut: T)");

  // Layout.
  //
  VLayout * layout = new VLayout(this);
  layout->spacing(0);
  layout->margin(0);
  layout->add(_menubar);
  layout->add(_toolbar);
  
  HLayout * toolbarLayout = new HLayout(_toolbar);
  toolbarLayout->margin(2);
  toolbarLayout->add(fileOpenButton);
  toolbarLayout->add(fileCloseButton);
  toolbarLayout->add(windowNewButton);
  toolbarLayout->add(windowCopyButton);
  toolbarLayout->add(windowDelButton);
  toolbarLayout->add(windowFitButton);
  toolbarLayout->add(windowFullscreenButton);
  toolbarLayout->add(viewZoomInButton);
  toolbarLayout->add(viewZoomOutButton);
  toolbarLayout->add(viewZoomResetButton);
  toolbarLayout->add(viewFitButton);
  toolbarLayout->add(_toolModeButton);
  
  layout->add(_viewWidget);
  layout->stretch(_viewWidget);

  layout->add(_playbar);
  HLayout * playbarLayout = new HLayout(_playbar);
  playbarLayout->margin(2);
  
  VLayout * leftLayout = new VLayout(playbarLayout);
  leftLayout->spacing(0);
  leftLayout->margin(0);
  
  HLayout * subLayout = new HLayout(leftLayout);
  subLayout->spacing(0);
  subLayout->margin(0);
  subLayout->add(playbackFrameStartButton);
  subLayout->add(playbackFramePrevButton);
  subLayout->add(_playbackButton[PLAYBACK_REVERSE]);
  subLayout->add(_playbackButton[PLAYBACK_STOP]);
  subLayout->add(_playbackButton[PLAYBACK_FORWARD]);
  subLayout->add(playbackShuttle);
  subLayout->add(playbackFrameNextButton);
  subLayout->add(playbackFrameEndButton);
  subLayout->add(playbackFrameShuttle);
  leftLayout->spacerAdd(2);
  leftLayout->stretchAdd();
  
  subLayout = new HLayout(leftLayout);
  subLayout->margin(0);
  subLayout->add(_playbackSpeedWidget);
  subLayout->add(_playbackSpeedRealWidget);
  subLayout->add(_playbackEveryFrameWidget);
  subLayout->stretchAdd();
  
  VLayout * rightLayout = new VLayout(playbarLayout);
  playbarLayout->stretch(rightLayout);
  rightLayout->spacing(0);
  rightLayout->margin(0);
  
  subLayout = new HLayout(rightLayout);
  subLayout->margin(0);
  subLayout->add(_playbackFrameWidget);
  subLayout->add(_playbackSlider);
  subLayout->stretch(_playbackSlider);
  rightLayout->spacerAdd(2);
  rightLayout->stretchAdd();
  
  subLayout = new HLayout(rightLayout);
  subLayout->margin(0);
  subLayout->add(_playbackFrameStartWidget);
  subLayout->add(_playbackInFrameWidget);
  subLayout->add(_playbackInButton);
  subLayout->add(playbackInMarkButton);
  subLayout->spacerAdd();
  subLayout->stretchAdd();
  subLayout->add(playbackOutMarkButton);
  subLayout->add(_playbackOutButton);
  subLayout->add(_playbackOutFrameWidget);
  subLayout->add(_playbackFrameEndWidget);
  
  // Initialize.
  //
  playbackUpdate();
  playbackTimeUpdate();
  playbackSpeedUpdate();
  playbackFrameUpdate();
  toolModeUpdate();
  menuUpdate();
  _viewWidget->take_focus();
  size(sizeHint() + Vec2i(0, 300));
  
  // Callbacks.
  //
  fileOpenButton->sig().set(this, fileOpenCallback);
  fileCloseButton->sig().set(this, fileCloseCallback);
  
  windowNewButton->sig().set(this, windowNewCallback);
  windowCopyButton->sig().set(this, windowCopyCallback);
  windowDelButton->sig().set(this, windowDelCallback);
  windowFitButton->sig().set(this, windowFitCallback);
  windowFullscreenButton->sig().set(this, windowFullscreenCallback);
  
  _viewWidget->toolSig().set(this, viewCallback);
  viewZoomInButton->sig().set(this, viewZoomInCallback);
  viewZoomOutButton->sig().set(this, viewZoomOutCallback);
  viewZoomResetButton->sig().set(this, viewZoomResetCallback);
  viewFitButton->sig().set(this, viewFitCallback);
  
  _playbackButton[PLAYBACK_STOP]->sig().set(this, playbackStopCallback);
  _playbackButton[PLAYBACK_FORWARD]->sig().set(this, playbackForwardCallback);
  _playbackButton[PLAYBACK_REVERSE]->sig().set(this, playbackReverseCallback);
  playbackShuttle->startSig().set(this, playbackShuttleStartCallback);
  playbackShuttle->stopSig().set(this, playbackShuttleStopCallback);
  playbackShuttle->valueSig().set(this, playbackShuttleValueCallback);
  playbackFrameStartButton->sig().set(this, playbackFrameStartCallback);
  playbackFrameEndButton->sig().set(this, playbackFrameEndCallback);
  playbackFramePrevButton->sig().set(this, playbackFramePrevCallback);
  playbackFrameNextButton->sig().set(this, playbackFrameNextCallback);
  _playbackFrameWidget->sig().set(this, playbackFrameCallback);
  playbackFrameShuttle->startSig().set(this, playbackFrameShuttleStartCallback);
  playbackFrameShuttle->valueSig().set(this, playbackFrameShuttleValueCallback);
  _playbackSlider->sig().set(this, playbackSliderCallback);
  _playbackInButton->sig().set(this, playbackInCallback);
  _playbackInFrameWidget->sig().set(this, playbackInFrameCallback);
  playbackInMarkButton->sig().set(this, playbackInMarkCallback);
  _playbackOutButton->sig().set(this, playbackOutCallback);
  _playbackOutFrameWidget->sig().set(this, playbackOutFrameCallback);
  playbackOutMarkButton->sig().set(this, playbackOutMarkCallback);
  _playbackSpeedWidget->sig().set(this, playbackSpeedCallback);
  _playbackEveryFrameWidget->sig().set(this, playbackEveryFrameCallback);
  
  _toolModeButton->sig().set(this, toolModeCallback);
  
  closeSig().set(this, windowDelCallback);
}

Window::~Window()
{
  DJ_DBG_FNC("Window::~Window");
 
  die();
  djviewApp->magnifyTool()->del(_viewWidget);
  if (_menuItems) delete _menuItems;
  if (_imageRef) _imageRef->refDel();
  if (_imageLoad) delete _imageLoad;
}

void Window::die()
{
  DJ_DBG_FNC("Window::die");
  
  Fl::remove_idle(timerCallback, this);
  fileBrowserDel(this, fileOpenCallback);
}

void Window::set(Window * in)
{
  _callbacks = false;
  
  _imageMirror = in->_imageMirror;
  _imageRotate = in->_imageRotate;
  _imageOp = in->_imageOp;

  _fileName = in->_fileName;
  _fileProxy = in->_fileProxy;
  _filePixelFormat = in->_filePixelFormat;
  _filePixelType = in->_filePixelType;

  _windowControls = in->_windowControls;
  
  _viewWidget->viewPositionZoom(
    in->_viewWidget->viewPosition(), in->_viewWidget->viewZoom()
  );
  //_viewWidget->renderer(in->_viewWidget->renderer());
  _viewWidget->hud(in->_viewHud);
  _viewWidget->redraw();
  
  _playback = in->_playback;
  _playbackFrame = in->_playbackFrame;
  _playbackIn = in->_playbackIn;
  _playbackOut = in->_playbackOut;
  _playbackInFrame = in->_playbackInFrame;
  _playbackOutFrame = in->_playbackOutFrame;
  _playbackSpeed = in->_playbackSpeed;
  _playbackEveryFrame = in->_playbackEveryFrame;
  
  _toolMode = in->_toolMode;

  _callbacks = true;
  
  windowControlsUpdate();
  toolModeUpdate();
  menuUpdate();

  fileOpen(_fileName, true);
  size(Vec2i(in->w(), in->h()));
}

void Window::imageUpdate()
{
  DJ_DBG_FNC("Window::imageUpdate");
  DJ_DBG_PRN("frame = " << _playbackFrame);

  const bool cache = djviewApp->imageCacheEnabled();

  // Get image.
  //
  _image = 0;
  ImageRef * oldRef = _imageRef;
  Str key;

  if (cache) {
    key = ImageCache::key(
      _fileName,
      _fileProxy,
      _filePixelFormat, _filePixelType,
      _playbackFrame
    );
    DJ_DBG_PRN("key = " << key);
    _imageRef = djviewApp->imageCache().get(key);
  } else
    _imageRef = 0;

  if (_imageRef) {

    DJ_DBG_PRN("referenced image");
    _image = _imageRef->get();
    
  } else if (_imageLoad) {
    
    DJ_DBG_PRN("loading image");
    if (cache) _image = new Image;
    else _image = &_imageTmp;
    _imageLoad->get(
      _image,
      _fileProxy,
      _filePixelFormat - 1, _filePixelType - 1,
      _playbackFrame
    );
    if (cache) _imageRef = djviewApp->imageCache().create(_image, key);
  }
  
  // Process image.
  //
  if (_image) {
    
    if (_imageMirror) {
      _imageTmp = ImageOpMirror::work(*_image, _imageMirror);
      _image = &_imageTmp;
    }

    if (_imageRotate) {
      _imageTmp = ImageOpRotate90::work(*_image, _imageRotate);
      _image = &_imageTmp;
    }

    if (_imageOp) {
      DJ_DBG_PRN("image operation = " << _imageOp);
      for (
        ConstListIt<ImageOp *> it =
          djviewApp->imageOpStack().stack(_imageOp - 1);
        it.isValid();
        ++it
       ) {
        DJ_DBG_PRN("operation = " << it()->name());
        ImageOp::InputList inputList;
        inputList += _image;
        _imageTmp = it()->work(inputList);
        _image = &_imageTmp;
      }
    }
  }

  // Initialize.
  //
  viewHudUpdate();
  _viewWidget->set(_image);
  _viewWidget->redraw();
  
  // Release old image.
  //
  if (oldRef) oldRef->refDel();
}

void Window::imageMirror(ImageOpMirror::MIRROR in)
{
  if (in == _imageMirror) return;
  _imageMirror = in;
  imageUpdate();
  menuUpdate();
}

void Window::imageRotate(ImageOpRotate90::ROTATE in)
{
  if (in == _imageRotate) return;
  _imageRotate = in;
  imageUpdate();
  menuUpdate();
}

void Window::imageOp(int in)
{
  if (in == _imageOp) return;
  
  DJ_DBG_FNC("Window::imageOp");
  DJ_DBG_PRN("in = " << in);
  
  _imageOp = in;
  imageUpdate();
  menuUpdate();
}

void Window::imageOpUpdate(int in)
{
  DJ_DBG_FNC("Window::imageOpUpdate");
  DJ_DBG_PRN("in = " << in);
  DJ_DBG_PRN("image operation = " << _imageOp);
  DJ_DBG_PRN("playback = " << _playback);
  
  if (in == (_imageOp - 1))
    imageUpdate();
}

void Window::imageOpUpdate()
{
  DJ_DBG_FNC("Window::imageOpUpdate");
  
  const int size = djviewApp->imageOpStack().stackSize();
  if (_imageOp > size)
    imageOp(size);
  else {
    imageUpdate();
    menuUpdate();
  }
}

void Window::windowNew()
{
  djviewApp->windowNew();
}

void Window::windowNewCallback(bool)
{
  if (! _callbacks) return;
  windowNew();
}

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

void Window::windowCopy()
{
  djviewApp->windowCopy(this);
}

void Window::windowCopyCallback(bool)
{
  if (! _callbacks) return;
  windowCopy();
}

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

void Window::windowDel()
{
  djviewApp->windowDel(this);
}

void Window::windowDelCallback(bool)
{
  if (! _callbacks) return;
  windowDel();
}

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

void Window::windowFit()
{
  const Vec2i size = sizeHint(), imageSize = _viewWidget->imageScreenSize();
  this->size(
    Vec2i(
      max(size.x(), imageSize.x()),
      size.y() + (imageSize.y() ? imageSize.y() : 300)
    )
  );
  _viewWidget->viewPosition(Vec2i());
  _viewWidget->redraw();
}

void Window::windowFitCallback(bool)
{
  if (! _callbacks) return;
  windowFit();
}

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

void Window::windowFullscreen()
{
  DJ_DBG_FNC("Window::windowFullscreen");
  
  if (fullscreen()) {
    if (djviewApp->windowFullscreenFit()) {
      _viewWidget->viewReset();
      windowFit();
    } else {
      fullscreen(false);
    }
  } else {
    fullscreen(true);
    if (djviewApp->windowFullscreenFit())
      _viewWidget->viewFit();
  }
  menuUpdate();
}

void Window::windowFullscreenCallback(bool)
{
  if (! _callbacks) return;
  windowFullscreen();
}

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

void Window::windowControls(bool in)
{
  if (in == _windowControls) return;
  _windowControls = in;
  windowControlsUpdate();
  menuUpdate();
}

void Window::windowControlsCallback()
{
  if (! _callbacks) return;
  windowControls(! _windowControls);
}

void Window::windowControlsUpdate()
{
  if (_windowControls) {
    _toolbar->show();
    _playbar->show();
  } else {
    _toolbar->hide();
    _playbar->hide();
  }
  layoutUpdate();
}

void Window::prefsCallback()
{
  if (! _callbacks) return;
  djviewApp->prefsDialog();
}

void Window::aboutCallback()
{
  if (! _callbacks) return;
  djviewApp->aboutDialog();
}

void Window::exitCallback()
{
  if (! _callbacks) return;
  djviewApp->exit();
}

}
