// Gmsh - Copyright (C) 1997-2009 C. Geuzaine, J.-F. Remacle
//
// See the LICENSE.txt file for license information. Please report all
// bugs and problems to <gmsh@geuz.org>.

#include <FL/gl.h>
#include "drawContext.h"
#include "PView.h"
#include "PViewOptions.h"
#include "PViewData.h"
#include "gl2ps.h"
#include "Context.h"
#include "Numeric.h"

int drawContext::fix2dCoordinates(double *x, double *y)
{
  int ret = (*x > 99999 && *y > 99999) ? 3 : (*y > 99999) ? 2 : (*x > 99999) ? 1 : 0;

  if(*x < 0) // measure from right border
    *x = viewport[2] + *x;
  else if(*x > 99999) // by convention, x-centered
    *x = viewport[2] / 2;

  if(*y < 0) // measure from bottom border
    *y = -(*y);
  else if(*y > 99999) // by convention, y-centered
    *y = viewport[3] / 2.;
  else
    *y = viewport[3] - *y;
  return ret;
}

void drawContext::drawText2d()
{
  for(unsigned int i = 0; i < PView::list.size(); i++){
    PViewData *data = PView::list[i]->getData();
    PViewOptions *opt = PView::list[i]->getOptions();
    if(opt->Visible && opt->DrawStrings && isVisible(PView::list[i])){
      glColor4ubv((GLubyte *) & opt->color.text2d);
      for(int j = 0; j < data->getNumStrings2D(); j++){
        double x, y, style;
        std::string str;
        data->getString2D(j, opt->TimeStep, str, x, y, style);
        fix2dCoordinates(&x, &y);
        glRasterPos2d(x, y);
        drawString(str, style);
      }
    }
  }
}

static bool getGraphData(PView *p, std::vector<double> &x, double &xmin, 
                         double &xmax, std::vector<std::vector<double> > &y) 
{
  PViewData *data = p->getData();
  PViewOptions *opt = p->getOptions();

  if(data->hasMultipleMeshes()) return false; // cannot handle multi-mesh

  int numy = 0;
  if(opt->Type == PViewOptions::Plot2DSpace){
    numy = 1;
  }
  else if(opt->Type == PViewOptions::Plot2DTime){
    numy = 0;
    for(int ent = 0; ent < data->getNumEntities(0); ent++)
      for(int i = 0; i < data->getNumElements(0, ent); i++)
        if(data->getDimension(0, ent, i) < 2) numy++;
  }
  
  if(!numy) return false;
  y.resize(numy);

  bool space = (opt->Type == PViewOptions::Plot2DSpace);

  SPoint3 p0(0., 0., 0.);

  numy = 0;
  for(int ent = 0; ent < data->getNumEntities(0); ent++){
    for(int i = 0; i < data->getNumElements(0, ent); i++){
      int dim = data->getDimension(0, ent, i);
      if(dim < 2){
        int numNodes = data->getNumNodes(0, ent, i);
        for(int ts = space ? opt->TimeStep : 0; ts < opt->TimeStep + 1; ts++){
          int numComp = data->getNumComponents(ts, ent, i);
          for(int j = 0; j < numNodes; j++){
            double val[9], xyz[3];
            data->getNode(ts, ent, i, j, xyz[0], xyz[1], xyz[2]);
            for(int k = 0; k < numComp; k++)
              data->getValue(ts, ent, i, j, k, val[k]);
            double vy = ComputeScalarRep(numComp, val);
            if(space){
              // store offset to origin + distance to first point
              if(x.empty()){
                p0 = SPoint3(xyz[0], xyz[1], xyz[2]);
                x.push_back(ComputeScalarRep(3, xyz));
              }
              else{
                x.push_back(x[0] + p0.distance(SPoint3(xyz[0], xyz[1], xyz[2])));
              }
              y[0].push_back(vy);
            }
            else{
              if(!numy) x.push_back(data->getTime(ts));
              y[numy].push_back(vy);
            }
          }
        }
        numy++;
      }
    }
  }

  if(x.empty()) return false;

  if(space){
    bool monotone = true;
    for(unsigned int i = 1; i < x.size(); i++){
      if(x[i] < x[i - 1]){
        monotone = false;
        break;
      }
    }
    if(monotone){ // use the "coordinate"
      xmin = xmax = x[0];
      for(unsigned int i = 1; i < x.size(); i++){
        xmin = std::min(xmin, x[i]);
        xmax = std::max(xmax, x[i]);
      }
    }
    else{ // just use an index
      for(unsigned int i = 0; i < x.size(); i++) x[i] = i;
      xmin = 0;
      xmax = x.size() - 1;
    }
  }
  else{
    xmin = data->getTime(0);
    xmax = data->getTime(data->getNumTimeSteps() - 1);
  }
  return true;
}

