//
// This file is part of MorphoGraphX - http://www.MorphoGraphX.org
// Copyright (C) 2012-2016 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 "ColorBar.hpp"

#include "Colors.hpp"
#include "Geometry.hpp"
#include "Information.hpp"
#include "Tie.hpp"

#include <climits>
#include <cmath>
#include <QFontMetricsF>
#include <QStringList>
#include <QTextStream>

namespace mgx 
{
  using Information::err;
  
  Point2d toPoint2d(const QPoint& p) { return Point2d(p.x(), p.y()); }
  
  Point2d toPoint2d(const QPointF& p) { return Point2d(p.x(), p.y()); }
  
  Colorbar::Colorbar(Position pos) : font(), prev_width(-1), prev_height(-1)
  {
    // font.setStyleHint(QFont::TypeWriter);
    font.setStyleStrategy(QFont::OpenGLCompatible);
    font.setStyleStrategy(QFont::PreferAntialias);
    position = pos;
    vmin = 0;
    vmax = 1;
  
    scale_length = 0.3;
    width = 20;
    distance_to_border = 5;
    text_to_bar = 5;
    tick_size = 2;
    exp_size = 0.7;
    epsilon = 1e-9;
    line_width = 2;
    globalScaling = 1.0;
  }
  
  void limit_rect(QRectF& rect, const QSize& size, double lim_width, double lim_height)
  {
    if(rect.left() < lim_width) {
      rect.moveLeft(lim_width);
      if(size.width() - rect.right() < lim_width)
        rect.setRight(size.width() - lim_width);
    } else if(size.width() - rect.right() < lim_width) {
      rect.moveRight(size.width() - lim_width);
      if(rect.left() < lim_width)
        rect.setLeft(lim_width);
    }
    if(rect.top() < lim_height) {
      rect.moveTop(lim_height);
      if(size.height() - rect.bottom() < lim_height)
        rect.setBottom(size.height() - lim_height);
    } else if(size.height() - rect.bottom() < lim_height) {
      rect.moveBottom(size.height() - lim_height);
      if(rect.top() < lim_height)
        rect.setTop(lim_height);
    }
  }
  
