// 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_Box.H>
#include <FL/Fl_Return_Button.H>
#include "FlGui.h"
#include "drawContext.h"
#include "statisticsWindow.h"
#include "paletteWindow.h"
#include "GModel.h"
#include "MElement.h"
#include "PView.h"
#include "Generator.h"
#include "Context.h"
#include "OS.h"
#include "Field.h"

enum QM_HISTO {
  QMH_SICN_XY,
  QMH_SICN_3D,
  QMH_GAMMA_XY,
  QMH_GAMMA_3D,
  QMH_SIGE_XY,
  QMH_SIGE_3D
};

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

static void statistics_update_cb(Fl_Widget *w, void *data)
{
  FlGui::instance()->stats->compute(true);
}

static void statistics_histogram_cb(Fl_Widget *w, void *data)
{
  QM_HISTO qmh = *(QM_HISTO *)data;
  bool visibleOnly = FlGui::instance()->stats->visible->value() ? true : false;

  std::vector<double> x, y;

  if(qmh == QMH_SICN_XY) {
    for(int i = 0; i < 100; i++) {
      x.push_back((double)(2 * i - 99) / 99);
      y.push_back(FlGui::instance()->stats->quality[0][i]);
    }
    new PView("SICN", "# Elements", x, y);
  }
  else if(qmh == QMH_GAMMA_XY) {
    for(int i = 0; i < 100; i++) {
      x.push_back((double)i / 99);
      y.push_back(FlGui::instance()->stats->quality[1][i]);
    }
    new PView("Gamma", "# Elements", x, y);
  }
  else if(qmh == QMH_SIGE_XY) {
    for(int i = 0; i < 100; i++) {
      x.push_back((double)(2 * i - 99) / 99);
      y.push_back(FlGui::instance()->stats->quality[2][i]);
    }
    new PView("SIGE", "# Elements", x, y);
  }
  else {
    std::vector<GEntity *> entities;
    GModel::current()->getEntities(entities);
    std::map<int, std::vector<double> > d;
    for(std::size_t i = 0; i < entities.size(); i++) {
      if(visibleOnly && !entities[i]->getVisibility()) continue;
      if(entities[i]->dim() < 2) continue;
      for(std::size_t j = 0; j < entities[i]->getNumMeshElements(); j++) {
        MElement *e = entities[i]->getMeshElement(j);
        if(qmh == QMH_SICN_3D)
          d[e->getNum()].push_back(e->minSICNShapeMeasure());
        else if(qmh == QMH_GAMMA_3D)
          d[e->getNum()].push_back(e->gammaShapeMeasure());
        else if(qmh == QMH_SIGE_3D)
          d[e->getNum()].push_back(e->minSIGEShapeMeasure());
      }
    }
    std::string name =
      (qmh == QMH_SICN_3D) ?
        "SICN" :
        (qmh == QMH_GAMMA_3D) ? "Gamma" : (qmh == QMH_SIGE_3D) ? "SIGE" : "";
    new PView(name, "ElementData", GModel::current(), d);
  }

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

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

  int num = 0;
  int width = 26 * FL_NORMAL_SIZE;
  int height = 6 * WB + 19 * BH;

  win = new paletteWindow(width, height,
                          CTX::instance()->nonModalWindows ? true : false,
                          "Statistics");
  win->box(GMSH_WINDOW_BOX);
  {
    Fl_Tabs *o = new Fl_Tabs(WB, WB, width - 2 * WB, height - 3 * WB - BH);
    {
      group[0] = new Fl_Group(WB, WB + BH, width - 2 * WB,
                              height - 3 * WB - 2 * BH, "Geometry");
      value[num++] = new Fl_Output(2 * WB, 2 * WB + 1 * BH, IW, BH, "Points");
      value[num++] = new Fl_Output(2 * WB, 2 * WB + 2 * BH, IW, BH, "Curves");
      value[num++] = new Fl_Output(2 * WB, 2 * WB + 3 * BH, IW, BH, "Surfaces");
      value[num++] = new Fl_Output(2 * WB, 2 * WB + 4 * BH, IW, BH, "Volumes");
      value[num++] =
        new Fl_Output(2 * WB, 2 * WB + 5 * BH, IW, BH, "Physical groups");
      group[0]->end();
    }
    {
      group[1] = new Fl_Group(WB, WB + BH, width - 2 * WB,
                              height - 3 * WB - 2 * BH, "Mesh");
      value[num++] = new Fl_Output(2 * WB, 2 * WB + 1 * BH, IW, BH, "Nodes");
      value[num++] = new Fl_Output(2 * WB, 2 * WB + 2 * BH, IW, BH, "Points");
      value[num++] = new Fl_Output(2 * WB, 2 * WB + 3 * BH, IW, BH, "Lines");
      value[num++] =
        new Fl_Output(2 * WB, 2 * WB + 4 * BH, IW, BH, "Triangles");
      value[num++] =
        new Fl_Output(2 * WB, 2 * WB + 5 * BH, IW, BH, "Quadrangles");
      value[num++] =
        new Fl_Output(2 * WB, 2 * WB + 6 * BH, IW, BH, "Tetrahedra");
      value[num++] =
        new Fl_Output(2 * WB, 2 * WB + 7 * BH, IW, BH, "Hexahedra");
      value[num++] = new Fl_Output(2 * WB, 2 * WB + 8 * BH, IW, BH, "Prisms");
      value[num++] = new Fl_Output(2 * WB, 2 * WB + 9 * BH, IW, BH, "Pyramids");
      value[num++] =
        new Fl_Output(2 * WB, 2 * WB + 10 * BH, IW, BH, "Trihedra");

      value[num++] =
        new Fl_Output(2 * WB, 2 * WB + 11 * BH, IW, BH, "Time for 1D mesh");
      value[num++] =
        new Fl_Output(2 * WB, 2 * WB + 12 * BH, IW, BH, "Time for 2D mesh");
      value[num++] =
        new Fl_Output(2 * WB, 2 * WB + 13 * BH, IW, BH, "Time for 3D mesh");

      value[num] = new Fl_Output(2 * WB, 2 * WB + 14 * BH, IW, BH, "SICN");
      value[num]->tooltip("~ signed inverse condition number");
      num++;
      value[num] = new Fl_Output(2 * WB, 2 * WB + 15 * BH, IW, BH, "Gamma");
      value[num]->tooltip(
        "~ inscribed_radius / circumscribed_radius (simplices)");
      num++;
      value[num] = new Fl_Output(2 * WB, 2 * WB + 16 * BH, IW, BH, "SIGE");
      value[num]->tooltip("~ signed inverse error on gradient FE solution");
      num++;

      for(int i = 0; i < 3; i++) {
        int ww = 3 * FL_NORMAL_SIZE;
        new Fl_Box(FL_NO_BOX, width - 3 * ww - 2 * WB, 2 * WB + (14 + i) * BH,
                   ww, BH, "Plot");
        butt[2 * i] = new Fl_Button(width - 2 * ww - 2 * WB,
                                    2 * WB + (14 + i) * BH, ww, BH, "X-Y");
        butt[2 * i + 1] = new Fl_Button(width - ww - 2 * WB,
                                        2 * WB + (14 + i) * BH, ww, BH, "3D");
      }
      static const QM_HISTO qmh0 = QMH_SICN_XY, qmh1 = QMH_SICN_3D,
                            qmh2 = QMH_GAMMA_XY, qmh3 = QMH_GAMMA_3D,
                            qmh4 = QMH_SIGE_XY, qmh5 = QMH_SIGE_3D;
      butt[0]->callback(statistics_histogram_cb, (void *)&qmh0);
      butt[1]->callback(statistics_histogram_cb, (void *)&qmh1);
      butt[2]->callback(statistics_histogram_cb, (void *)&qmh2);
      butt[3]->callback(statistics_histogram_cb, (void *)&qmh3);
      butt[4]->callback(statistics_histogram_cb, (void *)&qmh4);
      butt[5]->callback(statistics_histogram_cb, (void *)&qmh5);

      visible = new Fl_Check_Button(2 * WB, 2 * WB + 17 * BH + WB,  width - 4 * WB,
                                    BH, "Compute statistics for visible entities only");

      group[1]->end();
    }
    {
      group[2] = new Fl_Group(WB, WB + BH, width - 2 * WB,
                              height - 3 * WB - 2 * BH, "Post-processing");
      value[num++] = new Fl_Output(2 * WB, 2 * WB + 1 * BH, IW, BH, "Views");
      value[num++] = new Fl_Output(2 * WB, 2 * WB + 2 * BH, IW, BH, "Points");
      value[num++] = new Fl_Output(2 * WB, 2 * WB + 3 * BH, IW, BH, "Lines");
      value[num++] =
        new Fl_Output(2 * WB, 2 * WB + 4 * BH, IW, BH, "Triangles");
      value[num++] =
        new Fl_Output(2 * WB, 2 * WB + 5 * BH, IW, BH, "Quadrangles");
      value[num++] =
        new Fl_Output(2 * WB, 2 * WB + 6 * BH, IW, BH, "Tetrahedra");
      value[num++] =
        new Fl_Output(2 * WB, 2 * WB + 7 * BH, IW, BH, "Hexahedra");
      value[num++] = new Fl_Output(2 * WB, 2 * WB + 8 * BH, IW, BH, "Prisms");
      value[num++] = new Fl_Output(2 * WB, 2 * WB + 9 * BH, IW, BH, "Pyramids");
      value[num++] =
        new Fl_Output(2 * WB, 2 * WB + 10 * BH, IW, BH, "Trihedra");
      value[num++] = new Fl_Output(2 * WB, 2 * WB + 11 * BH, IW, BH, "Strings");
      group[2]->end();
    }
    o->end();
  }

  for(int i = 0; i < num; i++) {
    value[i]->align(FL_ALIGN_RIGHT);
    value[i]->value(0);
  }

  {
    memUsage = new Fl_Box(WB, height - BH - WB, width / 2, BH, "");
    memUsage->align(FL_ALIGN_LEFT | FL_ALIGN_INSIDE);

    Fl_Return_Button *o =
      new Fl_Return_Button(width - BB - WB, height - BH - WB, BB, BH, "Update");
    o->callback(statistics_update_cb);
  }

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

  FL_NORMAL_SIZE += deltaFontSize;
}

