//
// This file is part of MorphoGraphX - http://www.MorphoGraphX.org
// Copyright (C) 2012-2015 Richard S. Smith and collaborators.
//
// If you use MorphoGraphX in your work, please cite:
//   http://dx.doi.org/10.7554/eLife.05864
//
// MorphoGraphX is free software, and is licensed under under the terms of the 
// GNU General (GPL) Public License version 2.0, http://www.gnu.org/licenses.
// 
#include "ScaleBar.hpp"

#include "Colors.hpp"
#include "Information.hpp"

#include <math.h>
#include <QFontMetrics>
#include <QPainter>
#include <QPoint>
#include <QTextStream>

namespace mgx {
  using qglviewer::Camera;
  using qglviewer::Vec;
  
  ScaleBar::ScaleBar() : wantedSize(1), scale(1), unit("m"), displayUnit(true), pos(BottomRight),
      dir(Horizontal), thickness(5), shiftBorder(20, 20), textPosition(Out), fontSize(12), 
  		autoScale(true), autoUnit(true), minSize(50), maxSize(200), globalScale(1.0) {}
  
  void ScaleBar::init(QGLViewer* viewer)
  {
    viewer->setAutoFillBackground(false);
    viewer->camera()->setType(qglviewer::Camera::ORTHOGRAPHIC);
  }
  
  static const QString unit_prefix[] = {
    "y",                                // yocto - 10^-24
    "z",                                // zepto - 10^-21
    "a",                                // atto  - 10^-18
    "f",                                // femto - 10^-15
    "p",                                // pico  - 10^-12
    "n",                                // nano  - 10^-9
    QString::fromWCharArray(L"\xb5"),   // micro - 10^-6
    "m",                                // milli - 10^-3
    "",                                 // unit  - 10^0
    "k",                                // kilo  - 10^3
    "M",                                // mega  - 10^6
    "G",                                // giga  - 10^9
    "T",                                // tera  - 10^12
    "P",                                // peta  - 10^15
    "E",                                // exa   - 10^18
    "Z",                                // zetta - 10^21
    "Y",                                // yota  - 10^24
  };
  
  static int min_prefix = -24;
  static int max_prefix = 24;
  static int index_null = 8;
  
  void ScaleBar::findScale(double unit_size)
  {
    if(wantedSize > 0) {
      double s = unit_size * wantedSize / scale;
      if(s >= minSize and s <= maxSize)
        return;       // Nothing to do .. stay as stable as possible
    }
    double smallest = scale * minSize / unit_size;
    double biggest = scale * maxSize / unit_size;
    double log10_smallest = floor(log10(smallest));
    double log10_biggest = floor(log10(biggest));
    if(log10_smallest != log10_biggest)
      wantedSize = pow(10.0, log10_biggest);
    else {
      // Extra first digit of the smallest and the biggest
      double smallest_digit = floor(smallest / pow(10.0, log10_smallest));
      double biggest_digit = floor(biggest / pow(10.0, log10_biggest));
      if(smallest_digit == biggest_digit) {
        throw QString("The scale range is not big enough to automatically compute the size.");
      }
      if(smallest_digit <= 5 and biggest_digit >= 5)
        wantedSize = 5 * pow(10.0, log10_biggest);
      else if(smallest_digit <= 2 and biggest_digit >= 2)
        wantedSize = 2 * pow(10.0, log10_biggest);
      else
        wantedSize = biggest_digit * pow(10.0, log10_biggest);
    }
  }
  