  void Colorbar::draw(GLuint texId, QPaintDevice* device) const
  {
    if(device == 0)
      return;

    if(vmin == vmax)
      return;
    if(fabs(vmin - vmax) / (fabs(vmin) + fabs(vmax)) < epsilon)
      return;
    if(!finite(vmin) or !finite(vmax))
      return;
    glPushAttrib(GL_ALL_ATTRIB_BITS);
    glDisable(GL_LIGHTING);
    glDisable(GL_LIGHT0);
    glDisable(GL_LIGHT1);
    glDisable(GL_LIGHT2);
    glDisable(GL_LIGHT3);
    glDisable(GL_DITHER);
    glDisable(GL_DEPTH_TEST);
    glDisable(GL_CULL_FACE);
    glDisable(GL_POLYGON_OFFSET_FILL);
    glDisable(GL_POLYGON_OFFSET_POINT);
    glDisable(GL_POLYGON_OFFSET_LINE);
    glDisable(GL_AUTO_NORMAL);
  
    glColor3f(1, 1, 1);
  
    startScreenCoordinatesSystem(device);
    do {
      double w = device->width() / globalScaling;
      double h = device->height() / globalScaling;
  
      if(w == 0 or h == 0) {
        break;
      }
  
      // First, find the position of the scale bar
      QFontMetricsF metric(font, device);
      QStringList font_test;
      double lim_width = 0;
      for(int i = 0; i < 10; ++i) {
        QString s = QString::number(i);
        font_test << s + s + s + s + s;
      }
  
      forall(QString t, font_test) {
        lim_width = std::max(lim_width, metric.width(t));
      }
      double lim_height = 3 * metric.height();
      double shift_length;
      switch(position) {
      case TOP:
      case BOTTOM:
        shift_length = w * (1 - scale_length) / 2;
        break;
      case LEFT:
      case RIGHT:
        shift_length = h * (1 - scale_length) / 2;
        break;
      default:
        shift_length = distance_to_border;
      }
  
      double delta_value = vmax - vmin;
      double shift_width = distance_to_border;
  
      QSize size(w, h);
  
      Point2d start_pos, end_pos, next_side;
      QRectF scale_rect;
  
      switch(position) {
      case TOP: {
        scale_rect = QRectF(shift_length, shift_width, scale_length * w, width);
        limit_rect(scale_rect, size, lim_width, lim_height);
        start_pos = toPoint2d(scale_rect.bottomLeft());
        end_pos = toPoint2d(scale_rect.bottomRight());
        next_side = toPoint2d(scale_rect.topRight() - scale_rect.bottomRight());
      } break;
      case RIGHT: {
        scale_rect = QRectF(w - shift_width - width, shift_length, width, scale_length * h);
        limit_rect(scale_rect, size, lim_width, lim_height);
        start_pos = toPoint2d(scale_rect.bottomLeft());
        end_pos = toPoint2d(scale_rect.topLeft());
        next_side = toPoint2d(scale_rect.bottomRight() - scale_rect.bottomLeft());
      } break;
      case BOTTOM: {
        scale_rect = QRectF(shift_length, h - shift_width - width, scale_length * w, width);
        limit_rect(scale_rect, size, lim_width, lim_height);
        start_pos = toPoint2d(scale_rect.topLeft());
        end_pos = toPoint2d(scale_rect.topRight());
        next_side = toPoint2d(scale_rect.bottomRight() - scale_rect.topRight());
      } break;
      case LEFT: {
        scale_rect = QRectF(shift_width, shift_length, width, scale_length * h);
        limit_rect(scale_rect, size, lim_width, lim_height);
        start_pos = toPoint2d(scale_rect.bottomRight());
        end_pos = toPoint2d(scale_rect.topRight());
        next_side = toPoint2d(scale_rect.bottomLeft() - scale_rect.bottomRight());
      } break;
      case TOP_LEFT: {
        if(orientation == HORIZONTAL) {
          scale_rect = QRectF(shift_length, shift_width, scale_length * w, width);
          limit_rect(scale_rect, size, lim_width, lim_height);
          start_pos = toPoint2d(scale_rect.bottomLeft());
          end_pos = toPoint2d(scale_rect.bottomRight());
          next_side = toPoint2d(scale_rect.topRight() - scale_rect.bottomRight());
        } else {
          scale_rect = QRectF(shift_width, shift_length, width, scale_length * h);
          limit_rect(scale_rect, size, lim_width, lim_height);
          start_pos = toPoint2d(scale_rect.bottomRight());
          end_pos = toPoint2d(scale_rect.topRight());
          next_side = toPoint2d(scale_rect.bottomLeft() - scale_rect.bottomRight());
        }
      } break;
      case TOP_RIGHT: {
        if(orientation == HORIZONTAL) {
          scale_rect = QRectF(w * (1 - scale_length) - shift_length, shift_width, scale_length * w, width);
          limit_rect(scale_rect, size, lim_width, lim_height);
          start_pos = toPoint2d(scale_rect.bottomLeft());
          end_pos = toPoint2d(scale_rect.bottomRight());
          next_side = toPoint2d(scale_rect.topRight() - scale_rect.bottomRight());
        } else {
          scale_rect = QRectF(w - shift_width - width, shift_length, width, scale_length * h);
          limit_rect(scale_rect, size, lim_width, lim_height);
          start_pos = toPoint2d(scale_rect.bottomLeft());
          end_pos = toPoint2d(scale_rect.topLeft());
          next_side = toPoint2d(scale_rect.bottomRight() - scale_rect.bottomLeft());
        }
      } break;
      case BOTTOM_LEFT: {
        if(orientation == HORIZONTAL) {
          scale_rect = QRectF(shift_length, h - shift_width - width, scale_length * w, width);
          limit_rect(scale_rect, size, lim_width, lim_height);
          start_pos = toPoint2d(scale_rect.topLeft());
          end_pos = toPoint2d(scale_rect.topRight());
          next_side = toPoint2d(scale_rect.bottomRight() - scale_rect.topRight());
        } else {
          scale_rect = QRectF(shift_width, h * (1 - scale_length) - shift_length, width, scale_length * h);
          limit_rect(scale_rect, size, lim_width, lim_height);
          start_pos = toPoint2d(scale_rect.bottomRight());
          end_pos = toPoint2d(scale_rect.topRight());
          next_side = toPoint2d(scale_rect.bottomLeft() - scale_rect.bottomRight());
        }
      } break;
      case BOTTOM_RIGHT: {
        if(orientation == HORIZONTAL) {
          scale_rect
            = QRectF(w * (1 - scale_length) - shift_length, h - width - shift_width, scale_length * w, width);
          limit_rect(scale_rect, size, lim_width, lim_height);
          start_pos = toPoint2d(scale_rect.topLeft());
          end_pos = toPoint2d(scale_rect.topRight());
          next_side = toPoint2d(scale_rect.bottomRight() - scale_rect.topRight());
        } else {
          scale_rect
            = QRectF(w - shift_width - width, h * (1 - scale_length) - shift_length, width, scale_length * h);
          limit_rect(scale_rect, size, lim_width, lim_height);
          start_pos = toPoint2d(scale_rect.bottomLeft());
          end_pos = toPoint2d(scale_rect.topLeft());
          next_side = toPoint2d(scale_rect.bottomRight() - scale_rect.bottomLeft());
        }
      } break;
      default:
        break;
      }
      if(scale_rect.width() == 0 or scale_rect.height() == 0) {
        break;
      }
      Point2d shift_pos = (end_pos - start_pos) / delta_value;
      double length;
      if(orientation == VERTICAL)
        length = scale_rect.height();
      else
        length = scale_rect.width();
  
  
      // Get the ticks
      array ticks = selectValues(length, orientation == VERTICAL, &metric);
      if(ticks.size() == 0)
        break;
      if(DEBUG) {
        Information::out << "List of ticks = ";
        for(size_t i = 0; i < ticks.size(); ++i) {
          Information::out << ticks[i] << " ";
        }
        Information::out << endl;
      }
      QString ticks_extra;
      QStringList ticks_str = _tick2str(ticks, &ticks_extra);
  
      // Now, draw the gradient
      glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
      glEnable(GL_TEXTURE_1D);
      glBindTexture(GL_TEXTURE_1D, texId);
      glBegin(GL_QUADS);
      glTexCoord1f(0);
      glVertex2dv(start_pos.c_data());
      glVertex2dv((start_pos + next_side).c_data());
      glTexCoord1f(1);
      glVertex2dv((end_pos + next_side).c_data());
      glVertex2dv(end_pos.c_data());
      glEnd();
      glDisable(GL_TEXTURE_1D);
  
      // Draw text
  
      // Compute the shifts
      double max_width = 0;
      double max_height = metric.height();
      forall(QString tick, ticks_str) {
        double w = metric.width(tick);
        if(max_width < w)
          max_width = w;
      }
      double shift = 0;
      if(orientation == VERTICAL) {
        switch(position) {
        case LEFT:
        case TOP_LEFT:
        case BOTTOM_LEFT:
          shift = text_to_bar + line_width;
          break;
        case RIGHT:
        case TOP_RIGHT:
        case BOTTOM_RIGHT:
          shift = -text_to_bar - max_width - line_width;
          break;
        default:
          break;
        }
      } else {
        switch(position) {
        case TOP:
        case TOP_LEFT:
        case TOP_RIGHT:
          shift = text_to_bar + line_width;
          break;
        case BOTTOM:
        case BOTTOM_LEFT:
        case BOTTOM_RIGHT:
          shift = -text_to_bar - max_height - line_width;
          break;
        default:
          break;
        }
      }
  
      glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
      Colorf col = Colors::getColor(Colors::LegendColor);
      glColor3fv(col.c_data());
  
      {
        QPainter paint(device);
        paint.scale(globalScaling, globalScaling);
        paint.setFont(font);
        paint.setPen(Colors::getQColor(Colors::LegendColor));
  
        for(size_t i = 0; i < ticks.size(); ++i) {
          QString ts = ticks_str[i];
          double t = ticks[i];
          double h = metric.height();
          double w = metric.width(ts);
          Point2d pos = start_pos + shift_pos * (t - vmin);
          if(orientation == VERTICAL) {
            pos.x() += shift + max_width - w;
            pos.y() -= h / 2;
          } else {
            pos.x() -= w / 3;
            pos.y() += shift;
          }
          pos.y() += h;
          paint.drawText(pos.x(), pos.y(), ts);
        }
  
        if(!ticks_extra.isEmpty() or !label.isEmpty()) {
          QString exp_txt = QString::fromUtf8("×10");
          double width = metric.width(exp_txt);
          QFont exp_font = QFont(font);
          if(exp_font.pixelSize() != -1)
            exp_font.setPixelSize(exp_size * exp_font.pixelSize());
          else
            exp_font.setPointSizeF(exp_size * exp_font.pointSizeF());
          QFontMetricsF exp_metric(exp_font, device);
          double exp_width = exp_metric.boundingRect(ticks_extra).width();
          double exp_height = exp_metric.boundingRect(ticks_extra).height();
          double total_height = std::max(metric.ascent(), metric.ascent() / 2 + exp_metric.ascent());
          double label_width;
          if(!label.isEmpty())
            label_width = metric.boundingRect(label).width();
          else
            label_width = 0;
          double space_width = metric.width(" ");
          Point2d pos = toPoint2d(scale_rect.topRight());
          if(ticks_extra.isEmpty()) {
            exp_width = width = space_width = 0;
            exp_txt = "";
          }
          double total_width = width + exp_width + label_width + space_width;
          if(orientation == VERTICAL) {
            pos.x() -= (scale_rect.width() + total_width) / 2;
            if(pos.x() < 0) {
              pos.x() = text_to_bar;
            } else if(pos.x() + total_width + text_to_bar > w) {
              pos.x() = w - total_width - text_to_bar;
            }
            pos.y() -= text_to_bar + total_height / 2;
          } else {
            switch(position) {
            case BOTTOM:
            case BOTTOM_LEFT:
            case BOTTOM_RIGHT:
              pos.y() += scale_rect.height() + text_to_bar + total_height;
              pos.x() -= total_width;
              break;
            case TOP:
            case TOP_LEFT:
            case TOP_RIGHT:
              pos.y() -= text_to_bar;
              pos.x() -= total_width;
              break;
            default:
              break;
            }
          }
  
          paint.drawText(pos.x(), pos.y(), exp_txt);
  
          Point2d label_pos = pos;
  
          label_pos.x() += width + exp_width + space_width;
          paint.drawText(label_pos.x(), label_pos.y(), label);
  
          pos.x() += width;
          pos.y() -= metric.ascent() * 0.5;
          paint.setFont(exp_font);
          paint.drawText(pos.x(), pos.y() - exp_height, exp_width, exp_height,
                         Qt::AlignBottom | Qt::AlignLeft | Qt::TextDontClip, ticks_extra);
          paint.setFont(font);
        }
      }
  
      // Draw the box
      double lw = line_width * globalScaling;
      glLineWidth(lw);
      col = Colors::getColor(Colors::LegendColor);
      glColor3fv(col.c_data());
      double correct = (line_width > 0) ? (line_width / 2) : 0;
  
      Point2d p1 = start_pos;
      Point2d p2 = start_pos + next_side;
      Point2d p3 = end_pos + next_side;
      Point2d p4 = end_pos;
  
      Point2d d12 = correct * normalized(p2 - p1);
      Point2d d23 = correct * normalized(p3 - p2);
      Point2d d34 = correct * normalized(p4 - p3);
      Point2d d41 = correct * normalized(p1 - p4);
  
      glBegin(GL_LINES);
      glVertex2dv((p1 - d12).c_data());
      glVertex2dv((p2 + d12).c_data());
      glVertex2dv((p2 - d23).c_data());
      glVertex2dv((p3 + d23).c_data());
      glVertex2dv((p3 - d34).c_data());
      glVertex2dv((p4 + d34).c_data());
      glVertex2dv((p4 - d41).c_data());
      glVertex2dv((p1 + d41).c_data());
      glEnd();
  
      // At last, draw the ticks
      glBegin(GL_LINES);
      for(size_t i = 0; i < ticks.size(); ++i) {
        double t = ticks[i];
        Point2d pos1 = start_pos + shift_pos * (t - vmin);
        Point2d pos2 = pos1;
        if(orientation == VERTICAL) {
          pos1.x() = scale_rect.left();
          pos2.x() = pos1.x() + tick_size;
          glVertex2dv(pos1.c_data());
          glVertex2dv(pos2.c_data());
  
          pos1.x() = scale_rect.right();
          pos2.x() = pos1.x() - tick_size;
          glVertex2dv(pos1.c_data());
          glVertex2dv(pos2.c_data());
        } else {
          pos1.y() = scale_rect.top();
          pos2.y() = pos1.y() + tick_size;
          glVertex2dv(pos1.c_data());
          glVertex2dv(pos2.c_data());
  
          pos1.y() = scale_rect.bottom();
          pos2.y() = pos1.y() - tick_size;
          glVertex2dv(pos1.c_data());
          glVertex2dv(pos2.c_data());
        }
      }
      glEnd();
    } while(false);
  
    stopScreenCoordinatesSystem();
    glPopAttrib();
  }
  
