/*
 * (C) Copyright 1996-2016 ECMWF.
 *
 * This software is licensed under the terms of the Apache Licence Version 2.0
 * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0.
 * In applying this licence, ECMWF does not waive the privileges and immunities
 * granted to it by virtue of its status as an intergovernmental organisation
 * nor does it submit to any jurisdiction.
 */

/*! \file MatrixHandler.h
    \brief Definition of the Template class MatrixHandler.

    Magics Team - ECMWF 2004

    Started: Wed 18-Feb-2004

    Changes:

*/

#ifndef MatrixHandler_H
#define MatrixHandler_H

#include "AutoVector.h"
#include "BasePointsHandler.h"
#include "Matrix.h"
#include "ProjP.h"
#include "Timer.h"
#include "Transformation.h"
#include "magics.h"

namespace magics {

class MatrixHandler : public AbstractMatrix, public AbstractPoints {
 public:
  MatrixHandler(const AbstractMatrix& matrix)
      : AbstractMatrix(),
        AbstractPoints(),
        matrix_(matrix),
        min_(INT_MAX),
        max_(-INT_MAX),
        tile_(false) {}
  MatrixHandler(const MatrixHandler& matrix)
      : AbstractMatrix(),
        AbstractPoints(),
        matrix_(matrix),
        min_(INT_MAX),
        max_(-INT_MAX),
        tile_(false) {}

  virtual ~MatrixHandler() {}

  virtual bool tile() { return tile_; }

  virtual double operator()(int i, int j) const { return matrix_(i, j); }

  virtual int rowIndex(double r) const { return matrix_.rowIndex(r); }
  virtual int columnIndex(double c) const { return matrix_.columnIndex(c); }
  virtual bool akimaEnable() const { return matrix_.akimaEnable(); }
  virtual bool delegate() const { return matrix_.delegate(); }

  virtual void boundRow(double r, double& row1, int& index1, double& row2,
                        int& index2) const {
    return matrix_.boundRow(r, row1, index1, row2, index2);
  }
  virtual void boundColumn(double r, double& column1, int& index1,
                           double& column2, int& index2) const {
    return matrix_.boundColumn(r, column1, index1, column2, index2);
  }

  virtual double left() const { return matrix_.left(); }
  virtual double bottom() const { return matrix_.bottom(); }
  virtual double right() const { return matrix_.right(); }
  virtual double top() const { return matrix_.top(); }

  double x(double x, double y) const { return matrix_.x(x, y); }
  double y(double x, double y) const { return matrix_.y(x, y); }