static void drawGraphAxes(drawContext *ctx, PView *p, double xleft, double ytop, 
                          double width, double height, double xmin, double xmax)
{
  PViewData *data = p->getData();
  PViewOptions *opt = p->getOptions();

  if(!opt->Axes) return;

  char label[1024];    
  float font_h = gl_height() ? gl_height() : 1; // total font height
  float font_a = font_h - gl_descent(); // height above ref. point

  const double tic = 5.;

  glPointSize(CTX::instance()->point_size);
  gl2psPointSize(CTX::instance()->point_size *
                 CTX::instance()->print.eps_point_size_factor);

  glLineWidth(CTX::instance()->line_width);
  gl2psLineWidth(CTX::instance()->line_width * 
                 CTX::instance()->print.eps_line_width_factor);

  glColor4ubv((GLubyte *) & opt->color.axes);

  // bare axes
  glBegin(GL_LINE_STRIP);
  glVertex2d(xleft, ytop);
  glVertex2d(xleft, ytop - height);
  glVertex2d(xleft + width, ytop - height);
  if(opt->Axes > 1){
    glVertex2d(xleft + width, ytop);
    glVertex2d(xleft, ytop);
  }
  glEnd();

  // y label
  if(opt->Type == PViewOptions::Plot2DSpace){
    int nt = data->getNumTimeSteps();
    if((opt->ShowTime == 1 && nt > 1) || opt->ShowTime == 2){
      char tmp[256];
      sprintf(tmp, opt->Format.c_str(), data->getTime(opt->TimeStep));
      sprintf(label, "%s (%s)", data->getName().c_str(), tmp);
    }
    else if((opt->ShowTime == 3 && nt > 1) || opt->ShowTime == 4){
      sprintf(label, "%s (%d)", data->getName().c_str(), opt->TimeStep);
    }
    else
      sprintf(label, "%s", data->getName().c_str());
  }
  else
    sprintf(label, "%s", data->getName().c_str());
  glRasterPos2d(xleft, ytop + font_h + tic);
  ctx->drawStringCenter(label);
  
  // x label
  sprintf(label, "%s", opt->AxesLabel[0].c_str());
  glRasterPos2d(xleft + width / 2, ytop - height - 2 * font_h - 2 * tic);
  ctx->drawStringCenter(label);

  // y tics and horizontal grid
  if(opt->NbIso > 0){
    int nb = opt->NbIso;
    if(opt->ShowScale && (opt->NbIso * font_h > height))
      nb = (int)floor(height / font_h);
    double dy = height / (double)nb;
    double dv = (opt->TmpMax - opt->TmpMin) / (double)nb;
    for(int i = 0; i < nb + 1; i++){
      if(opt->Axes > 0){
        glBegin(GL_LINES);
        glVertex2d(xleft, ytop - i * dy);
        glVertex2d(xleft + tic, ytop - i * dy);
        if(opt->Axes > 1){
          glVertex2d(xleft + width - tic, ytop - i * dy);
          glVertex2d(xleft + width, ytop - i * dy);
        }
        glEnd();
        if(opt->Axes > 2 && i != 0 && i != nb){
          glEnable(GL_LINE_STIPPLE);
          glLineStipple(1, 0x1111);
          gl2psEnable(GL2PS_LINE_STIPPLE);
          gl2psLineWidth(1. * CTX::instance()->print.eps_line_width_factor);
          glBegin(GL_LINES);
          glVertex2d(xleft, ytop - i * dy);
          glVertex2d(xleft + width, ytop - i * dy);
          glEnd();
          glDisable(GL_LINE_STIPPLE);
          gl2psDisable(GL2PS_LINE_STIPPLE);
          gl2psLineWidth(CTX::instance()->line_width * 
                         CTX::instance()->print.eps_line_width_factor);
        }
      }
      if(opt->ShowScale){
        sprintf(label, opt->Format.c_str(), (i == nb) ? opt->TmpMin : 
                (opt->TmpMax - i * dv));
        glRasterPos2d(xleft - 2 * tic, ytop - i * dy - font_a / 3.);
        ctx->drawStringRight(label);
      }
    }
  }

  // x tics and vertical grid
  if(opt->AxesTics[0] > 0){
    int nb = opt->AxesTics[0];
    if(opt->Axes){
      sprintf(label, opt->AxesFormat[0].c_str(), - M_PI * 1.e-4);
      if((nb - 1) * gl_width(label) > width)
        nb = (int)(width / gl_width(label)) + 1;
    }
    if(nb == 1) nb++;
    
    double dx = width / (double)(nb - 1);
    double ybot = ytop - height;
    
    for(int i = 0; i < nb; i++){
      if(opt->Axes){
        glBegin(GL_LINES);
        glVertex2d(xleft + i * dx, ybot);
        glVertex2d(xleft + i * dx, ybot + tic);
        if(opt->Axes > 1){
          glVertex2d(xleft + i * dx, ytop);
          glVertex2d(xleft + i * dx, ytop - tic);
        }
        glEnd();
        if(opt->Axes > 2 && i != 0 && i != nb - 1){
          glEnable(GL_LINE_STIPPLE);
          glLineStipple(1, 0x1111);
          gl2psEnable(GL2PS_LINE_STIPPLE);
          gl2psLineWidth(1. * CTX::instance()->print.eps_line_width_factor);
          glBegin(GL_LINES);
          glVertex2d(xleft + i * dx, ytop);
          glVertex2d(xleft + i * dx, ybot);
          glEnd();
          glDisable(GL_LINE_STIPPLE);
          gl2psDisable(GL2PS_LINE_STIPPLE);
          gl2psLineWidth(CTX::instance()->line_width * 
                         CTX::instance()->print.eps_line_width_factor);
        }
        
        if(nb == 1)
          sprintf(label, opt->AxesFormat[0].c_str(), xmin);
        else
          sprintf(label, opt->AxesFormat[0].c_str(),
                  xmin + i * (xmax - xmin) / (double)(nb - 1));
        glRasterPos2d(xleft + i * dx, ybot - font_h - tic);
        ctx->drawStringCenter(label);
      }
    }
  }
  
}