  void ScaleBar::draw(QGLViewer* viewer, QPaintDevice* device)
  {
    if(device == 0)
      device = viewer;
    glDisable(GL_CULL_FACE);
    glDisable(GL_DEPTH_TEST);
    glDisable(GL_LIGHTING);
    glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
    // Find unit size
    Camera* cam = viewer->camera();
    double ratio = (double)cam->pixelGLRatio(Vec(0, 0, 0));
    double unit_size = 1 / ratio;   // size of a vector of size 1 in pixels
    if(autoScale)
      findScale(unit_size);
    double needed_size = unit_size * wantedSize / scale;
    QSizeF deviceSize = QSizeF(device->width(), device->height()) / globalScale;
    QSizeF size;
    switch(dir) {
    case Horizontal:
      size = QSizeF(needed_size, thickness);
      break;
    case Vertical:
      size = QSizeF(thickness, needed_size);
    }
    // Convert relative to absolute coordinates
    QPointF abs_pos;
    switch(pos) {
    case Top:
      abs_pos = QPointF((deviceSize.width() - size.width()) / 2.0, shiftBorder.y());
      break;
    case Bottom:
      abs_pos
        = QPointF((deviceSize.width() - size.width()) / 2.0, deviceSize.height() - shiftBorder.y() - size.height());
      break;
    case Left:
      abs_pos = QPointF(shiftBorder.x(), (deviceSize.height() - size.height()) / 2.0);
      break;
    case Right:
      abs_pos
        = QPointF(deviceSize.width() - shiftBorder.x() - size.width(), (deviceSize.height() - size.height()) / 2.0);
      break;
    case TopLeft:
      abs_pos = QPointF(shiftBorder.x(), shiftBorder.y());
      break;
    case BottomLeft:
      abs_pos = QPointF(shiftBorder.x(), deviceSize.height() - size.height() - shiftBorder.y());
      break;
    case TopRight:
      abs_pos = QPointF(deviceSize.width() - shiftBorder.x() - size.width(), shiftBorder.y());
      break;
    case BottomRight:
      abs_pos = QPointF(deviceSize.width() - shiftBorder.x() - size.width(),
                        deviceSize.height() - shiftBorder.y() - size.height());
      break;
    case Center:
      abs_pos = QPointF((deviceSize.width() - size.width()) / 2.0, (deviceSize.height() - size.height()) / 2.0);
      break;
    default:
      throw QString("Unknown positionning: %1").arg(pos).toStdString();
    }
    QPainter paint(device);
    paint.setRenderHints(QPainter::Antialiasing | QPainter::TextAntialiasing | QPainter::SmoothPixmapTransform
                         | QPainter::HighQualityAntialiasing);
    // paint.scale(globalScale, globalScale);
    paint.setWindow(0, 0, deviceSize.width(), deviceSize.height());
    QColor color = Colors::getQColor(Colors::ScaleBarColor);
    QBrush b(color);
    paint.setPen(Qt::NoPen);
    paint.setBrush(b);
    QRectF r(abs_pos, size);
    if(DEBUG)
      Information::out << "Drawing scalebar at position " << abs_pos.x() << "," << abs_pos.y() << " with size "
                       << size.width() << "x" << size.height() << endl;
    paint.drawRect(r);
    if(displayUnit) {
      QString real_unit = unit;
      double real_size = wantedSize;
      if(autoUnit) {
        double power = floor(log10(wantedSize));
        if(power < min_prefix)
          power = min_prefix;
        else if(power > max_prefix)
          power = max_prefix;
        power /= 3;
        power = floor(power);
        int pi = int(power) + index_null;
        real_unit = unit_prefix[pi] + unit;
        double real_power = pow(10.0, 3 * (pi - index_null));
        real_size /= real_power;
      }
      QFont font(unit_font, device);
      font.setPixelSize(fontSize);
      QString text = QString("%1 %2").arg(real_size, 0, 'g', 3).arg(real_unit);
      QFontMetrics metric(font);
      QRect tr = metric.boundingRect(text);
      int th = tr.height();
      QPointF text_pos_hor, text_pos_ver;
      int text_flags_hor = 0, text_flags_ver = 0;
      if(textPosition == In) {
        switch(pos) {
        case Top:
          text_pos_hor = QPointF(deviceSize.width() / 2.0, shiftBorder.y() + size.height() / 2);
          text_pos_ver = QPointF(deviceSize.width() / 2.0, shiftBorder.y());
          text_flags_hor = Qt::AlignHCenter | Qt::AlignVCenter;
          text_flags_ver = Qt::AlignRight | Qt::AlignVCenter;
          break;
        case Bottom:
          text_pos_hor
            = QPointF(deviceSize.width() / 2.0, deviceSize.height() - shiftBorder.y() - size.height() / 2);
          text_pos_ver = QPointF(deviceSize.width() / 2.0, deviceSize.height() - shiftBorder.y());
          text_flags_hor = Qt::AlignHCenter | Qt::AlignVCenter;
          text_flags_ver = Qt::AlignLeft | Qt::AlignVCenter;
          break;
        case Left:
          text_pos_hor = QPointF(shiftBorder.x(), deviceSize.height() / 2.0);
          text_pos_ver = QPointF(shiftBorder.x() + size.width() / 2.0, deviceSize.height() / 2.0);
          text_flags_hor = Qt::AlignLeft | Qt::AlignVCenter;
          text_flags_ver = Qt::AlignHCenter | Qt::AlignVCenter;
          break;
        case Right:
          text_pos_hor = QPointF(deviceSize.width() - shiftBorder.x(), deviceSize.height() / 2.0);
          text_pos_ver
            = QPointF(deviceSize.width() - shiftBorder.x() - size.width() / 2.0, deviceSize.height() / 2.0);
          text_flags_hor = Qt::AlignRight | Qt::AlignVCenter;
          text_flags_ver = Qt::AlignHCenter | Qt::AlignVCenter;
          break;
        case TopLeft:
          text_pos_hor = QPointF(shiftBorder.x(), shiftBorder.y() + size.height() / 2.0);
          text_pos_ver = QPointF(shiftBorder.x() + size.width() / 2.0, shiftBorder.y());
          text_flags_hor = Qt::AlignLeft | Qt::AlignVCenter;
          text_flags_ver = Qt::AlignRight | Qt::AlignVCenter;
          break;
        case BottomLeft:
          text_pos_hor = QPointF(shiftBorder.x(), deviceSize.height() - shiftBorder.y() - size.height() / 2.0);
          text_pos_ver = QPointF(shiftBorder.x() + size.width() / 2.0, deviceSize.height() - shiftBorder.y());
          text_flags_hor = Qt::AlignLeft | Qt::AlignVCenter;
          text_flags_ver = Qt::AlignLeft | Qt::AlignVCenter;
          break;
        case TopRight:
          text_pos_hor = QPointF(deviceSize.width() - shiftBorder.x(), shiftBorder.y() + size.height() / 2.0);
          text_pos_ver = QPointF(deviceSize.width() - shiftBorder.x() - size.width() / 2.0, shiftBorder.y());
          text_flags_hor = Qt::AlignRight | Qt::AlignVCenter;
          text_flags_ver = Qt::AlignRight | Qt::AlignVCenter;
          break;
        case BottomRight:
          text_pos_hor = QPointF(deviceSize.width() - shiftBorder.x(),
                                 deviceSize.height() - shiftBorder.y() - size.height() / 2.0);
          text_pos_ver = QPointF(deviceSize.width() - shiftBorder.x() - size.width() / 2.0,
                                 deviceSize.height() - shiftBorder.y());
          text_flags_hor = Qt::AlignRight | Qt::AlignVCenter;
          text_flags_ver = Qt::AlignLeft | Qt::AlignVCenter;
          break;
        case Center:
          text_pos_ver = text_pos_hor = QPointF(deviceSize.width() / 2.0, deviceSize.height() / 2.0);
          text_flags_hor = text_flags_ver = Qt::AlignHCenter | Qt::AlignVCenter;
          break;
        default:
          throw QString("Unknown positionning: %1").arg(pos).toStdString();
        }
      } else {
        switch(pos) {
        case Top:
          text_pos_hor = QPointF(deviceSize.width() / 2.0, shiftBorder.y() + size.height() + th / 4.0);
          text_pos_ver
            = QPointF(deviceSize.width() / 2.0 + size.width() + th / 8.0, shiftBorder.y() + size.height() / 2);
          text_flags_hor = Qt::AlignHCenter | Qt::AlignTop;
          text_flags_ver = Qt::AlignHCenter | Qt::AlignTop;
          break;
        case Bottom:
          text_pos_hor = QPointF(deviceSize.width() / 2.0,
                                 deviceSize.height() - shiftBorder.y() - size.height() - th / 8.0);
          text_pos_ver = QPointF(deviceSize.width() / 2.0 + size.width() + th / 8.0,
                                 deviceSize.height() - shiftBorder.y());
          text_flags_hor = Qt::AlignHCenter | Qt::AlignBottom;
          text_flags_ver = Qt::AlignLeft | Qt::AlignTop;
          break;
        case Left:
          text_pos_hor = QPointF(shiftBorder.x(), (deviceSize.height() - size.height()) / 2.0 - th / 8.0);
          text_pos_ver = QPointF(shiftBorder.x() + size.width() + th / 8.0, deviceSize.height() / 2.0);
          text_flags_hor = Qt::AlignLeft | Qt::AlignBottom;
          text_flags_ver = Qt::AlignHCenter | Qt::AlignTop;
          break;
        case Right:
          text_pos_hor = QPointF(deviceSize.width() - shiftBorder.x(),
                                 (deviceSize.height() - size.height()) / 2.0 - th / 8.0);
          text_pos_ver = QPointF(deviceSize.width() - shiftBorder.x() - size.width() - th / 8.0,
                                 deviceSize.height() / 2.0);
          text_flags_hor = Qt::AlignRight | Qt::AlignBottom;
          text_flags_ver = Qt::AlignHCenter | Qt::AlignBottom;
          break;
        case TopLeft:
          text_pos_hor = QPointF(shiftBorder.x(), shiftBorder.y() + size.height() + th / 8.0);
          text_pos_ver = QPointF(shiftBorder.x() + size.width() + th / 8.0, shiftBorder.y());
          text_flags_hor = Qt::AlignLeft | Qt::AlignTop;
          text_flags_ver = Qt::AlignRight | Qt::AlignTop;
          break;
        case BottomLeft:
          text_pos_hor
            = QPointF(shiftBorder.x(), deviceSize.height() - shiftBorder.y() - size.height() - th / 8.0);
          text_pos_ver
            = QPointF(shiftBorder.x() + size.width() + th / 8.0, deviceSize.height() - shiftBorder.y());
          text_flags_hor = Qt::AlignLeft | Qt::AlignBottom;
          text_flags_ver = Qt::AlignLeft | Qt::AlignTop;
          break;
        case TopRight:
          text_pos_hor
            = QPointF(deviceSize.width() - shiftBorder.x(), shiftBorder.y() + size.height() + th / 8.0);
          text_pos_ver = QPointF(deviceSize.width() - shiftBorder.x() - size.width() - th / 8.0, shiftBorder.y());
          text_flags_hor = Qt::AlignRight | Qt::AlignTop;
          text_flags_ver = Qt::AlignRight | Qt::AlignBottom;
          break;
        case BottomRight:
          text_pos_hor = QPointF(deviceSize.width() - shiftBorder.x(),
                                 deviceSize.height() - shiftBorder.y() - size.height() - th / 8.0);
          text_pos_ver = QPointF(deviceSize.width() - shiftBorder.x() - size.width() - th / 8.0,
                                 deviceSize.height() - shiftBorder.y());
          text_flags_hor = Qt::AlignRight | Qt::AlignBottom;
          text_flags_ver = Qt::AlignLeft | Qt::AlignBottom;
          break;
        case Center:
          text_pos_hor
            = QPointF(deviceSize.width() / 2.0, (deviceSize.height() - size.height()) / 2.0 - th / 8.0);
          text_pos_ver = QPointF((deviceSize.width() - size.width()) / 2.0 - th / 8.0, deviceSize.height() / 2.0);
          text_flags_hor = Qt::AlignHCenter | Qt::AlignBottom;
          text_flags_ver = Qt::AlignHCenter | Qt::AlignBottom;
          break;
        default:
          throw QString("Unknown positionning: %1").arg(pos).toStdString();
        }
      }
      paint.setFont(font);
      QColor textColor = Colors::getQColor((textPosition == Out) ? Colors::LegendColor : Colors::BackgroundColor);
      paint.setPen(textColor);
      QTransform trans;
      switch(dir) {
      case Horizontal:
        trans.translate(text_pos_hor.x(), text_pos_hor.y());
        break;
      case Vertical:
        trans.translate(text_pos_ver.x(), text_pos_ver.y());
      }
      if(dir == Vertical) {
        trans.rotate(-90);
      }
      paint.setWorldTransform(trans, true);
      int text_flags = (dir == Horizontal) ? text_flags_hor : text_flags_ver;
      paint.drawText(QRectF(QPointF(0, 0), QSizeF()), text_flags | Qt::TextDontClip, text);
      /*
       * if(textPosition == In)
       *{
       *  paint.setWorldTransform(QTransform());
       *  paint.setClipRegion(QRegion(r.toRect()));
       *  //paint.setWorldTransform(trans);
       *  QColor textColor = Colors::getQColor(Colors::BackgroundColor);
       *  paint.setPen(textColor);
       *  paint.drawText(QRectF(QPointF(0,0), QSizeF()), text_flags | Qt::TextDontClip, text);
       *}
       */
    }
    paint.end();
  }
  