  void Colorbar::getValues(double start, double end, double delta, array& result) const
  {
    double new_start = floor(start / delta) * delta;
    if((start < epsilon and fabs(new_start - start) > epsilon)
       or (start >= epsilon and fabs((new_start - start) / start) > epsilon))
      new_start += delta;
    int nb_values = floor((end + delta / 100 - new_start) / delta) + 1;
    result.resize(nb_values);
    int i = 0;
    for(double value = new_start; value < end + delta / 100; value += delta) {
      result[i++] = value;
    }
    if(DEBUG) {
      Information::out << "  value = ";
      for(size_t i = 0; i < result.size(); ++i) {
        Information::out << result[i] << " ";
      }
    }
  }
  
  Colorbar::array Colorbar::selectValues(double length, bool is_vertical, const QFontMetricsF* arg_metric) const
  {
    const QFontMetricsF* metric = arg_metric ? arg_metric : new QFontMetricsF(font);
    double min_dist = 1;
    if(!is_vertical)   // Find the maximum size of 2 figures
    {
      for(int i = 0; i < 10; ++i) {
        QString t = QString::number(i) + QString::number(i);
        double w = metric->boundingRect(t).width();
        if(min_dist < w)
          min_dist = w;
      }
    }
    array result = selectValuesDirect(length, is_vertical, *metric, min_dist);
    if(arg_metric == 0)
      delete metric;
    return result;
  }
  
