// Gmsh - Copyright (C) 1997-2019 C. Geuzaine, J.-F. Remacle
//
// See the LICENSE.txt file for license information. Please report all
// issues on https://gitlab.onelab.info/gmsh/gmsh/issues.

#include <FL/Fl_Tabs.H>
#include <FL/Fl_Return_Button.H>
#include <algorithm>
#include "FlGui.h"
#include "drawContext.h"
#include "clippingWindow.h"
#include "paletteWindow.h"
#include "GmshDefines.h"
#include "PView.h"
#include "PViewOptions.h"
#include "Context.h"

void clip_cb(Fl_Widget *w, void *data) { FlGui::instance()->clipping->show(); }

static void clip_num_cb(Fl_Widget *w, void *data)
{
  FlGui::instance()->clipping->resetBrowser();
}

static void clip_update_cb(Fl_Widget *w, void *data)
{
  if(FlGui::instance()->clipping->group[0]->visible()) { // plane tab
    int idx = FlGui::instance()->clipping->choice->value();
    CTX::instance()->geom.clip &= ~(1 << idx);
    CTX::instance()->mesh.clip &= ~(1 << idx);
    for(std::size_t i = 0; i < PView::list.size(); i++)
      PView::list[i]->getOptions()->clip &= ~(1 << idx);
    for(int i = 0; i < FlGui::instance()->clipping->browser->size(); i++) {
      if(FlGui::instance()->clipping->browser->selected(i + 1)) {
        if(i == 0)
          CTX::instance()->geom.clip |= (1 << idx);
        else if(i == 1)
          CTX::instance()->mesh.clip |= (1 << idx);
        else if(i - 2 < (int)PView::list.size())
          PView::list[i - 2]->getOptions()->clip |= (1 << idx);
      }
    }
    for(int j = 0; j < 4; j++)
      CTX::instance()->clipPlane[idx][j] =
        FlGui::instance()->clipping->plane[j]->value();
    FlGui::instance()->clipping->fillBoxValuesFromPlaneValues();
  }
  else { // box tab
    CTX::instance()->geom.clip = 0;
    CTX::instance()->mesh.clip = 0;
    for(std::size_t i = 0; i < PView::list.size(); i++)
      PView::list[i]->getOptions()->clip = 0;
    for(int i = 0; i < FlGui::instance()->clipping->browser->size(); i++) {
      if(FlGui::instance()->clipping->browser->selected(i + 1)) {
        for(int idx = 0; idx < 6; idx++) {
          if(i == 0)
            CTX::instance()->geom.clip |= (1 << idx);
          else if(i == 1)
            CTX::instance()->mesh.clip |= (1 << idx);
          else if(i - 2 < (int)PView::list.size())
            PView::list[i - 2]->getOptions()->clip |= (1 << idx);
        }
      }
    }
    double c[3] = {FlGui::instance()->clipping->box[0]->value(),
                   FlGui::instance()->clipping->box[1]->value(),
                   FlGui::instance()->clipping->box[2]->value()};
    double d[3] = {FlGui::instance()->clipping->box[3]->value(),
                   FlGui::instance()->clipping->box[4]->value(),
                   FlGui::instance()->clipping->box[5]->value()};
    // left
    CTX::instance()->clipPlane[0][0] = 1.;
    CTX::instance()->clipPlane[0][1] = 0.;
    CTX::instance()->clipPlane[0][2] = 0.;
    CTX::instance()->clipPlane[0][3] = -c[0] + d[0] / 2.;
    // top
    CTX::instance()->clipPlane[1][0] = 0.;
    CTX::instance()->clipPlane[1][1] = 1.;
    CTX::instance()->clipPlane[1][2] = 0.;
    CTX::instance()->clipPlane[1][3] = -c[1] + d[1] / 2.;
    // near
    CTX::instance()->clipPlane[2][0] = 0.;
    CTX::instance()->clipPlane[2][1] = 0.;
    CTX::instance()->clipPlane[2][2] = 1.;
    CTX::instance()->clipPlane[2][3] = -c[2] + d[2] / 2.;
    // right
    CTX::instance()->clipPlane[3][0] = -1.;
    CTX::instance()->clipPlane[3][1] = 0.;
    CTX::instance()->clipPlane[3][2] = 0.;
    CTX::instance()->clipPlane[3][3] = c[0] + d[0] / 2.;
    // bottom
    CTX::instance()->clipPlane[4][0] = 0.;
    CTX::instance()->clipPlane[4][1] = -1.;
    CTX::instance()->clipPlane[4][2] = 0.;
    CTX::instance()->clipPlane[4][3] = c[1] + d[1] / 2.;
    // far
    CTX::instance()->clipPlane[5][0] = 0.;
    CTX::instance()->clipPlane[5][1] = 0.;
    CTX::instance()->clipPlane[5][2] = -1.;
    CTX::instance()->clipPlane[5][3] = c[2] + d[2] / 2.;

    int idx = FlGui::instance()->clipping->choice->value();
    for(int j = 0; j < 4; j++)
      FlGui::instance()->clipping->plane[j]->value(
        CTX::instance()->clipPlane[idx][j]);
  }

  if(CTX::instance()->clipWholeElements ||
     CTX::instance()->clipWholeElements !=
       FlGui::instance()->clipping->butt[0]->value()) {
    for(int clip = 0; clip < 6; clip++) {
      if(CTX::instance()->mesh.clip)
        CTX::instance()->mesh.changed |= (ENT_CURVE | ENT_SURFACE | ENT_VOLUME);
      for(std::size_t index = 0; index < PView::list.size(); index++)
        if(PView::list[index]->getOptions()->clip)
          PView::list[index]->setChanged(true);
    }
  }

  CTX::instance()->clipWholeElements =
    FlGui::instance()->clipping->butt[0]->value();
  CTX::instance()->clipOnlyDrawIntersectingVolume =
    FlGui::instance()->clipping->butt[1]->value();
  CTX::instance()->clipOnlyVolume =
    FlGui::instance()->clipping->butt[2]->value();

  int old = CTX::instance()->drawBBox;
  CTX::instance()->drawBBox = 1;
  if(CTX::instance()->fastRedraw)
    CTX::instance()->post.draw = CTX::instance()->mesh.draw = 0;
  drawContext::global()->draw();
  CTX::instance()->drawBBox = old;
  CTX::instance()->post.draw = CTX::instance()->mesh.draw = 1;
}