  virtual double nearest(double row, double column, double& rowOut,
                         double& columnOut) const {
    rowOut = -1;
    columnOut = -1;
    return nearest(row, column);
  }
  virtual double nearest(double row, double column) const {
    if (columns() == 0 || rows() == 0) return matrix_.missing();

    if (column < left() && !same(column, left())) return matrix_.missing();
    if (column > right() && !same(column, right())) return matrix_.missing();
    if (row < bottom() && !same(row, bottom())) return matrix_.missing();
    if (row > top() && !same(row, top())) return matrix_.missing();

    int ri = rowIndex(row);
    int ci = columnIndex(column);
    if (ri != -1 && ci != -1) return (*this)(ri, ci);
    double x1, x2;
    double y1, y2;
    int r1, r2, c1, c2;
    vector<double> distances;
    map<double, pair<std::pair<double, double>, pair<int, int> > > helper;
    vector<std::pair<std::pair<double, double>, pair<int, int> > > coordinates;
    if (ri != -1) {
      boundColumn(column, x1, c1, x2, c2);
      if (x1 != -1)
        coordinates.push_back(
            make_pair(make_pair(row, x1), std::make_pair(ri, c1)));
      if (x2 != -1)
        coordinates.push_back(
            make_pair(make_pair(row, x2), std::make_pair(ri, c2)));
    } else if (ci != -1) {
      boundRow(row, y1, r1, y2, r2);
      if (y1 != -1)
        coordinates.push_back(
            make_pair(make_pair(y1, column), std::make_pair(r1, ci)));
      if (y2 != -1)
        coordinates.push_back(
            make_pair(make_pair(y2, column), std::make_pair(r2, ci)));
    } else {
      boundColumn(column, x1, c1, x2, c2);
      boundRow(row, y1, r1, y2, r2);

      // 4 points ...
      // x1, y1 - x2, y1 -  x1, y2 - x2, y2
      // find the nearest...
      if (y1 != -1) {
        if (x1 != -1)
          coordinates.push_back(
              make_pair(make_pair(y1, x1), std::make_pair(r1, c1)));
        if (x2 != -1)
          coordinates.push_back(
              make_pair(make_pair(y1, x2), std::make_pair(r1, c2)));
      }
      if (y2 != -1) {
        if (x1 != -1)
          coordinates.push_back(
              make_pair(make_pair(y2, x1), std::make_pair(r2, c1)));
        if (x2 != -1)
          coordinates.push_back(
              make_pair(make_pair(y2, x2), std::make_pair(r2, c2)));
      }
    }

    for (vector<pair<std::pair<double, double>, pair<int, int> > >::iterator
             coord = coordinates.begin();
         coord != coordinates.end(); ++coord) {
      double distance =
          (row - coord->first.first) * (row - coord->first.first) +
          (column - coord->first.second) * (column - coord->first.second);
      // cout << distance << " [ " << coord->first.first << ", " <<
      // coord->first.second << "]" << endl;
      distances.push_back(distance);
      helper.insert(make_pair(distance, *coord));
    }

    if (distances.empty()) return matrix_.missing();

    double min = *std::min_element(distances.begin(), distances.end());

    map<double, pair<std::pair<double, double>, pair<int, int> > >::iterator n =
        helper.find(min);

    if (n == helper.end()) return matrix_.missing();

    return (*this)(n->second.second.first, n->second.second.second);
  }

  virtual double interpolate(double i, double j) const {
    if (columns() == 0 || rows() == 0) return matrix_.missing();

    if (j < left()) {
      if (!same(j, left())) return matrix_.missing();
      j = left();  // not really necessary but now there will be no more obscur
                   // rounding problems!
    }
    if (j > right()) {
      if (!same(j, right())) return matrix_.missing();
      j = right();  // id left
    }
    if (i < bottom()) {
      if (!same(i, bottom())) return matrix_.missing();
      i = bottom();  // id left
    }
    if (i > top()) {
      if (!same(i, top())) return matrix_.missing();
      i = top();  // id left
    }

    int ii = rowIndex(i);
    if (ii == -1) {
      // interpolate between 2 rows.
      double v1, v2;
      int i1, i2;
      boundRow(i, v1, i1, v2, i2);

      if (i1 == -1 || i2 == -1) return missing();
      internal_ = true;
      double a = (*this).interpolate(v1, j);
      internal_ = false;

      if (same(a, missing())) return missing();
      if (i1 == i2 || v1 == v2)
        return a;  // already seen with Akima760 (da,db ---> zero divide !)

      internal_ = true;
      double b = (*this).interpolate(v2, j);
      internal_ = false;
      if (same(b, missing())) return missing();

      double da = (v2 - i) / (v2 - v1);
      double db = (i - v1) / (v2 - v1);
      double val = (a * da) + (b * db);
      return val;
    }
    int jj = columnIndex(j);
    if (jj == -1) {
      double v1, v2;
      int i1, i2;
      boundColumn(j, v1, i1, v2, i2);
      if (i1 == -1 || i2 == -1) return missing();

      double a = (*this)(ii, i1);
      if (same(a, missing())) return missing();

      if (i1 == i2 || v1 == v2) return a;  // Id rows

      double b = (*this)(ii, i2);

      if (same(b, missing())) return missing();

      double da = (v2 - j) / (v2 - v1);
      double db = (j - v1) / (v2 - v1);
      double val = (a * da) + (b * db);
      return val;
    }

    return (*this)(ii, jj);
  }