  Colorbar::array Colorbar::selectValuesDirect(double length, bool is_vertical, const QFontMetricsF& metric,
                                                 double min_dist) const
  {
    double start = vmin;
    double end = vmax;
    double real_delta, exp;
    tie(real_delta, exp) = significantDigits(start, end);
    array ticks;
    getValues(start, end, real_delta * exp, ticks);
    while(!canRenderTicks(ticks, length, min_dist, is_vertical, metric)) {
      if(real_delta == 1)
        real_delta = 2;
      else if(real_delta == 2)
        real_delta = 5;
      else if(real_delta == 5) {
        exp *= 10;
        real_delta = 1;
      } else {
        exp *= 10;
        real_delta = 2;
      }
      getValues(start, end, real_delta * exp, ticks);
      if(ticks.size() < 2)
        return array();
    }
    return ticks;
  }
  
  std::pair<double, double> Colorbar::significantDigits(double start, double end) const
  {
    double delta = (end - start) / 10;
    double first_num = floor(log10(delta));
    double exp = pow(10.0, first_num);
    double real_delta = ceil(delta / exp);
    if(real_delta > 6)
      real_delta = 10;
    else if(real_delta > 2)
      real_delta = 5;
    return std::make_pair(real_delta, exp);
  }
  
  QStringList Colorbar::_tick2str(const array& ticks, QString* extra) const
  {
    if(DEBUG) {
      Information::out << "_tick2str" << endl;
      Information::out << "Representation of the ticks (";
      for(size_t i = 0; i < ticks.size(); ++i) {
        Information::out << ticks[i];
        if(i < ticks.size() - 1)
          Information::out << ',';
      }
      Information::out << ")" << endl;
    }
    double m = abs(ticks).max();
    double exp;
    array new_ticks = ticks;
    double factor = 1;
    array absticks = abs(ticks);
    array absolute_numbers = absticks[ticks != 0.0];
  
    if(DEBUG) {
      Information::out << "Working on numbers (";
      for(size_t i = 0; i < absolute_numbers.size(); ++i) {
        Information::out << absolute_numbers[i];
        if(i < absolute_numbers.size() - 1)
          Information::out << ',';
      }
      Information::out << ")" << endl;
    }
    if(m <= 0.01) {
      exp = log10(absolute_numbers).max();
      exp = floor(exp);
      factor = pow(10.0, exp);
      new_ticks /= factor;
      array t = abs(new_ticks.apply(round) - new_ticks);
      t[new_ticks != 0.0] /= new_ticks[new_ticks != 0.0];
      if((t > epsilon).sum()) {
        exp = floor(log10(m));
        factor = pow(10.0, exp);
        new_ticks = ticks / factor;
      }
    } else if(m >= 20000) {
      exp = round(log10(absolute_numbers).min());
      factor = pow(10.0, exp);
      new_ticks /= factor;
      array t = abs(new_ticks.apply(round) - new_ticks);
      t[new_ticks != 0.0] /= new_ticks[new_ticks != 0.0];
      if((t > epsilon).sum()) {
        exp = floor(log10(m));
        factor = pow(10.0, exp);
        new_ticks = ticks / factor;
      }
    } else {
  #ifdef _MSC_VER
      unsigned long nan[2] = { 0xffffffff, 0x7fffffff };
      exp = *(double*)nan;
  // exp = std::numeric_limits<double>::quiet_NaN();
  #else
      exp = nan("");
  #endif
    }
    new_ticks[abs((new_ticks * factor) / abs(ticks).max()) < epsilon] = 0;
    QStringList ticks_str;
    if(DEBUG) {
      Information::out << "new_ticks = (";
      for(size_t i = 0; i < new_ticks.size(); ++i) {
        Information::out << new_ticks[i];
        if(i < new_ticks.size() - 1)
          Information::out << ',';
      }
      Information::out << ")" << endl;
    }
    for(size_t i = 0; i < new_ticks.size(); ++i) {
      ticks_str << QString::number(new_ticks[i]);
    }
    if(extra) {
      if(isNan(exp))
        *extra = QString();
      else
        *extra = QString::number(int(round(exp)));
    }
    if(DEBUG) {
      Information::out << "  max tick = " << ticks.max() << endl << "  exp = " << exp << endl 
                       << "  factor = " << factor << endl;
      if(extra)
        Information::out << "  extra = " << *extra << endl;
    }
    return ticks_str;
  }
  
