// Gmsh - Copyright (C) 1997-2017 C. Geuzaine, J.-F. Remacle
//
// See the LICENSE.txt file for license information. Please report all
// bugs and problems to the public mailing list <gmsh@onelab.info>.

#include "GmshConfig.h"
#if !defined(HAVE_NO_STDINT_H)
#include <stdint.h>
#elif defined(HAVE_NO_INTPTR_T)
typedef unsigned long intptr_t;
#endif
#include <FL/fl_ask.H>
#include "FlGui.h"
#include "drawContext.h"
#include "fileDialogs.h"
#include "optionWindow.h"
#include "pluginWindow.h"
#include "Context.h"
#include "GModel.h"
#include "PView.h"
#include "PViewData.h"
#include "PViewOptions.h"
#include "Options.h"
#include "OpenFile.h"
#include "Field.h"
#include "OS.h"
#include "onelabGroup.h"
#include "viewButton.h"

static void view_toggle_cb(Fl_Widget *w, void *data)
{
  int num = (intptr_t)data;
  viewButton *but = FlGui::instance()->onelab->getViewButton(num);
  if(but){
    if(Fl::event_state(FL_SHIFT)){
      for(unsigned int i = 0; i < PView::list.size(); i++){
        if(i != num) opt_view_visible(i, GMSH_SET | GMSH_GUI, 0);
        else opt_view_visible(i, GMSH_SET | GMSH_GUI, 1);
      }
    }
    else
      opt_view_visible(num, GMSH_SET, but->value());
    drawContext::global()->draw();
  }
}

static void view_reload(int index)
{
  if(index >= 0 && index < (int)PView::list.size()){
    PView *p = PView::list[index];

    if(StatFile(p->getData()->getFileName())){
      Msg::Error("File '%s' does not exist", p->getData()->getFileName().c_str());
      return;
    }

    int n = PView::list.size();

    // FIXME: use fileIndex
    MergeFile(p->getData()->getFileName());

    if((int)PView::list.size() > n){ // we loaded a new view
      // delete old data and replace with new
      delete p->getData();
      p->setData(PView::list.back()->getData());
      PView::list.back()->setData(0);
      // delete new view
      delete PView::list.back();
      // in case the reloaded view has a different number of time steps
      if(p->getOptions()->timeStep > p->getData()->getNumTimeSteps() - 1)
        p->getOptions()->timeStep = 0;
      p->setChanged(true);
      FlGui::instance()->updateViews(true, true);
    }
  }
}

static void view_reload_cb(Fl_Widget *w, void *data)
{
  view_reload((intptr_t)data);
  drawContext::global()->draw();
}

static void view_reload_all_cb(Fl_Widget *w, void *data)
{
  for(unsigned int i = 0; i < PView::list.size(); i++)
    view_reload(i);
  drawContext::global()->draw();
}

static void view_reload_visible_cb(Fl_Widget *w, void *data)
{
  for(unsigned int i = 0; i < PView::list.size(); i++)
    if(opt_view_visible(i, GMSH_GET, 0))
      view_reload(i);
  drawContext::global()->draw();
}

static void view_sort_cb(Fl_Widget *w, void *data)
{
  PView::sortByName();
  FlGui::instance()->updateViews(true, true);
  drawContext::global()->draw();
}

static void view_remove_other_cb(Fl_Widget *w, void *data)
{
  if(PView::list.empty()) return;
  for(int i = PView::list.size() - 1; i >= 0; i--)
    if(i != (intptr_t)data) delete PView::list[i];
  FlGui::instance()->updateViews(true, true);
  drawContext::global()->draw();
}

static void view_remove_all_cb(Fl_Widget *w, void *data)
{
  if(PView::list.empty()) return;
  int mode = (intptr_t)data;
  if(mode == -1){ // remove all
    if(PView::list.empty()) return;
    while(PView::list.size()) delete PView::list[0];
  }
  else if(mode == -2){ // remove all visible
    for(int i = PView::list.size() - 1; i >= 0; i--)
      if(opt_view_visible(i, GMSH_GET, 0)) delete PView::list[i];
  }
  else if(mode == -3){ // remove all invisible
    for(int i = PView::list.size() - 1; i >= 0; i--)
      if(!opt_view_visible(i, GMSH_GET, 0)) delete PView::list[i];
  }
  else if(mode == -4){ // remove all empty
    for(int i = PView::list.size() - 1; i >= 0; i--)
      if(PView::list[i]->getData()->empty()) delete PView::list[i];
  }
  else if(mode >=0 && mode < (int)PView::list.size()){ // remove by name
    std::string name = PView::list[mode]->getData()->getName();
    for(int i = PView::list.size() - 1; i >= 0; i--)
      if(PView::list[i]->getData()->getName() == name) delete PView::list[i];
  }

  FlGui::instance()->updateViews(true, true);
  drawContext::global()->draw();
}