static void addGraphPoint(drawContext *ctx, PView *p, double xleft, double ytop, 
                          double width, double height, double x, double y, 
                          double xmin, double xmax, double ymin, double ymax, 
                          bool numeric)
{
  PViewOptions *opt = p->getOptions();

  double px = xleft;
  if(xmin != xmax) px += (x - xmin) / (xmax - xmin) * width;

  if(opt->SaturateValues){
    if(y > ymax)
      y = ymax;
    else if(y < ymin)
      y = ymin;
  }
  
  double ybot = ytop - height;
  double py = ybot;
  if(ymax != ymin) py += (y - ymin) / (ymax - ymin) * height;

  if(y >= ymin && y <= ymax){
    unsigned int col = opt->getColor(y, ymin, ymax, true);
    glColor4ubv((GLubyte *) &col);
    if(numeric){
      glRasterPos2d(px + 3, py + 3);
      char label[256];
      sprintf(label, opt->Format.c_str(), y);
      ctx->drawString(label);
    }
    else
      glVertex2d(px, py);
  }
}

static void drawGraphCurves(drawContext *ctx, PView *p, double xleft, double ytop,
                            double width, double height, std::vector<double> &x,
                            double xmin, double xmax, 
                            std::vector<std::vector<double> > &y)
{
  PViewOptions *opt = p->getOptions();

  glPointSize(opt->PointSize);
  gl2psPointSize(opt->PointSize * CTX::instance()->print.eps_point_size_factor);

  glLineWidth(opt->LineWidth);
  gl2psLineWidth(opt->LineWidth * CTX::instance()->print.eps_line_width_factor);

  if(opt->IntervalsType == PViewOptions::Numeric){
    for(unsigned int i = 0; i < y.size(); i++)
      for(unsigned int j = 0; j < x.size(); j++)
        addGraphPoint(ctx, p, xleft, ytop, width, height, x[j], y[i][j], 
                      xmin, xmax, opt->TmpMin, opt->TmpMax, true);
  }

  if(opt->IntervalsType == PViewOptions::Iso ||
     opt->IntervalsType == PViewOptions::Discrete ||
     opt->IntervalsType == PViewOptions::Numeric){
    glBegin(GL_POINTS);
    for(unsigned int i = 0; i < y.size(); i++)
      for(unsigned int j = 0; j < x.size(); j++)
        addGraphPoint(ctx, p, xleft, ytop, width, height, x[j], y[i][j], 
                      xmin, xmax, opt->TmpMin, opt->TmpMax, false);
    glEnd();    
  }

  if(opt->IntervalsType == PViewOptions::Discrete ||
     opt->IntervalsType == PViewOptions::Continuous){
    for(unsigned int i = 0; i < y.size(); i++){
      if(opt->UseStipple){
        glEnable(GL_LINE_STIPPLE);
        glLineStipple(opt->Stipple[i % 10][0], opt->Stipple[i % 10][1]);
        gl2psEnable(GL2PS_LINE_STIPPLE);
      }
      glBegin(GL_LINE_STRIP);
      for(unsigned int j = 0; j < x.size(); j++)
        addGraphPoint(ctx, p, xleft, ytop, width, height, x[j], y[i][j], 
                      xmin, xmax, opt->TmpMin, opt->TmpMax, false);
      glEnd();
      if(opt->UseStipple){
        glDisable(GL_LINE_STIPPLE);
        gl2psDisable(GL2PS_LINE_STIPPLE);
      }
    }
  }
}