  void ScaleBar::readParms(Parms& parms, QString section)
  {
    // parms(section, "Scale", scale);
    parms(section, "AutoScale", autoScale, true);
    QString u;
    parms(section, "Unit", u, QString("m"));
    setUnit(u);
    if(autoScale) {
      parms(section, "MinSize", minSize, 50.0);
      parms(section, "MaxSize", maxSize, 200.0);
      wantedSize = -1;
    } else {
      parms(section, "WantedSize", wantedSize, 1.0);
    }
    parms(section, "AutoUnit", autoUnit, true);
    QString p;
    parms(section, "Position", p, QString("BottomRight"));
    p = p.toLower();
    if(p == "top")
      pos = Top;
    else if(p == "bottom")
      pos = Bottom;
    else if(p == "left")
      pos = Left;
    else if(p == "right")
      pos = Right;
    else if(p == "topleft")
      pos = TopLeft;
    else if(p == "bottomleft")
      pos = BottomLeft;
    else if(p == "topright")
      pos = TopRight;
    else if(p == "bottomright")
      pos = BottomRight;
    else if(p == "center")
      pos = Center;
    parms(section, "Direction", p, QString("Horizontal"));
    p = p.toLower();
    if(p == "horizontal")
      dir = Horizontal;
    else if(p == "vertical")
      dir = Vertical;
    parms(section, "Thickness", thickness, 5.0);
    parms(section, "ShiftBorder", shiftBorder, Point2u(20, 20));
    parms(section, "TextPosition", p, QString("Out"));
    p = p.toLower();
    if(p == "in")
      textPosition = In;
    else if(p == "out")
      textPosition = Out;
    parms(section, "FontSize", fontSize, 12);
  }
  