static void clip_invert_cb(Fl_Widget *w, void *data)
{
  for(int i = 0; i < 4; i++)
    FlGui::instance()->clipping->plane[i]->value(
      -FlGui::instance()->clipping->plane[i]->value());
  clip_update_cb(NULL, NULL);
}

static void clip_reset_cb(Fl_Widget *w, void *data)
{
  CTX::instance()->geom.clip = 0;
  CTX::instance()->mesh.clip = 0;
  for(std::size_t index = 0; index < PView::list.size(); index++)
    PView::list[index]->getOptions()->clip = 0;

  // Warning: for consistency these reset values should match the
  // default values for the associated options
  for(int i = 0; i < 6; i++)
    for(int j = 0; j < 4; j++) CTX::instance()->clipPlane[0][0] = 0.;
  CTX::instance()->clipPlane[0][0] = 1.;
  CTX::instance()->clipPlane[1][1] = 1.;
  CTX::instance()->clipPlane[2][2] = 1.;
  CTX::instance()->clipPlane[3][0] = -1.;
  CTX::instance()->clipPlane[4][1] = -1.;
  CTX::instance()->clipPlane[5][2] = -1.;
  CTX::instance()->clipPlane[3][3] = 1.;
  CTX::instance()->clipPlane[4][3] = 1.;
  CTX::instance()->clipPlane[5][3] = 1.;

  if(CTX::instance()->clipWholeElements) {
    CTX::instance()->mesh.changed |= (ENT_CURVE | ENT_SURFACE | ENT_VOLUME);
    for(std::size_t index = 0; index < PView::list.size(); index++)
      PView::list[index]->setChanged(true);
  }

  FlGui::instance()->clipping->resetBrowser();
  drawContext::global()->draw();
}