  void setTile() { tile_ = true; }

  virtual int rows() const { return matrix_.rows(); }
  virtual int columns() const { return matrix_.columns(); }
  virtual int lowerRow(double v) const { return matrix_.lowerRow(v); }
  virtual int lowerColumn(double v) const { return matrix_.lowerColumn(v); }
  virtual double XResolution() const { return matrix_.XResolution(); }
  virtual double YResolution() const { return matrix_.YResolution(); }
  virtual double width() const { return matrix_.width(); }
  virtual double height() const { return matrix_.height(); }

  virtual const AbstractMatrix& original() const {
    return delegate() ? matrix_ : matrix_.original();
  }
  virtual int firstRow() const { return matrix_.firstRow(); }
  virtual int nextRow(int i, int f) const { return matrix_.nextRow(i, f); }
  virtual int firstColumn() const { return matrix_.firstColumn(); }
  virtual int nextColumn(int j, int f) const {
    return matrix_.nextColumn(j, f);
  }

  virtual void setMinMax() const {
    int nb_rows = rows();
    int nb_columns = columns();
    double missing = matrix_.missing();

    for (int r = 0; r < nb_rows; r++) {
      for (int c = 0; c < nb_columns; c++) {
        double val = (*this)(r, c);
        if (val == missing) continue;
        if (val < min_) min_ = val;
        if (val > max_) max_ = val;
      }
    }
  }

  double min() const {
    if (min_ != INT_MAX) return min_;

    setMinMax();
    return min_;
  }

  double max() const {
    if (max_ != -INT_MAX) return max_;

    setMinMax();
    return max_;
  }

  virtual double minX() const { return matrix_.minX(); }
  virtual double maxX() const { return matrix_.maxX(); }
  virtual double minY() const { return matrix_.minY(); }
  virtual double maxY() const { return matrix_.maxY(); }

  // Implements the AbstractPoints interface
  virtual void setToFirst() {
    if (points_.empty()) {
      int nb_rows = rows();
      int nb_columns = columns();

      points_.reserve(nb_rows * nb_columns);

      for (int r = 0; r < nb_rows; r++) {
        for (int c = 0; c < nb_columns; c++) {
          if (matrix_.accept(column(r, c), row(r, c)))
            if (!same((*this)(r, c), matrix_.missing()))
              points_.push_back(
                  new UserPoint(column(r, c), row(r, c), (*this)(r, c)));
        }
      }
    }
    current_ = points_.begin();
  }

  //! Method to test the end of collection.
  virtual bool more() { return current_ != points_.end(); }

  virtual bool accept(double x, double y) const { return matrix_.accept(x, y); }

  virtual UserPoint& current() { return **current_; }

  virtual void advance() { current_++; }

  virtual vector<double>& rowsAxis() const {
    return const_cast<MatrixHandler*>(this)->matrix_.rowsAxis();
  }
  virtual vector<double>& columnsAxis() const {
    return const_cast<MatrixHandler*>(this)->matrix_.columnsAxis();
  }

  virtual double row(int i, int j) const { return matrix_.row(i, j); }
  virtual double column(int i, int j) const { return matrix_.column(i, j); }

  virtual double regular_row(int i) const { return matrix_.regular_row(i); }
  virtual double regular_column(int i) const {
    return matrix_.regular_column(i);
  }

  virtual double missing() const { return matrix_.missing(); }
  virtual bool hasMissingValues() const {
    for (int r = 0; r < rows(); r++) {
      for (int c = 0; c < columns(); c++) {
        if (operator()(r, c) == matrix_.missing()) return true;
      }
    }
    return false;
  }

  const AbstractMatrix& matrix() { return matrix_; }

  MatrixHandler* getReady(const Transformation& transformation) {
    return matrix_.getReady(transformation);
  }

