/*
 *  Copyright (c) 2008,2010 Cyrille Berger <cberger@cberger.net>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation;
 * either version 2, or (at your option) any later version of the License.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this library; see the file COPYING.  If not, write to
 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 */

#ifndef _GTLCORE_REGION_H_
#define _GTLCORE_REGION_H_

#include <GTLCore/Export.h>

namespace GTLCore {
  /**
   * Define a region of an image. In OpenGTL, pixel coordinates are centered on the
   * middle of a physical pixel. So (0,0) correspond to the center of the top left
   * pixel, while (0.5,0.5) is the intersection of the four pixels in the top left
   * corner.
   * 
   * @ingroup GTLCore
   */
  template<typename _T_>
  class Region {
    protected:
      inline Region();
      inline Region( _T_ _x, _T_ _y, _T_ _cols, _T_ _rows );
    public:
      inline ~Region();
      inline _T_ x() const;
      inline _T_ y() const;
      /**
       * @return the number of rows in that region
       */
      inline _T_ rows() const;
      /**
       * @return the number of columns in that region
       */
      inline _T_ columns() const;
      /**
       * @return the coordinate of the left pixel in the region
       */
      inline _T_ left() const;
      /**
       * @return the coordinate of the top pixel in the region
       */
      inline _T_ top() const;
      /**
       * Set the top coordinate without changing bottom, making
       * needed adjustment to the number of rows
       */
      inline void setTop(_T_);
      /**
       * Set the left coordinate without changing right, making
       * needed adjustment to the number of columns
       */
      inline void setLeft(_T_);
      inline void setRows( _T_ _rows );
      inline void setCols( _T_ cols );
      inline GTLCore::Region<_T_> united( const Region<_T_>& _region ) const;
      inline GTLCore::Region<_T_>& operator+=( const Region<_T_>& _region );
      inline bool operator!=( const Region<_T_>& _region ) const;
    private:
      static inline _T_ min(_T_ v1, _T_ v2) { return (v1 < v2) ? v1 : v2; }
      static inline _T_ max(_T_ v1, _T_ v2) { return (v1 < v2) ? v2 : v1; }
    private:
      _T_ m_x, m_y, m_cols, m_rows;
  };
  
  #define MAKE_REGION(_name_, _T_) \
  
  class RegionF;
  
  /**
   * Integer version of the region.
   * @ingroup GTLCore
   */
  class RegionI : public Region<int> {
    public:
      RegionI() : Region<int>() {}
      RegionI(const Region<int>& _rhs) : Region<int>(_rhs) {}
      RegionI( int _x, int _y, int _cols, int _rows ) : Region<int>(_x, _y, _cols, _rows) {}
      inline RegionF toRegionF() const;
      int right() const
      {
          return x() + columns() - 1;
      }
      int bottom() const
      {
          return y() + rows() - 1;
      }
      /**
       * Set the bottom coordinate without changing top, making
       * needed adjustment to the number of rows
       */
      void setBottom(int v)
      {
        setRows(v - y() + 1);
      }
      /**
       * Set the right coordinate without changing left, making
       * needed adjustment to the number of columns
       */
      void setRight(int v)
      {
        setCols(v - x() + 1);
      }
  };
  
  /**
   * Floating-point version of the region.
   * @ingroup GTLCore
   */
  class RegionF : public Region<float> {
    public:
      RegionF() : Region<float>() {}
      RegionF( float _x, float _y, float _cols, float _rows ) : Region<float>(_x, _y, _cols, _rows) {}
      inline RegionI toRegionI() const;
      float right() const
      {
          return x() + columns();
      }
      float bottom() const
      {
          return y() + rows();
      }
      /**
       * Set the bottom coordinate without changing top, making
       * needed adjustment to the number of rows
       */
      void setBottom(int v)
      {
        setRows(v - y());
      }
      /**
       * Set the right coordinate without changing left, making
       * needed adjustment to the number of columns
       */
      void setRight(int v)
      {
        setCols(v - x());
      }
    };
  
  //--- Implementation ---//

  template<typename _T_>
  Region<_T_>::Region() : m_x(0), m_y(0), m_cols(0), m_rows(0)
  {
  }

  template<typename _T_>
  Region<_T_>::Region( _T_ _x, _T_ _y, _T_ _cols, _T_ _rows )
    : m_x(_x), m_y(_y), m_cols(_cols), m_rows(_rows)
  {
  }

  template<typename _T_>
  Region<_T_>::~Region()
  {
  }

  template<typename _T_>
  _T_ Region<_T_>::x() const
  {
    return m_x;
  }

  template<typename _T_>
  _T_ Region<_T_>::y() const
  {
    return m_y;
  }

  template<typename _T_>
  _T_ Region<_T_>::columns() const
  {
    return m_cols;
  }

  template<typename _T_>
  void Region<_T_>::setCols( _T_ _cols )
  {
    m_cols = _cols;
  }

  template<typename _T_>
  _T_ Region<_T_>::rows() const
  {
    return m_rows;
  }

  template<typename _T_>
  void Region<_T_>::setRows( _T_ _rows )
  {
    m_rows = _rows;
  }

  template<typename _T_>
  _T_ Region<_T_>::left() const
  {
    return m_x;
  }

  template<typename _T_>
  _T_ Region<_T_>::top() const
  {
    return m_y;
  }
  template<typename _T_>
  void Region<_T_>::setTop(_T_ v)
  {
    m_rows += m_y - v;
    m_y = v;
  }
  template<typename _T_>
  void Region<_T_>::setLeft(_T_ v)
  {
    m_cols += m_x - v;
    m_x = v;
  }

  template<typename _T_>
  bool Region<_T_>::operator!=( const Region& _region ) const
  {
    return x() != _region.x() or y() != _region.y() or columns() != _region.columns() or rows() != _region.rows();
  }

  template<typename _T_>
  GTLCore::Region<_T_>& Region<_T_>::operator+=( const Region& _region )
  {
    return (*this = this->united( _region ) );
  }

  template<typename _T_>
  GTLCore::Region<_T_> Region<_T_>::united( const Region& _region ) const
  {
    _T_ right = max( _region.left() + _region.columns(), this->left() + this->columns() );
    _T_ bottom = max( _region.top() + _region.rows(), this->top() + this->rows() );
    _T_ left = min( _region.left(), this->left() );
    _T_ top = min( _region.top(), this->top() );
    
    return GTLCore::Region<_T_>(  left, top, right - left, bottom - top );
  }

  RegionF RegionI::toRegionF() const
  {
    return RegionF(x(), y(), columns(), rows());
  }

  RegionI RegionF::toRegionI() const
  {
    int ix = int(x());
    int iy = int(y());
    return RegionI(ix, iy, int(columns()+0.5 + x() - ix), int(rows()+0.5 + y() - iy));
  }
}

#endif
