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

//#define DJ_DBG
#include "djFileBrowser.h"
#include <djButtonWidget.h>
#include <djTextWidget.h>
#include <djMenuWidget.h>
#include <FL/Fl.H>
#include <FL/Fl_Menu_.H>
#include <FL/fl_draw.H>

namespace dj {

FileBrowserDialog::FileBrowserDialog() :
  DialogBase("File Browser"),
  _info(true), _seq(true), _showHidden(false),
  _sort(FILE_SORT_TIME), _sortReverse(true), _sortDirsFirst(true),
  _images(FILE_BROWSER_IMAGES_LOW),
  _pin(false),
  _menuItems(0),
  _callbacks(true)
{
  // Widgets.
  //
  _menuItems = new MenuItems;
  _menubar = new MenubarWidget;
  Widget * dirFrameWidget = new Widget;
  dirFrameWidget->box(FL_THIN_UP_BOX);
  _dirWidget = new TextWidget;
  _dirWidget->tooltip("Directory selection.");
  ToolButtonWidget * upWidget = new ToolButtonWidget("up");
  upWidget->tooltip("Go up a directory. (Shortcut: Left Arrow)");
  ToolButtonWidget * prevWidget = new ToolButtonWidget("left");
  prevWidget->tooltip("Return to previous directory. (Shortcut: Backspace)");
  ToolButtonWidget * reloadWidget = new ToolButtonWidget("reload");
  reloadWidget->tooltip("Reload the current directory. (Shortcut: R)");
  _browserWidget = new FileBrowserWidget;
  Widget * fileFrameWidget = new Widget;
  fileFrameWidget->box(FL_THIN_UP_BOX);
  _widget = new TextWidget;
  _widget->tooltip("Selection.");
  Widget * dialogFrameWidget = new Widget;
  dialogFrameWidget->box(FL_THIN_UP_BOX);
  _pinWidget = new CheckButtonWidget("&Pin");
  PushButtonWidget * okWidget = new PushButtonWidget("&Ok");
  PushButtonWidget * closeWidget = new PushButtonWidget("Clos&e");

  // Layout.
  //
  VLayout * windowLayout = new VLayout(this);
  windowLayout->margin(0);
  windowLayout->spacing(0);
  windowLayout->add(_menubar);
  windowLayout->add(dirFrameWidget);
  HLayout * dirFrameLayout = new HLayout(dirFrameWidget);
  dirFrameLayout->add(_dirWidget);
  dirFrameLayout->stretch(_dirWidget);
  HLayout * toolLayout = new HLayout(dirFrameLayout);
  toolLayout->spacing(0);
  toolLayout->margin(0);
  toolLayout->add(upWidget);
  toolLayout->add(prevWidget);
  toolLayout->add(reloadWidget);
  windowLayout->add(_browserWidget);
  windowLayout->stretch(_browserWidget);
  windowLayout->add(fileFrameWidget);
  HLayout * fileFrameLayout = new HLayout(fileFrameWidget);
  fileFrameLayout->add(_widget);
  windowLayout->add(dialogFrameWidget);
  HLayout * dialogLayout = new HLayout(dialogFrameWidget);
  dialogLayout->add(_pinWidget);
  dialogLayout->stretchAdd();
  dialogLayout->add(okWidget);
  dialogLayout->add(closeWidget);
  
  // Initialize.
  //
  _dirPrev = dirPathAbsolute(".");
  _dir.set(_dirPrev);
  dirUpdate();
  menuUpdate();
  _widget->take_focus();
  size(Vec2i(550, 500));
  
  // Callbacks.
  //
  _dirWidget->sig().set(this, dirCallback);
  upWidget->sig().set(this, upCallback);
  prevWidget->sig().set(this, prevCallback);
  reloadWidget->sig().set(this, reloadCallback);
  _browserWidget->sig().set(this, browserCallback);
  _browserWidget->actionSig().set(this, browserActionCallback);
  _widget->sig().set(this, widgetCallback);
  _widget->actionSig().set(this, okCallback);
  _pinWidget->sig().set(this, pinCallback);
  okWidget->sig().set(this, okCallback);
  closeWidget->sig().set(this, closeCallback);
}

FileBrowserDialog::~FileBrowserDialog()
{
  die();
  if (_menuItems) delete _menuItems;
}

void FileBrowserDialog::die()
{
  questionDialogDel(this, bookmarkListDelAllCallback);
}

void FileBrowserDialog::set(const Str & in)
{
  _widget->set(in);
}

const Str FileBrowserDialog::get() const
{
  return _dir.get() + _widget->get();
}

void FileBrowserDialog::dir(const Str & in)
{
  DJ_DBG_FNC("dj::FileBrowserDialog::dir");
  DJ_DBG_PRN("in = " << in);
  
  _dirPrev = _dir.get();
  _dir.set(dirPathAbsolute(in));
  dirUpdate();
}

const Str FileBrowserDialog::dir() const { return _dir.get(); }

void FileBrowserDialog::info(bool in)
{
  if (in == _info) return;
  _info = in;
  dirUpdate();
  menuUpdate();
}

void FileBrowserDialog::seq(bool in)
{
  if (in == _seq) return;
  _seq = in;
  dirUpdate();
  menuUpdate();
}

void FileBrowserDialog::showHidden(bool in)
{
  if (in == _showHidden) return;
  _showHidden = in;
  dirUpdate();
  menuUpdate();
}

void FileBrowserDialog::sort(FILE_SORT in)
{
  if (in == _sort) return;
  _sort = in;
  dirUpdate();
  menuUpdate();
}

void FileBrowserDialog::sortReverse(bool in)
{
  if (in == _sortReverse) return;
  _sortReverse = in;
  dirUpdate();
  menuUpdate();
}

void FileBrowserDialog::sortDirsFirst(bool in)
{
  if (in == _sortDirsFirst) return;
  _sortDirsFirst = in;
  dirUpdate();
  menuUpdate();
}

void FileBrowserDialog::images(FILE_BROWSER_IMAGES in)
{
  if (in == _images) return;
  _images = in;
  dirUpdate();
  menuUpdate();
}

void FileBrowserDialog::bookmarkList(const StrList & in)
{
  if (in == _bookmarkList) return;
  _bookmarkList = in;
  menuUpdate();
}

void FileBrowserDialog::pin(bool in)
{
  if (in == _pin) return;
  _pin = in;
  _pinWidget->set(_pin);
}

int FileBrowserDialog::handle(int in)
{
  switch (in) {
  
    case FL_KEYBOARD:
      switch (Fl::event_key()) {
        case FL_Left: upCallback(true); return 1;
      }
      break;
  }
  
  return Window::handle(in);
}

void FileBrowserDialog::widgetCallback(const Str & in)
{
  if (! _callbacks) return;
  FileInfo fileInfo(in);
  const Str path = fileInfo.path();
  if (! path.isNull()) {
    dir(path);
    set(fileInfo.get(-1, false));
  }
}

void FileBrowserDialog::dirCallback(const Str & in)
{
  if (! _callbacks) return;
  dir(in);
}

void FileBrowserDialog::browserCallback(const FileBrowserItem * in)
{
  if (! _callbacks || ! in) return;
  set(in->get().get(-1, false));
}

void FileBrowserDialog::browserActionCallback(const FileBrowserItem * in)
{
  if (! _callbacks || ! in) return;
  
  DJ_DBG_FNC("dj::FileBrowserDialog::browserActionCallback");
  
  const FileInfo & fileInfo = in->get();
  const Str fileName = fileInfo.get(-1, false);
  if (FileInfo::DIR == fileInfo.type()) {
    _dirPrev = _dir.get();
    _dir.cd(fileName);
    dirUpdate();
  } else {
    set(fileName);
    okCallback(true);
  }
}

void FileBrowserDialog::upCallback(bool)
{
  if (! _callbacks) return;
  _dirPrev = _dir.get();
  _dir.up();
  dirUpdate();
}

void FileBrowserDialog::upCallback() { upCallback(true); }

void FileBrowserDialog::prevCallback(bool)
{
  if (! _callbacks) return;
  
  // XXX Can't pass by reference, make a copy.
  //
  dir(Str(_dirPrev));
}

void FileBrowserDialog::prevCallback() { prevCallback(true); }

void FileBrowserDialog::homeCallback(bool)
{
  if (! _callbacks) return;
  _dirPrev = _dir.get();
  _dir.home();
  dirUpdate();
}

void FileBrowserDialog::homeCallback() { homeCallback(true); }

void FileBrowserDialog::workCallback(bool)
{
  if (! _callbacks) return;
  _dirPrev = _dir.get();
  _dir.set(dirPathAbsolute("."));
  dirUpdate();
}

void FileBrowserDialog::workCallback() { workCallback(true); }

void FileBrowserDialog::rootCallback(bool)
{
  if (! _callbacks) return;
  _dirPrev = _dir.get();
  _dir.root();
  dirUpdate();
}

void FileBrowserDialog::rootCallback() { rootCallback(true); }

void FileBrowserDialog::reloadCallback(bool)
{
  if (! _callbacks) return;
  dirUpdate();
}

void FileBrowserDialog::reloadCallback() { reloadCallback(true); }

void FileBrowserDialog::infoCallback() { if (_callbacks) info(! _info); }
void FileBrowserDialog::seqCallback() { if (_callbacks) seq(! _seq); }
void FileBrowserDialog::showHiddenCallback()
  { if (_callbacks) showHidden(! _showHidden); }

void FileBrowserDialog::sortCallback()
{
  if (! _callbacks) return;
  sort(FILE_SORT(dj::find(_menuSortId, _menubar->value()))); 
}

void FileBrowserDialog::sortReverseCallback()
  { if (_callbacks) sortReverse(! _sortReverse); }
void FileBrowserDialog::sortDirsFirstCallback()
  { if (_callbacks) sortDirsFirst(! _sortDirsFirst); }

void FileBrowserDialog::imagesCallback()
{
  if (! _callbacks) return;
  images(FILE_BROWSER_IMAGES(dj::find(_menuImagesId, _menubar->value()))); 
}

void FileBrowserDialog::bookmarkListAddCallback()
{
  if (! _callbacks) return;
  _bookmarkList += _dirWidget->get();
  menuUpdate();
}

void FileBrowserDialog::bookmarkListDelCallback()
{
  if (! _callbacks) return;
  const int i = dj::find(_bookmarkList, _dirWidget->get());
  if (-1 == i) return;
  _bookmarkList.del(i);
  menuUpdate();
}

void FileBrowserDialog::bookmarkListDelAllCallback(bool)
{
  if (! _callbacks) return;
  _bookmarkList.del();
  menuUpdate();
}

void FileBrowserDialog::bookmarkListDelAllCallback()
{
  if (! _callbacks) return;
  if (_bookmarkList.size())
    questionDialog("Delete all bookmarks?", this, bookmarkListDelAllCallback);
}

void FileBrowserDialog::bookmarkListCallback()
{
  if (! _callbacks) return;
  dir(_bookmarkList[dj::find(_menuBookmarkListId, _menubar->value())]);
}

void FileBrowserDialog::pinCallback(bool)
{
  if (! _callbacks) return;
  pin(_pinWidget->get());
}

void FileBrowserDialog::okCallback(bool)
{
  if (! _callbacks) return;
  _sig.emit(get());
  if (! _pin) hide();
}

void FileBrowserDialog::closeCallback(bool)
{
  if (! _callbacks) return;
  hide();
}
void FileBrowserDialog::dirUpdate()
{
  DJ_DBG_FNC("dj::FileBrowserDialog::dirUpdate");
  DJ_DBG_PRN("dir = " << _dir.get());
  
  _callbacks = false;
  
  fl_cursor(FL_CURSOR_WAIT);
  Fl::flush();

  _dirWidget->set(_dir.get());

  List<FileInfo> itemList = _dir.itemList();
  if (_seq) itemList = dj::seq(itemList);
  if (! _showHidden)
    itemList = dj::filter(itemList, FILE_FILTER_FILES | FILE_FILTER_DIRS);
  itemList = dj::sort(itemList, _sort, _sortReverse);
  if (_sortDirsFirst) itemList = dj::sortDirsFirst(itemList);
  _browserWidget->set(itemList, _info, _images);

  fl_cursor(FL_CURSOR_DEFAULT);

  _callbacks = true;
}

void FileBrowserDialog::menuUpdate()
{
  DJ_DBG_FNC("dj::FileBrowserDialog::menuUpdate");
  DJ_DBG_PRN("info = " << _info);
  DJ_DBG_PRN("seq = " << _seq);
  DJ_DBG_PRN("show hidden = " << _showHidden);
  DJ_DBG_PRN("sort = " << _sort);
  DJ_DBG_PRN("sort reverse = " << _sortReverse);
  DJ_DBG_PRN("sort dirs first = " << _sortDirsFirst);
  DJ_DBG_PRN("images = " << _images);
  
  _menuItems->del();
  
  // Directory menu.
  //
  _menuItems->add("&Directory", 0, 0, 0, FL_SUBMENU);
  _menuItems->add("&Up", FL_Left, upCallback, this);
  _menuItems->add(
    "&Previous", FL_BackSpace, prevCallback, this, FL_MENU_DIVIDER
  );
  _menuItems->add("&Home", ',', homeCallback, this);
  _menuItems->add("&Work", '.', workCallback, this);
  _menuItems->add("Roo&t", '/', rootCallback, this, FL_MENU_DIVIDER);
  _menuItems->add("&Reload", 'r', reloadCallback, this);
  _menuItems->end();
  
  // Options menu.
  //
  _menuItems->add("&Options", 0, 0, 0, FL_SUBMENU);
  _menuItems->add(
    "&Information", 'i', infoCallback, this,
    FL_MENU_TOGGLE | (_info ? FL_MENU_VALUE : 0)
  );
  _menuItems->add(
    "Se&quence", 'q', seqCallback, this,
    FL_MENU_TOGGLE | (_seq ? FL_MENU_VALUE : 0)
  );
  _menuItems->add(
    "Hidde&n", 'n', showHiddenCallback, this,
    FL_MENU_TOGGLE | (_showHidden ? FL_MENU_VALUE : 0) | FL_MENU_DIVIDER
  );
  _menuItems->add("&Sort", 0, 0, 0, FL_SUBMENU);
  _menuSortId.del();
  const StrList sortStr = fileSortStr();
  for (ConstStrListIt it = sortStr; it.isValid(); ++it)
    _menuSortId += _menuItems->add(
      it(), '1' + it.count(), sortCallback, this,
      FL_MENU_RADIO | (it.count() == _sort ? FL_MENU_VALUE : 0)
    );
  _menuItems->end();
  _menuItems->add(
    "Sort &Reverse", '`', sortReverseCallback, this,
    FL_MENU_TOGGLE | (_sortReverse ? FL_MENU_VALUE : 0)
  );
  _menuItems->add(
    "Sort &Directories First", 'd', sortDirsFirstCallback, this,
    FL_MENU_TOGGLE | (_sortDirsFirst ? FL_MENU_VALUE : 0) | FL_MENU_DIVIDER
  );
  _menuItems->add("&Images", 0, 0, 0, FL_SUBMENU);
  _menuImagesId.del();
  const StrList imagesStr = fileBrowserImagesStr();
  for (ConstStrListIt it = imagesStr; it.isValid(); ++it)
    _menuImagesId += _menuItems->add(
      it(), 0, imagesCallback, this,
      FL_MENU_RADIO | (it.count() == _images ? FL_MENU_VALUE : 0)
    );
  _menuItems->end();
  _menuItems->end();
  
  // Bookmarks menu.
  //
  _menuItems->add("&Bookmarks", 0, 0, 0, FL_SUBMENU);
  _menuItems->add("&Add", 'b', bookmarkListAddCallback, this);
  _menuItems->add("&Delete", 0, bookmarkListDelCallback, this);
  _menuItems->add(
    "Delete A&ll", 0, bookmarkListDelAllCallback, this, FL_MENU_DIVIDER
  );
  _menuBookmarkListId.del();
  for (StrListIt it = _bookmarkList; it.isValid(); ++it)
    _menuBookmarkListId += _menuItems->add(
      it(), it.count() < 12 ? (FL_F + it.count() + 1) : 0,
      bookmarkListCallback, this
    );
  _menuItems->end();
  
  _menuItems->end();
  _menuItems->set(_menubar);
}

void FileBrowserDialog::prefs(const Str & name, const Prefs & in)
{
  Prefs prefs(in, name);
  StrList data;
  
  prefs.get("info", &_info, _info);
  prefs.get("seq", &_seq, _seq);
  prefs.get("showHidden", &_showHidden, _showHidden);
  
  data.del();
  if (prefs.get("sort", &data))
    inflateStr(data, &_sort, fileSortStr());
  
  prefs.get("sortReverse", &_sortReverse, _sortReverse);
  prefs.get("sortDirsFirst", &_sortDirsFirst, _sortDirsFirst);
  
  data.del();
  if (prefs.get("images", &data))
    inflateStr(data, &_images, fileBrowserImagesStr());
  
  prefs.get("bookmarkList", &_bookmarkList, _bookmarkList);
  
  dirUpdate();
  menuUpdate();
}

void FileBrowserDialog::prefs(const Str & name, Prefs * in)
{
  Prefs prefs(in, name);
  
  prefs.set("info", _info);
  prefs.set("seq", _seq);
  prefs.set("showHidden", _showHidden);
  prefs.set("sort", deflateStr(_sort, fileSortStr()));
  prefs.set("sortReverse", _sortReverse);
  prefs.set("sortDirsFirst", _sortDirsFirst);
  prefs.set("images", deflateStr(_images, fileBrowserImagesStr()));
  prefs.set("bookmarkList", _bookmarkList);
}

}