clippingWindow::clippingWindow(int deltaFontSize)
{
  FL_NORMAL_SIZE -= deltaFontSize;

  static Fl_Menu_Item plane_number[] = {{"Plane 0", 0, 0},
                                        {"Plane 1", 0, 0},
                                        {"Plane 2", 0, 0},
                                        {"Plane 3", 0, 0},
                                        {"Plane 4", 0, 0},
                                        {"Plane 5", 0, 0},
                                        {0}};

  int width = 26 * FL_NORMAL_SIZE;
  int height = 10 * BH + 5 * WB;
  int L = 7 * FL_NORMAL_SIZE;

  win = new paletteWindow(
    width, height, CTX::instance()->nonModalWindows ? true : false, "Clipping");
  win->box(GMSH_WINDOW_BOX);

  browser = new Fl_Multi_Browser(0, 0, L, height);
  browser->callback(clip_update_cb);
  browser->box(GMSH_SIMPLE_RIGHT_BOX);
  browser->scrollbar_size(
    std::max(10, FL_NORMAL_SIZE - 2)); // thinner scrollbars

  Fl_Tabs *o =
    new Fl_Tabs(L + WB, WB, width - L - 2 * WB, height - 3 * WB - 4 * BH);
  {
    group[0] = new Fl_Group(L + WB, WB + BH, width - L - 2 * WB,
                            height - 3 * WB - 5 * BH, "Planes");

    int BW = width - L - 4 * WB - 4 * FL_NORMAL_SIZE;

    choice = new Fl_Choice(L + 2 * WB, 2 * WB + 1 * BH, BW, BH);
    choice->menu(plane_number);
    choice->callback(clip_num_cb);

    Fl_Button *invert =
      new Fl_Button(L + 2 * WB, 2 * WB + 2 * BH, FL_NORMAL_SIZE, 4 * BH, "-");
    invert->box(FL_THIN_UP_BOX);
    invert->callback(clip_invert_cb);
    invert->tooltip("Invert orientation");

    plane[0] = new Fl_Value_Input(L + 2 * WB + FL_NORMAL_SIZE, 2 * WB + 2 * BH,
                                  BW - FL_NORMAL_SIZE, BH, "A");
    plane[1] = new Fl_Value_Input(L + 2 * WB + FL_NORMAL_SIZE, 2 * WB + 3 * BH,
                                  BW - FL_NORMAL_SIZE, BH, "B");
    plane[2] = new Fl_Value_Input(L + 2 * WB + FL_NORMAL_SIZE, 2 * WB + 4 * BH,
                                  BW - FL_NORMAL_SIZE, BH, "C");
    plane[3] = new Fl_Value_Input(L + 2 * WB + FL_NORMAL_SIZE, 2 * WB + 5 * BH,
                                  BW - FL_NORMAL_SIZE, BH, "D");
    for(int j = 0; j < 4; j++) {
      plane[j]->align(FL_ALIGN_RIGHT);
      plane[j]->callback(clip_update_cb);
    }

    group[0]->end();
  }
  {
    group[1] = new Fl_Group(L + WB, WB + BH, width - L - 2 * WB,
                            height - 3 * WB - 5 * BH, "Box");
    group[1]->hide();

    int w2 = (width - L - 4 * WB) / 2;
    int BW = w2 - 2 * FL_NORMAL_SIZE;
    box[0] = new Fl_Value_Input(L + 2 * WB, 2 * WB + 1 * BH, BW, BH, "Cx");
    box[1] = new Fl_Value_Input(L + 2 * WB, 2 * WB + 2 * BH, BW, BH, "Cy");
    box[2] = new Fl_Value_Input(L + 2 * WB, 2 * WB + 3 * BH, BW, BH, "Cz");
    box[3] = new Fl_Value_Input(L + 2 * WB + w2, 2 * WB + 1 * BH, BW, BH, "Wx");
    box[4] = new Fl_Value_Input(L + 2 * WB + w2, 2 * WB + 2 * BH, BW, BH, "Wy");
    box[5] = new Fl_Value_Input(L + 2 * WB + w2, 2 * WB + 3 * BH, BW, BH, "Wz");
    for(int i = 0; i < 6; i++) {
      box[i]->align(FL_ALIGN_RIGHT);
      box[i]->callback(clip_update_cb);
    }

    group[1]->end();
  }
  o->callback(clip_update_cb); // force update when we switch tabs
  o->end();

  butt[0] = new Fl_Check_Button(L + WB, 3 * WB + 6 * BH, width - L - 2 * WB, BH,
                                "Keep whole elements");
  butt[1] = new Fl_Check_Button(L + WB, 3 * WB + 7 * BH, width - L - 2 * WB, BH,
                                "Only draw volume layer");
  butt[2] = new Fl_Check_Button(L + WB, 3 * WB + 8 * BH, width - L - 2 * WB, BH,
                                "Cut only volume elements");
  for(int i = 0; i < 3; i++) {
    butt[i]->type(FL_TOGGLE_BUTTON);
    butt[i]->callback(clip_update_cb);
  }

  resetBrowser();

  {
    Fl_Return_Button *o = new Fl_Return_Button(
      width - 2 * BB - 2 * WB, height - BH - WB, BB, BH, "Redraw");
    o->callback(redraw_cb);
  }
  {
    Fl_Button *o =
      new Fl_Button(width - BB - WB, height - BH - WB, BB, BH, "Reset");
    o->callback(clip_reset_cb);
  }

  win->position(CTX::instance()->clipPosition[0],
                CTX::instance()->clipPosition[1]);
  win->end();

  FL_NORMAL_SIZE += deltaFontSize;
}