 protected:
  const AbstractMatrix& matrix_;
  mutable AutoVector<UserPoint> points_;
  mutable AutoVector<UserPoint>::const_iterator current_;
  mutable double min_;
  mutable double max_;
  mutable bool internal_;
  mutable bool tile_;
};

class TransformMatrixHandler : public MatrixHandler {
 public:
  TransformMatrixHandler(const AbstractMatrix& matrix)
      : MatrixHandler(matrix) {}

  double operator()(int i, int j) const {
    return matrix_(i + minrow_, j + mincolumn_);
  }

  double left() const { return minx_; }
  double right() const { return maxx_; }
  double bottom() const { return miny_; }
  double top() const { return maxy_; }

  void set() {
    for (int i = 0; i < rows(); i++) {
      double row = matrix_.regular_row(minrow_ + i);
      rowsMap_.insert(make_pair(row, i));
      fastRows_.push_back(row);
    }
    for (int i = 0; i < columns(); i++) {
      double column = matrix_.regular_column(mincolumn_ + i);
      columnsMap_.insert(make_pair(column, i));
      fastColumns_.push_back(column);
    }
    minx_ = std::min(fastColumns_.front(), fastColumns_.back());
    maxx_ = std::max(fastColumns_.front(), fastColumns_.back());
    miny_ = std::min(fastRows_.front(), fastRows_.back());
    maxy_ = std::max(fastRows_.front(), fastRows_.back());
  }

  int rows() const { return maxrow_ - minrow_ + 1; }
  int columns() const { return maxcolumn_ - mincolumn_ + 1; }
  double regular_row(int index) const { return fastRows_[index]; }
  double regular_column(int index) const { return fastColumns_[index]; }
  double real_row(int index) const { return fastRows_[index]; }
  double real_column(int index) const { return fastColumns_[index]; }
  inline double column(int, int j) const { return fastColumns_[j]; }
  virtual double real_row(double row, double) const { return row; }
  virtual double real_column(double, double column) const { return column; }
  inline double row(int i, int) const { return fastRows_[i]; }
  virtual bool hasMissingValues() const { return matrix_.hasMissingValues(); }
  double interpolate(double i, double j) const {
    return matrix_.interpolate(i, j);
  }
  double missing() const { return matrix_.missing(); }
  int lowerRow(double r) const {
    int last = -1;
    for (map<double, int>::const_iterator i = rowsMap_.begin();
         i != rowsMap_.end(); ++i) {
      if (i->first > r) {
        return last;
      }
      last = i->second;
    }
    return -1;
  }
  int lowerColumn(double c) const {
    int last = -1;
    for (map<double, int>::const_iterator i = columnsMap_.begin();
         i != columnsMap_.end(); ++i) {
      if (i->first > c) return last;
      last = i->second;
    }
    return -1;
  }
  int upperRow(double r) const {
    for (map<double, int>::const_iterator i = rowsMap_.begin();
         i != rowsMap_.end(); ++i) {
      if (i->first > r) {
        return i->second;
      }
    }
    return -1;
  }
  int upperColumn(double c) const {
    for (map<double, int>::const_iterator i = columnsMap_.begin();
         i != columnsMap_.end(); ++i) {
      if (i->first > c) return i->second;
    }
    return -1;
  }

 protected:
  int minrow_;
  int maxrow_;
  int mincolumn_;
  int maxcolumn_;
  map<double, int> rowsMap_;
  map<double, int> columnsMap_;
  vector<double> fastRows_;
  vector<double> fastColumns_;
  double minx_;
  double maxx_;
  double miny_;
  double maxy_;

  bool rowrevert_;
  bool columnrevert_;
};

class DelegateMatrixHandler : public MatrixHandler {
 public:
  DelegateMatrixHandler(const AbstractMatrix& matrix) : MatrixHandler(matrix) {}

  double interpolate(double row, double column) const {
    return matrix_.interpolate(row, column);
  }
  double nearest(double row, double column) const {
    return matrix_.nearest(row, column);
  }