  bool Colorbar::canRenderTicks(const array& ticks, double length, double min_dist, bool is_vertical,
                                 const QFontMetricsF& font_metric) const
  {
    double b = vmin;
    double e = vmax;
    double dl = length / (e - b);
    array pos = dl * (ticks - b);
    double cur_pos = -length;
    QStringList ticks_str = _tick2str(ticks);
    for(size_t i = 0; i < pos.size(); ++i) {
      QString t = ticks_str[i];
      double p = pos[i];
      QRectF r = font_metric.boundingRect(t);
      double w;
      if(is_vertical)
        w = r.height() / 2;
      else
        w = r.width() / 2;
      double left = p - w;
      if(left < cur_pos)
        return false;
      cur_pos = p + w + min_dist;
    }
    return true;
  }
  
  template <typename stream> void writepos(stream& s, const Colorbar::Position& pos)
  {
    switch(pos) {
    case Colorbar::LEFT:
      s << "Left";
      break;
    case Colorbar::RIGHT:
      s << "Right";
      break;
    case Colorbar::TOP:
      s << "Top";
      break;
    case Colorbar::BOTTOM:
      s << "Bottom";
      break;
    case Colorbar::TOP_LEFT:
      s << "TopLeft";
      break;
    case Colorbar::TOP_RIGHT:
      s << "TopRight";
      break;
    case Colorbar::BOTTOM_LEFT:
      s << "BottomLeft";
      break;
    case Colorbar::BOTTOM_RIGHT:
      s << "BottomRight";
      break;
    }
  }
  