static void view_remove_cb(Fl_Widget *w, void *data)
{
  delete PView::list[(intptr_t)data];
  FlGui::instance()->updateViews(true, true);
  drawContext::global()->draw();
}

#if defined(HAVE_NATIVE_FILE_CHOOSER)
#  define TT "\t"
#  define NN "\n"
#else
#  define TT " ("
#  define NN ")\t"
#endif

static void view_save_cb(Fl_Widget *w, void *data)
{
  static const char *formats =
    "Gmsh Parsed" TT "*.pos" NN
    "Gmsh Mesh-based" TT "*.pos" NN
    "Gmsh Legacy ASCII" TT "*.pos" NN
    "Gmsh Legacy Binary" TT "*.pos" NN
    "MED" TT "*.rmed" NN
    "STL Surface" TT "*.stl" NN
    "Generic TXT" TT "*.txt" NN;

  PView *view = PView::list[(intptr_t)data];
 test:
  if(fileChooser(FILE_CHOOSER_CREATE, "Save As", formats,
                 view->getData()->getFileName().c_str())){
    std::string name = fileChooserGetName(1);
    if(CTX::instance()->confirmOverwrite) {
      if(!StatFile(name))
        if(!fl_choice("File '%s' already exists.\n\nDo you want to replace it?",
                      "Cancel", "Replace", 0, name.c_str()))
          goto test;
    }
    int format = 0;
    switch(fileChooserGetFilter()){
    case 0: format = 2; break;
    case 1: format = 5; break;
    case 2: format = 0; break;
    case 3: format = 1; break;
    case 4: format = 6; break;
    case 5: format = 3; break;
    case 6: format = 4; break;
    }
    view->write(name, format);
  }
}

#undef TT
#undef NN

static void view_alias_with_options_cb(Fl_Widget *w, void *data)
{
  const bool copyOptions = true;
  new PView(PView::list[(intptr_t)data], copyOptions);
  FlGui::instance()->updateViews(true, true);
  drawContext::global()->draw();
}

static void view_combine_space_all_cb(Fl_Widget *w, void *data)
{
  PView::combine(false, 1, CTX::instance()->post.combineRemoveOrig);
  FlGui::instance()->updateViews(true, true);
  drawContext::global()->draw();
}

static void view_combine_space_visible_cb(Fl_Widget *w, void *data)
{
  PView::combine(false, 0, CTX::instance()->post.combineRemoveOrig);
  FlGui::instance()->updateViews(true, true);
  drawContext::global()->draw();
}

static void view_combine_space_by_name_cb(Fl_Widget *w, void *data)
{
  PView::combine(false, 2, CTX::instance()->post.combineRemoveOrig);
  FlGui::instance()->updateViews(true, true);
  drawContext::global()->draw();
}

static void view_combine_time_all_cb(Fl_Widget *w, void *data)
{
  PView::combine(true, 1, CTX::instance()->post.combineRemoveOrig);
  FlGui::instance()->updateViews(true, true);
  drawContext::global()->draw();
}

static void view_combine_time_visible_cb(Fl_Widget *w, void *data)
{
  PView::combine(true, 0, CTX::instance()->post.combineRemoveOrig);
  FlGui::instance()->updateViews(true, true);
  drawContext::global()->draw();
}

static void view_combine_time_by_name_cb(Fl_Widget *w, void *data)
{
  PView::combine(true, 2, CTX::instance()->post.combineRemoveOrig);
  FlGui::instance()->updateViews(true, true);
  drawContext::global()->draw();
}

static void view_all_visible_cb(Fl_Widget *w, void *data)
{
  int mode = (intptr_t)data;
  std::string name;
  if(mode >= 0) name = PView::list[mode]->getData()->getName();
  for(unsigned int i = 0; i < PView::list.size(); i++)
    opt_view_visible(i, GMSH_SET | GMSH_GUI,
                     (mode == -1) ? 1 :
                     (mode == -2) ? 0 :
                     (mode == -3) ? !opt_view_visible(i, GMSH_GET, 0) :
                     (name == PView::list[i]->getData()->getName()) ? 1 :
                     0);
  drawContext::global()->draw();
}

static void view_applybgmesh_cb(Fl_Widget *w, void *data)
{
  int index =  (intptr_t)data;
  if(index >= 0 && index < (int)PView::list.size())
    GModel::current()->getFields()->setBackgroundMesh(index);
}