  double column(int i, int j) { return matrix_.column(i, j); }
  double row(int i, int j) { return matrix_.row(i, j); }
  double left() const { return matrix_.left(); }
  double bottom() const { return matrix_.bottom(); }
  double right() const { return matrix_.right(); }
  double top() const { return matrix_.top(); }

 protected:
};

#include "magics_windef.h"
#ifdef MAGICS_ON_WINDOWS
#define PROJ_MSVC_DLL_IMPORT 1
#endif

#include <ProjP.h>

class Proj4MatrixHandler : public MatrixHandler {
 public:
  Proj4MatrixHandler(const AbstractMatrix& matrix, const string&);

  double interpolate(double row, double column) const;
  double nearest(double row, double column) const;

  double column(int, int) const;
  double row(int, int) const;

  bool delegate() const { return true; }

 protected:
  double minx_;
  double maxx_;
  double miny_;
  double maxy_;

  LatLonProjP projHelper_;
};

class RotatedMatrixHandler : public MatrixHandler {
 public:
  RotatedMatrixHandler(const AbstractMatrix& matrix, double lat, double lon);

  double interpolate(double row, double column) const;
  double nearest(double row, double column) const;

  double column(int, int) const;
  double row(int, int) const;

  bool delegate() const { return true; }
  pair<double, double> unrotate(double, double) const;
  pair<double, double> rotate(double, double) const;

 protected:
  double minx_;
  double maxx_;
  double miny_;
  double maxy_;
  double southPoleLat_;
  double southPoleLon_;
};

class BoxMatrixHandler : public TransformMatrixHandler {
 public:
  BoxMatrixHandler(const AbstractMatrix& matrix,
                   const Transformation& transformation)
      : TransformMatrixHandler(matrix),
        transformation_(transformation),
        original_(0) {
    double minx = std::min(transformation.getMinX(), transformation.getMaxX());
    double maxx = std::max(transformation.getMinX(), transformation.getMaxX());
    double miny = std::min(transformation.getMinY(), transformation.getMaxY());
    double maxy = std::max(transformation.getMinY(), transformation.getMaxY());

    int rows = matrix_.rows();
    int columns = matrix_.columns();

    mincolumn_ = columns - 1;
    maxcolumn_ = 0;
    minrow_ = rows - 1;
    maxrow_ = 0;

    for (int row = 0; row < rows; row++) {
      for (int column = 0; column < columns; column++) {
        double x = matrix_.column(row, column);
        double y = matrix_.row(row, column);
        if (minx <= x && x < maxx && miny <= y && y <= maxy) {
          mincolumn_ = std::min(mincolumn_, column);
          maxcolumn_ = std::max(maxcolumn_, column);
          minrow_ = std::min(minrow_, row);
          maxrow_ = std::max(maxrow_, row);
        }
      }
    }

    if (mincolumn_ > maxcolumn_) {
      mincolumn_ = maxcolumn_;
      MagLog::warning() << "No data to plot in the requested area" << endl;
    }
    if (minrow_ > maxrow_) {
      minrow_ = maxrow_;
      MagLog::warning() << "No data to plot in the requested area" << endl;
    }
    // MagLog::broadcast();

    mincolumn_ = std::max(mincolumn_ - 1, 0);
    maxcolumn_ = std::min(maxcolumn_ + 1, columns - 1);

    columnrevert_ =
        matrix_.column(0, maxcolumn_) < matrix_.column(0, mincolumn_);

    minrow_ = std::max(minrow_ - 1, 0);
    maxrow_ = std::min(maxrow_ + 1, rows - 1);

    rowrevert_ = matrix_.row(maxrow_, 0) < matrix_.row(minrow_, 0);
    set();
  }

  virtual const AbstractMatrix& original() const {
    if (!original_)
      original_ = new BoxMatrixHandler(matrix_.original(), transformation_);
    return *original_;
  }

  virtual void boundRow(double r, double& row1, int& index1, double& row2,
                        int& index2) const {
    index1 = lowerRow(r);
    row1 = regular_row(index1);
    index2 = upperRow(r);
    row2 = regular_row(index2);
  }