static void drawGraph(drawContext *ctx, PView *p, double xleft, double ytop,
                      double width, double height)
{
  PViewData *data = p->getData();
  PViewOptions *opt = p->getOptions();
  
  if(opt->RangeType == PViewOptions::Custom){
    opt->TmpMin = opt->CustomMin;
    opt->TmpMax = opt->CustomMax;
  }
  else if(opt->RangeType == PViewOptions::PerTimeStep){
    opt->TmpMin = data->getMin(opt->TimeStep);
    opt->TmpMax = data->getMax(opt->TimeStep);
  }
  else{
    opt->TmpMin = data->getMin();
    opt->TmpMax = data->getMax();
  }
  
  std::vector<double> x;
  std::vector<std::vector<double> > y;
  double xmin, xmax;
  if(!getGraphData(p, x, xmin, xmax, y)) return;
  drawGraphAxes(ctx, p, xleft, ytop, width, height, xmin, xmax);
  drawGraphCurves(ctx, p, xleft, ytop, width, height, x, xmin, xmax, y);
}

void drawContext::drawGraph2d()
{
  std::vector<PView*> graphs;
  for(unsigned int i = 0; i < PView::list.size(); i++){
    PViewData *data = PView::list[i]->getData();
    PViewOptions *opt = PView::list[i]->getOptions();
    if(!data->getDirty() && opt->Visible && opt->Type != PViewOptions::Plot3D &&
       isVisible(PView::list[i]))
      graphs.push_back(PView::list[i]);
  }
  if(graphs.empty()) return;

  gl_font(CTX::instance()->gl_font_enum, CTX::instance()->gl_fontsize);
  double xsep = 0., ysep = 5 * gl_height();
  char label[1024];
  for(unsigned int i = 0; i < graphs.size(); i++){
    PViewOptions *opt = graphs[i]->getOptions();
    sprintf(label, opt->Format.c_str(), -M_PI * 1.e-4);
    xsep = std::max(xsep, gl_width(label));
  }
  
  for(unsigned int i = 0; i < graphs.size(); i++){
    PView *p = graphs[i];
    PViewOptions *opt = graphs[i]->getOptions();
    if(!opt->AutoPosition){
      double x = opt->Position[0], y = opt->Position[1];
      int center = fix2dCoordinates(&x, &y);
      drawGraph(this, p, x - (center & 1 ? opt->Size[0] / 2. : 0), 
                y + (center & 2 ? opt->Size[1] / 2. : 0), 
                opt->Size[0], opt->Size[1]);
    }
    else{
      double winw = viewport[2] - viewport[0];
      double winh = viewport[3] - viewport[1];
      if(graphs.size() == 1){
        double fracw = 0.75, frach = 0.75;
        double w = fracw * winw - xsep;
        double h = frach * winh - ysep;
        double x = viewport[0] + (1 - fracw) / 2. * winw;
        double y = viewport[1] + (1 - frach) / 2. * winh;
        drawGraph(this, p, x + 0.95 * xsep, viewport[3] - (y + 0.4 * ysep), w, h);
      }
      else if(graphs.size() == 2){
        double fracw = 0.75, frach = 0.85;
        double w = fracw * winw - xsep;
        double h = frach * winh / 2. - ysep;
        double x = viewport[0] + (1 - fracw) / 2. * winw;
        double y = viewport[1] + (1 - frach) / 3. * winh;
        if(i == 1) y += (h + ysep + (1 - frach) / 3. * winh);
        drawGraph(this, p, x + 0.95 * xsep, viewport[3] - (y + 0.4 * ysep), w, h);
      }
      else{
        double fracw = 0.85, frach = 0.85;
        double w = fracw * winw / 2. - xsep;
        double h = frach * winh / 2. - ysep;
        double x = viewport[0] + (1 - fracw) / 3. * winw;
        if(i == 1 || i == 3) x += (w + xsep + (1-fracw)/3. * winw);
        double y = viewport[1] + (1 - frach) / 3. * winh;
        if(i == 2 || i == 3) y += (h + ysep + (1 - frach) / 3. * winh);
        drawGraph(this, p, x + 0.95 * xsep, viewport[3] - (y + 0.4 * ysep), w, h);
      }
    }
  }
}