void clippingWindow::resetBrowser()
{
  char str[128];
  browser->clear();
  browser->add("Geometry");
  browser->add("Mesh");
  for(std::size_t i = 0; i < PView::list.size(); i++) {
    sprintf(str, "View [%lu]", i);
    browser->add(str);
  }

  int idx = choice->value();

  browser->deselect();
  for(int i = 0; i < browser->size(); i++) {
    if((i == 0 && CTX::instance()->geom.clip & (1 << idx)) ||
       (i == 1 && CTX::instance()->mesh.clip & (1 << idx)) ||
       (i > 1 && i - 2 < (int)PView::list.size() &&
        PView::list[i - 2]->getOptions()->clip & (1 << idx)))
      browser->select(i + 1);
  }

  for(int j = 0; j < 4; j++)
    plane[j]->value(CTX::instance()->clipPlane[idx][j]);

  for(int j = 0; j < 3; j++) {
    plane[j]->step(0.01);
    plane[j]->minimum(-1.0);
    plane[j]->maximum(1.0);
  }
  double val1 = 0;
  for(int i = 0; i < 3; i++)
    val1 = std::max(val1, std::max(fabs(CTX::instance()->min[i]),
                                   fabs(CTX::instance()->max[i])));
  val1 *= 1.5;

  plane[3]->step(val1 / 200., 1);
  plane[3]->minimum(-val1);
  plane[3]->maximum(val1);

  fillBoxValuesFromPlaneValues();

  for(int i = 0; i < 6; i++) {
    box[i]->step(val1 / 200., 1);
    box[i]->minimum(-val1);
    box[i]->maximum(val1);
  }
}

void clippingWindow::fillBoxValuesFromPlaneValues()
{
  double c[3] = {
    (-CTX::instance()->clipPlane[0][3] + CTX::instance()->clipPlane[3][3]) / 2.,
    (-CTX::instance()->clipPlane[1][3] + CTX::instance()->clipPlane[4][3]) / 2.,
    (-CTX::instance()->clipPlane[2][3] + CTX::instance()->clipPlane[5][3]) /
      2.};
  double d[3] = {
    CTX::instance()->clipPlane[0][3] + CTX::instance()->clipPlane[3][3],
    CTX::instance()->clipPlane[1][3] + CTX::instance()->clipPlane[4][3],
    CTX::instance()->clipPlane[2][3] + CTX::instance()->clipPlane[5][3]};
  box[0]->value(c[0]);
  box[1]->value(c[1]);
  box[2]->value(c[2]);
  box[3]->value(d[0]);
  box[4]->value(d[1]);
  box[5]->value(d[2]);
}

void clippingWindow::show()
{
  resetBrowser();
  win->show();
}