  virtual void boundColumn(double r, double& column1, int& index1,
                           double& column2, int& index2) const {
    index1 = lowerColumn(r);
    column1 = regular_column(index1);
    index2 = upperColumn(r);
    column2 = regular_column(index2);
  }
  int rowIndex(double r) const {
    map<double, int>::const_iterator i = rowsMap_.lower_bound(r);
    if (i != rowsMap_.end()) {
      if (same(i->first, r)) return i->second;
    } else {  // Utilité ? A confirmer
      map<double, int>::const_reverse_iterator i = rowsMap_.rbegin();
      if (same(i->first, r)) return i->second;
    }
    return -1;
  }

  int columnIndex(double c) const {
    map<double, int>::const_iterator i = columnsMap_.lower_bound(c);
    if (i != columnsMap_.end()) {
      if (same(i->first, c)) return i->second;
    } else {  // Utilité ? A confirmer
      map<double, int>::const_reverse_iterator i = columnsMap_.rbegin();
      if (same(i->first, c)) return i->second;
    }
    return -1;
  }

  virtual ~BoxMatrixHandler() { delete original_; }

  // Implements the AbstractPoints interface
  virtual bool accept(double x, double y) const {
    return transformation_.in(x, y);
  }

  double minX() const {
    return std::min(transformation_.getMinX(), transformation_.getMaxX());
  }
  double maxX() const {
    return std::max(transformation_.getMinX(), transformation_.getMaxX());
  }
  double minY() const {
    return std::min(transformation_.getMinY(), transformation_.getMaxY());
  }
  double maxY() const {
    return std::max(transformation_.getMinY(), transformation_.getMaxY());
  }

 protected:
  const Transformation& transformation_;
  mutable BoxMatrixHandler* original_;
};

class GeoBoxMatrixHandler : public TransformMatrixHandler {
 public:
  GeoBoxMatrixHandler(const AbstractMatrix& matrix,
                      const Transformation& transformation);

  virtual const AbstractMatrix& original() const {
    if (!original_)
      original_ = new GeoBoxMatrixHandler(matrix_.original(), transformation_);
    return *original_;
  }

  int columns() const { return columnsMap_.size(); }
  int rows() const { return rowsMap_.size(); }

  int rowIndex(double r) const {
    map<double, int>::const_iterator i = rowsMap_.lower_bound(r);
    if (i != rowsMap_.end()) {
      if (same(i->first, r)) return i->second;
    } else {  // Utilité ? A confirmer
      map<double, int>::const_reverse_iterator i = rowsMap_.rbegin();
      if (same(i->first, r)) return i->second;
    }
    return -1;
  }

  int columnIndex(double c) const {
    map<double, int>::const_iterator i = columnsMap_.lower_bound(c);
    if (i != columnsMap_.end()) {
      if (same(i->first, c)) return i->second;
    } else {  // Utilité ? A confirmer
      map<double, int>::const_reverse_iterator i = columnsMap_.rbegin();
      if (same(i->first, c)) return i->second;
    }
    return -1;
  }

  inline double column(int, int column) const {
    return regular_longitudes_[column];
  }
  inline double row(int row, int) const { return regular_latitudes_[row]; }
  double operator()(int row, int column) const {
    if (columns_[column] == -1) return matrix_.missing();
    return matrix_(rows_[row], columns_[column]);
  }

  int lowerRow(double r) const {
    map<double, int>::const_iterator i = rowsMap_.lower_bound(r);
    if (i != rowsMap_.end()) {
      if (same(i->first, r)) return i->second;
      if (i != rowsMap_.begin()) {
        i--;
        return i->second;
      }
    }
    return -1;
  }

  int lowerColumn(double c) const {
    map<double, int>::const_iterator i = columnsMap_.lower_bound(c);
    if (i != columnsMap_.end()) {
      if (same(i->first, c)) return i->second;
      if (i != columnsMap_.begin()) {
        i--;
        return i->second;
      }
    }
    return -1;
  }