  template <typename str> bool readpos(const str& s, Colorbar::Position& pos)
  {
    if(s == "Left")
      pos = Colorbar::LEFT;
    else if(s == "Right")
      pos = Colorbar::RIGHT;
    else if(s == "Top")
      pos = Colorbar::TOP;
    else if(s == "Bottom")
      pos = Colorbar::BOTTOM;
    else if(s == "TopLeft")
      pos = Colorbar::TOP_LEFT;
    else if(s == "TopRight")
      pos = Colorbar::TOP_RIGHT;
    else if(s == "BottomLeft")
      pos = Colorbar::BOTTOM_LEFT;
    else if(s == "BottomRight")
      pos = Colorbar::BOTTOM_RIGHT;
    else {
      pos = Colorbar::TOP_LEFT;
      return false;
    }
    return true;
  }
  
  std::ostream& operator<<(std::ostream& s, const Colorbar::Position& pos)
  {
    writepos(s, pos);
    return s;
  }
  
  std::istream& operator>>(std::istream& s, Colorbar::Position& pos)
  {
    std::string str;
    s >> str;
    if(!readpos(str, pos)) {
      std::cerr << "Error, cannot interpret the position '" << str << "', position will default to Left" << std::endl;
    }
    return s;
  }
  
  QTextStream& operator<<(QTextStream& s, const Colorbar::Position& pos)
  {
    writepos(s, pos);
    return s;
  }
  