void statisticsWindow::compute(bool elementQuality)
{
  int num = 0;
  static double s[50];
  static char label[50][256];
  bool visibleOnly = visible->value() ? true : false;

  if(elementQuality)
    GetStatistics(s, quality, visibleOnly);
  else
    GetStatistics(s, 0, visibleOnly);

  // geom
  sprintf(label[num], "%g", s[0]);
  value[num]->value(label[num]);
  num++;
  sprintf(label[num], "%g", s[1]);
  value[num]->value(label[num]);
  num++;
  sprintf(label[num], "%g", s[2]);
  value[num]->value(label[num]);
  num++;
  sprintf(label[num], "%g", s[3]);
  value[num]->value(label[num]);
  num++;
  sprintf(label[num], "%g", s[45]);
  value[num]->value(label[num]);
  num++;

  // mesh
  sprintf(label[num], "%g", s[4]);
  value[num]->value(label[num]);
  num++;
  sprintf(label[num], "%g", s[5]);
  value[num]->value(label[num]);
  num++;
  sprintf(label[num], "%g", s[6]);
  value[num]->value(label[num]);
  num++;
  sprintf(label[num], "%g", s[7]);
  value[num]->value(label[num]);
  num++;
  sprintf(label[num], "%g", s[8]);
  value[num]->value(label[num]);
  num++;
  sprintf(label[num], "%g", s[9]);
  value[num]->value(label[num]);
  num++;
  sprintf(label[num], "%g", s[10]);
  value[num]->value(label[num]);
  num++;
  sprintf(label[num], "%g", s[11]);
  value[num]->value(label[num]);
  num++;
  sprintf(label[num], "%g", s[12]);
  value[num]->value(label[num]);
  num++;
  sprintf(label[num], "%g", s[13]);
  value[num]->value(label[num]);
  num++;

  sprintf(label[num], "%g", s[14]);
  value[num]->value(label[num]);
  num++;
  sprintf(label[num], "%g", s[15]);
  value[num]->value(label[num]);
  num++;
  sprintf(label[num], "%g", s[16]);
  value[num]->value(label[num]);
  num++;

  if(!elementQuality) {
    for(int i = 0; i < 6; i += 2) butt[i]->deactivate();
    sprintf(label[num], "Press Update");
    value[num]->deactivate();
    value[num]->value(label[num]);
    num++;
    sprintf(label[num], "Press Update");
    value[num]->deactivate();
    value[num]->value(label[num]);
    num++;
    sprintf(label[num], "Press Update");
    value[num]->deactivate();
    value[num]->value(label[num]);
    num++;
  }
  else {
    for(int i = 0; i < 6; i += 2) butt[i]->activate();
    sprintf(label[num], "%.4g (%.4g->%.4g)", s[18], s[19], s[20]);
    value[num]->activate();
    value[num]->value(label[num]);
    num++;
    sprintf(label[num], "%.4g (%.4g->%.4g)", s[21], s[22], s[23]);
    value[num]->activate();
    value[num]->value(label[num]);
    num++;
    sprintf(label[num], "%.4g (%.4g->%.4g)", s[24], s[25], s[26]);
    value[num]->activate();
    value[num]->value(label[num]);
    num++;
  }

  // post
  sprintf(label[num], "%g", s[27]);
  value[num]->value(label[num]);
  num++;
  sprintf(label[num], "%g", s[28]);
  value[num]->value(label[num]);
  num++;
  sprintf(label[num], "%g", s[29]);
  value[num]->value(label[num]);
  num++;
  sprintf(label[num], "%g", s[30]);
  value[num]->value(label[num]);
  num++;
  sprintf(label[num], "%g", s[31]);
  value[num]->value(label[num]);
  num++;
  sprintf(label[num], "%g", s[32]);
  value[num]->value(label[num]);
  num++;
  sprintf(label[num], "%g", s[33]);
  value[num]->value(label[num]);
  num++;
  sprintf(label[num], "%g", s[34]);
  value[num]->value(label[num]);
  num++;
  sprintf(label[num], "%g", s[35]);
  value[num]->value(label[num]);
  num++;
  sprintf(label[num], "%g", s[36]);
  value[num]->value(label[num]);
  num++;
  sprintf(label[num], "%g", s[37]);
  value[num]->value(label[num]);
  num++;

  static char mem[256];
  long m = GetMemoryUsage();
  if(m) {
    sprintf(mem, "Memory usage: %gMb", GetMemoryUsage() / 1024. / 1024.);
    memUsage->label(mem);
  }
}

void statisticsWindow::show()
{
  if(!win->shown()) compute(false);

  for(int i = 0; i < 3; i++) group[i]->hide();

  if(GModel::current()->getMeshStatus(true) > 0)
    group[1]->show();
  else if(PView::list.size())
    group[2]->show();
  else
    group[0]->show();

  win->show();
}