  double regular_row(int i) const { return regular_latitudes_[i]; }
  double regular_column(int i) const { return regular_longitudes_[i]; }

  virtual ~GeoBoxMatrixHandler() { delete original_; }

  // Implements the AbstractPoints interface
  virtual bool accept(double x, double y) const {
    return transformation_.in(x, y);
  }

  double minX() const {
    return std::min(transformation_.getMinX(), transformation_.getMaxX());
  }
  double maxX() const {
    return std::max(transformation_.getMinX(), transformation_.getMaxX());
  }
  double minY() const {
    return std::min(transformation_.getMinY(), transformation_.getMaxY());
  }
  double maxY() const {
    return std::max(transformation_.getMinY(), transformation_.getMaxY());
  }

  double left() const { return regular_longitudes_.front(); }
  double bottom() const { return regular_latitudes_.front(); }
  double right() const { return regular_longitudes_.back(); }
  double top() const { return regular_latitudes_.back(); }

  virtual void boundRow(double r, double& row1, int& index1, double& row2,
                        int& index2) const {
    index1 = lowerRow(r);
    if (index1 < 0) {
      index2 = -1;
      return;
    }

    row1 = regular_latitudes_[index1];
    if (index1 >= regular_latitudes_.size() - 1) {
      index2 = -1;
      return;
    }

    index2 = index1 + 1;
    row2 = regular_latitudes_[index2];
  }

  virtual void boundColumn(double r, double& column1, int& index1,
                           double& column2, int& index2) const {
    index1 = lowerColumn(r);
    if (index1 < 0) {
      index2 = -1;
      return;
    }

    column1 = regular_longitudes_[index1];
    if (index1 >= regular_longitudes_.size() - 1) {
      index2 = -1;
      return;
    }

    index2 = index1 + 1;
    column2 = regular_longitudes_[index2];
  }

 protected:
  const Transformation& transformation_;
  mutable GeoBoxMatrixHandler* original_;
  mutable map<int, int> rows_;
  mutable map<int, int> columns_;
  vector<double> regular_latitudes_;
  vector<double> regular_longitudes_;
};

class MonotonicIncreasingMatrixHandler : public MatrixHandler {
 public:
  MonotonicIncreasingMatrixHandler(const AbstractMatrix& matrix)
      : MatrixHandler(matrix) {
    // Check RowAxis...
    int row = matrix_.rows();
    if (matrix_.regular_row(1) - matrix_.regular_row(0) >=
        0)  // Increasing Axis...
      for (int i = 0; i < row; i++) {
        rows_[i] = i;
        newRowsMap_[matrix.regular_row(i)] = i;
      }
    else  // Decreasing axis...
      for (int i = 0; i < row; i++) {
        rows_[i] = (row - 1) - i;
        newRowsMap_[matrix.regular_row((row - 1) - i)] = i;
      }
    // Check ColumnAxis
    int column = matrix_.columns();
    if (matrix_.regular_column(1) - matrix_.regular_column(0) >=
        0)  // Increasing Axis...
      for (int j = 0; j < column; j++) {
        columns_[j] = j;
        newColumnsMap_[matrix.regular_column(j)] = j;
      }
    else  // Decreasing axis...
      for (int j = 0; j < column; j++) {
        columns_[j] = (column - 1) - j;
        newColumnsMap_[matrix.regular_column((column - 1) - j)] = j;
      }
  }
  virtual ~MonotonicIncreasingMatrixHandler() {}

  double operator()(int i, int j) const {
    int x = const_cast<MonotonicIncreasingMatrixHandler*>(this)->rows_[i];
    int y = const_cast<MonotonicIncreasingMatrixHandler*>(this)->columns_[j];

    return matrix_(x, y);
  }