  QTextStream& operator>>(QTextStream& s, Colorbar::Position& pos)
  {
    QString str;
    s >> str;
    if(!readpos(str, pos)) {
      err << "Error, cannot interpret the position '" << str << "', position will default to Right" << endl;
    }
    return s;
  }
  
  QDataStream& operator<<(QDataStream& s, const Colorbar::Position& pos)
  {
    s << (unsigned int)pos;
    return s;
  }
  
  QDataStream& operator>>(QDataStream& s, Colorbar::Position& pos)
  {
    unsigned int i;
    s >> i;
    if(i < 4) {
      pos = (Colorbar::Position)i;
    } else {
      std::cerr << "Error, integer value " << i << " doesn't correspond to a valid position, using Right as default"
                << std::endl;
      pos = Colorbar::RIGHT;
    }
    return s;
  }
  
  void Colorbar::readParms(Parms& parms, QString section)
  {
    parms(section, "ScaleLength", scale_length, 0.3);
    parms(section, "Width", width, 20.0);
    parms(section, "DistanceToBorder", distance_to_border, 5.0);
    parms(section, "TextToBar", text_to_bar, 5.0);
    parms(section, "TickSize", tick_size, 2.0);
    parms(section, "ExpSize", exp_size, 0.7);
    parms(section, "Epsilon", epsilon, 1e-9);
    parms(section, "LineWidth", line_width, 2.0);
    int ps;
    parms(section, "FontSize", ps, 10);
    font.setPointSize(ps);
    QString ori;
    parms(section, "Orientation", ori, QString("Vertical"));
    if(ori == "Horizontal")
      orientation = HORIZONTAL;
    else if(ori == "Vertical")
      orientation = VERTICAL;
    else {
      err << "Error, invalid orientation: " << ori << endl;
      orientation = VERTICAL;
    }
  
    parms(section, "Position", position, TOP_LEFT);
    if(position == TOP or position == BOTTOM)
      orientation = HORIZONTAL;
    else if(position == LEFT or position == RIGHT)
      orientation = VERTICAL;
  }
  
  void Colorbar::writeParms(QTextStream& pout, QString section)
  {
    pout << endl;
    pout << "[" << section << "]" << endl;
    pout << "ScaleLength: " << scale_length << endl;
    pout << "Width: " << width << endl;
    pout << "DistanceToBorder: " << distance_to_border << endl;
    pout << "TextToBar: " << text_to_bar << endl;
    pout << "TickSize: " << tick_size << endl;
    pout << "ExpSize: " << exp_size << endl;
    pout << "Epsilon: " << epsilon << endl;
    pout << "LineWidth: " << line_width << endl;
    pout << "Position: " << position << endl;
    pout << "FontSize: " << font.pointSize() << endl;
    pout << "Orientation: ";
    switch(orientation) {
    case HORIZONTAL:
      pout << "Horizontal" << endl;
      break;
    case VERTICAL:
      pout << "Vertical" << endl;
      break;
    }
  }
  
  void Colorbar::startScreenCoordinatesSystem(QPaintDevice* device) const
  {
    glMatrixMode(GL_PROJECTION);
    glPushMatrix();
    glLoadIdentity();
    glOrtho(0, device->width() / globalScaling, device->height() / globalScaling, 0, 0.0, -1.0);
  
    glMatrixMode(GL_MODELVIEW);
    glPushMatrix();
    glLoadIdentity();
  }
  
  void Colorbar::scaleDrawing(double scale) {
    globalScaling = scale;
  }
  
  void Colorbar::restoreScale() {
    globalScaling = 1.0;
  }
  
  void Colorbar::stopScreenCoordinatesSystem() const
  {
    glMatrixMode(GL_PROJECTION);
    glPopMatrix();
  
    glMatrixMode(GL_MODELVIEW);
    glPopMatrix();
  }
}