  void ScaleBar::writeParms(QTextStream& pout, QString section)
  {
    pout << endl;
    pout << "[" << section << "]" << endl;
    // pout << "Scale: " << scale << endl;
    pout << "AutoScale: " << (autoScale ? "true" : "false") << endl;
    pout << "Unit:" << unit << endl;
    if(autoScale) {
      pout << "MinSize: " << minSize << endl;
      pout << "MaxSize: " << maxSize << endl;
    } else
      pout << "WantedSize: " << wantedSize << endl;
    pout << "AutoUnit: " << (autoUnit ? "true" : "false") << endl;
    switch(pos) {
    case Top:
      pout << "Position: Top" << endl;
      break;
    case Bottom:
      pout << "Position: Bottom" << endl;
      break;
    case Left:
      pout << "Position: Left" << endl;
      break;
    case Right:
      pout << "Position: Right" << endl;
      break;
    case TopLeft:
      pout << "Position: TopLeft" << endl;
      break;
    case TopRight:
      pout << "Position: TopRight" << endl;
      break;
    case BottomLeft:
      pout << "Position: BottomLeft" << endl;
      break;
    case BottomRight:
      pout << "Position: BottomRight" << endl;
      break;
    case Center:
      pout << "Position: Center" << endl;
      break;
    }
    switch(dir) {
    case Horizontal:
      pout << "Direction: Horizontal" << endl;
      break;
    case Vertical:
      pout << "Direction: Vertical" << endl;
      break;
    }
    switch(textPosition) {
    case In:
      pout << "TextPosition: In" << endl;
      break;
    case Out:
      pout << "TextPosition: Out" << endl;
    }
    pout << "Thickness: " << thickness << endl;
    pout << "ShiftBorder: " << shiftBorder << endl;
    pout << "FontSize: " << fontSize << endl;
  }
  
  void ScaleBar::scaleDrawing(double s)
  {
    globalScale = s;
    /*
     * savedWantedSize = wantedSize;
     * savedThickness = thickness;
     * savedShiftBorder = shiftBorder;
     * savedFontSize = fontSize;
     * savedMinSize = minSize;
     * savedMaxSize = maxSize;
     * wantedSize *= s;
     * thickness *= s;
     * shiftBorder *= s;
     * fontSize *= s;
     * minSize *= s;
     * maxSize *= s;
     */
  }
  
  void ScaleBar::restoreScale()
  {
    globalScale = 1.0;
    /*
     * wantedSize = savedWantedSize;
     * thickness = savedThickness;
     * shiftBorder = savedShiftBorder;
     * fontSize = savedFontSize;
     * minSize = savedMinSize;
     * maxSize = savedMaxSize;
     */
  }
}