  int rows() const { return matrix_.rows(); }
  virtual int columns() const { return matrix_.columns(); }
  virtual double regular_column(int i) const {
    return matrix_.regular_column(
        const_cast<MonotonicIncreasingMatrixHandler*>(this)->columns_[i]);
  }
  virtual double regular_row(int j) const {
    return matrix_.regular_row(
        const_cast<MonotonicIncreasingMatrixHandler*>(this)->rows_[j]);
  }
  virtual double interpolate(double i, double j) const {
    return matrix_.interpolate(i, j);
  }
  virtual double missing() const { return matrix_.missing(); }
  void print() {
    MagLog::debug() << "MonotonicIncreasingMatrixHandler->\n";
    for (int j = 0; j < rows(); j++) {
      for (int i = 0; i < columns(); i++) {
        MagLog::dev() << (*this)(j, i) << " ";
      }
      MagLog::dev() << "\n";
    }
    MagLog::debug() << "<--" << endl;
  }

  int lowerRow(double r) const {
    map<double, int>::const_iterator bound = newRowsMap_.find(r);
    if (bound != newRowsMap_.end()) return (*bound).second;

    bound = newRowsMap_.lower_bound(r);
    if (bound == newRowsMap_.end()) return -1;
    return (*bound).second - 1;
  }

  int lowerColumn(double c) const {
    map<double, int>::const_iterator bound = newColumnsMap_.find(c);
    if (bound != newColumnsMap_.end()) return (*bound).second;

    bound = newColumnsMap_.lower_bound(c);
    if (bound == newColumnsMap_.end()) return -1;
    return (*bound).second - 1;
  }

 protected:
  map<int, int> rows_;
  map<int, int> columns_;
  map<double, int> newRowsMap_;
  map<double, int> newColumnsMap_;
};

class OriginalMatrixHandler : public MatrixHandler {
 public:
  OriginalMatrixHandler(AbstractMatrix& matrix)
      : MatrixHandler(matrix.original()) {}
};

class ThinningMatrixHandler : public MatrixHandler {
 public:
  ThinningMatrixHandler(const AbstractMatrix& matrix, int fr, int fc)
      : MatrixHandler(matrix), frequencyRow_(fr), frequencyColumn_(fc) {
    int rows = matrix_.rows();
    int columns = matrix_.columns();

    int row = 0;
    for (int i = 0; i < rows; i += frequencyRow_) {
      rowIndex_.insert(make_pair(row, i));
      row++;
    }
    int column = 0;
    for (int i = 0; i < columns; i += frequencyColumn_) {
      // MagLog::dev()<< "Sample --> " << column << "=" << i << endl;
      columnIndex_.insert(make_pair(column, i));
      // MagLog::dev()<< "Sample --> " << column << "=" << i << "[" <<
      // regular_column(column) << "]" << endl;

      column++;
    }
    columnIndex_.insert(make_pair(column, columns - 1));
    // MagLog::dev()<< "Sample --> " << column << "=" << columns-1 << "[" <<
    // regular_column(column) << "]"<< endl;
  }

  int rows() const { return rowIndex_.size(); }
  int columns() const { return columnIndex_.size(); }

  double operator()(int row, int column) const {
    return matrix_(rowIndex(row), columnIndex(column));
  }
  double column(int row, int column) const {
    return matrix_.column(rowIndex(row), columnIndex(column));
  }
  double row(int row, int column) const {
    return matrix_.row(rowIndex(row), columnIndex(column));
  }
  double regular_row(int row) const {
    return matrix_.regular_row(rowIndex(row));
  }
  double regular_column(int column) const {
    return matrix_.regular_column(columnIndex(column));
  }

 protected:
  int columnIndex(int column) const {
    map<int, int>::const_iterator index = columnIndex_.find(column);
    ASSERT(index != columnIndex_.end());
    return index->second;
  }
  int rowIndex(int row) const {
    map<int, int>::const_iterator index = rowIndex_.find(row);
    ASSERT(index != rowIndex_.end());
    return index->second;
  }
  int frequencyRow_;
  int frequencyColumn_;
  map<int, int> rowIndex_;
  map<int, int> columnIndex_;
};

}  // namespace magics
#endif