viewButton::viewButton(int x, int y, int w, int h, int num, Fl_Color col)
  : Fl_Group(x,y,w,h)
{
  int popw = FL_NORMAL_SIZE + 2;

  PView *view = PView::list[num];
  PViewData *data = view->getData();
  PViewOptions *opt = view->getOptions();

  _toggle = new Fl_Check_Button(x, y, w - popw, h);
  _toggle->box(FL_FLAT_BOX);
  _toggle->color(col);
  _toggle->callback(view_toggle_cb, (void *)(intptr_t)num);
  _toggle->align(FL_ALIGN_LEFT | FL_ALIGN_INSIDE | FL_ALIGN_CLIP);
  _toggle->value(opt->visible);
  char tmp[256];
  sprintf(tmp, "[%d] %s", num, data->getName().c_str());
  _toggle->copy_label(tmp);
  strcpy(_tooltip, data->getFileName().c_str());
  _toggle->tooltip(_tooltip);

  _butt = new Fl_Button(x + w - popw, y, popw, h, "@>");
  _butt->align(FL_ALIGN_RIGHT | FL_ALIGN_INSIDE | FL_ALIGN_CLIP);
  _butt->tooltip("Show view options (Shift+w)");
  _butt->box(FL_FLAT_BOX);
  _butt->color(col);
  _butt->selection_color(col);
  _popup = new Fl_Menu_Button(x + w - popw, y, popw, h);
  _popup->type(Fl_Menu_Button::POPUP123);

  _popup->add("Options", 'o',
              (Fl_Callback *) view_options_cb, (void *)(intptr_t)num, 0);
  _popup->add("_Plugins", 'p',
              (Fl_Callback *) plugin_cb, (void *)(intptr_t)num, 0);

  _popup->add("Reload", 'r',
              (Fl_Callback *) view_reload_cb, (void *)(intptr_t)num, 0);
  _popup->add("Reload Views/All", 0,
              (Fl_Callback *) view_reload_all_cb, (void *)(intptr_t)num, 0);
  _popup->add("Reload Views/Visible", 0,
              (Fl_Callback *) view_reload_visible_cb, (void *)(intptr_t)num, 0);
  _popup->add("_Create Alias", 0,
              (Fl_Callback *) view_alias_with_options_cb, (void *)(intptr_t)num, 0);

  _popup->add("Remove", FL_Delete,
              (Fl_Callback *) view_remove_cb, (void *)(intptr_t)num, 0);
  _popup->add("_Remove Views/All", 0,
              (Fl_Callback *) view_remove_all_cb, (void *)-1, 0);
  _popup->add("Remove Views/Visible", 0,
              (Fl_Callback *) view_remove_all_cb, (void *)-2, 0);
  _popup->add("Remove Views/Invisible", 0,
              (Fl_Callback *) view_remove_all_cb, (void *)-3, 0);
  _popup->add("Remove Views/Other", 0,
              (Fl_Callback *) view_remove_other_cb, (void *)(intptr_t)num, 0);
  _popup->add("Remove Views/Empty", 0,
              (Fl_Callback *) view_remove_all_cb, (void *)-4, 0);
  _popup->add("Remove Views/With Same Name", 0,
              (Fl_Callback *) view_remove_all_cb, (void *)(intptr_t)num, 0);

  _popup->add("Sort By Name", 0,
              (Fl_Callback *) view_sort_cb, (void *)0, 0);
  _popup->add("Set Visibility/All On", 0,
              (Fl_Callback *) view_all_visible_cb, (void *)-1, 0);
  _popup->add("Set Visibility/All Off", 0,
              (Fl_Callback *) view_all_visible_cb, (void *)-2, 0);
  _popup->add("Set Visibility/Invert", 0,
              (Fl_Callback *) view_all_visible_cb, (void *)-3, 0);
  _popup->add("Set Visibility/Same Name On", 0,
              (Fl_Callback *) view_all_visible_cb, (void *)(intptr_t)num, 0);

  _popup->add("Combine Elements/From All Views", 0,
              (Fl_Callback *) view_combine_space_all_cb, (void *)(intptr_t)num, 0);
  _popup->add("Combine Elements/From Visible Views", 0,
              (Fl_Callback *) view_combine_space_visible_cb, (void *)(intptr_t)num, 0);
  _popup->add("Combine Elements/From All Views With Same Name", 0,
              (Fl_Callback *) view_combine_space_by_name_cb, (void *)(intptr_t)num, 0);

  _popup->add("_Combine Time Steps/From All Views", 0,
              (Fl_Callback *) view_combine_time_all_cb, (void *)(intptr_t)num, 0);
  _popup->add("Combine Time Steps/From Visible Views", 0,
              (Fl_Callback *) view_combine_time_visible_cb, (void *)(intptr_t)num, 0);
  _popup->add("Combine Time Steps/From All Views With Same Name", 0,
              (Fl_Callback *) view_combine_time_by_name_cb, (void *)(intptr_t)num, 0);

  _popup->add("Apply As Background Mesh", 0,
              (Fl_Callback *) view_applybgmesh_cb, (void *)(intptr_t)num, 0);
  _popup->add("Save As...", 0,
              (Fl_Callback *) view_save_cb, (void *)(intptr_t)num, 0);

  end(); // close the group
  resizable(_toggle);
}