#ifndef IMAGE_REF_H
#define IMAGE_REF_H

#include <cassert>
#include "object.h"

namespace mimas {

/** To represent a displacement in an image.
    Used by image iterator */
struct dist2D{
  dist2D(int x = 0, int y = 0):
    width(x), height(y){}
  
  int width;
  int height;
};

/** Wrapper for read-only access of image-data.
    This class is used for wrapping raw constant data to make it accessible to
    the Mimas-library. */
template<
  typename T,
  typename TPtr = const T* >
class const_image_ref: public object
{
public:
  /** Default constructor.
      Creates an empty image. */
  const_image_ref(void): data(NULL), width(0), height(0) { dummy = T(); }
  ///
  const_image_ref( TPtr _data, int _width, int _height ):
    data(_data), width(_width), height(_height) { dummy = T(); }
  ///
  template< typename OPtr >
  const_image_ref( const const_image_ref< T, OPtr > &_imgref ):
    data( _imgref.rawData() ),
    width( _imgref.getWidth() ), height( _imgref.getHeight() )
    { dummy = T(); }
  ///
  class const_iterator{
  public:
    typedef T value_type;
    typedef const value_type * MoveX;
        
    struct MoveY{         
      MoveY(unsigned int width = 0):
        width(width),offset(0){
      }
      
      void operator++(void){
        offset += width;
      }

      //postfix operator. There is no difference as we don't return any value
      //but avoid any warning message
      void operator++(int){
        offset += width;
      }
      
      void operator--(void){
        offset -= width;
      }

      //postfix operator. There is no difference as we don't return any value
      //but avoid any warning message
      void operator--(int){
        offset -= width;
      }
          
      MoveY & operator+=(int n)
      {
        offset += width * n;
        return *this;
      }

      MoveY & operator-=(int n)
      {
        offset -= width * n;
        return *this;
      }
      
      int operator-(const MoveY &y2) const
      {
        return (offset - y2.offset)/width;
      }

      MoveY & operator=(const MoveY &y2) 
      {
        offset = y2.offset;
        width = y2.width;
        return *this;
      }
            
      bool operator==(const MoveY &y2) const
      {
        return (offset == y2.offset) && (width == y2.width);
      }

      bool operator<(const MoveY &y2) const
      {
        return offset < y2.offset;
      }

      unsigned int width;
      unsigned int offset;
    };
          
    MoveX x;
    MoveY y;
    
    const_iterator(const value_type *data = NULL, unsigned int width = 0):
      x(data), y(width) {}
        
    const_iterator(const const_iterator & rhs)
    {
      x = rhs.x;
      y = MoveY(rhs.y.width);  
      y.offset = rhs.y.offset;
    }
    
    const_iterator & operator=(const const_iterator & rhs)
    {
      x = rhs.x;
      y = rhs.y;
      return *this;
    }

    const_iterator & operator+=(const dist2D & dist)
    {
      x += dist.width;
      y += dist.height;
      return *this;
    }
    
    const_iterator & operator-=(const dist2D & dist)
    {
      x -= dist.width;
      y -= dist.height;
      return *this;
    }
    
    const_iterator operator+(const dist2D & dist) const 
    {
      const_iterator k(*this);
      k += dist;
      return k;
    }
    
    const_iterator operator-(const dist2D & dist) const
    {
      const_iterator k(*this);
      k -= dist;
      return k;
    }
    
    //mechanism to check the iterator of both images are of same width.
    dist2D operator-(const const_iterator & rhs) const
    {
#ifndef NDEBUG
      if(y.width != rhs.y.width){
        std::cerr<<"const_iterator (in image.h): you cannot compare"
          "2 iterators if both images don't have the same width" <<std::endl;
      }
#endif
      return dist2D(x - rhs.x, (y.offset - rhs.y.offset)/ rhs.y.width);
    }
    
    bool operator==(const const_iterator & imIt) const
    {
      return (x == imIt.x) && (y == imIt);
    }
    
    const value_type & operator*()
    {
      return *(x + y.offset);
    }
    
    const value_type operator*() const 
    {
      return *(x + y.offset);
    }
    
    const value_type &operator[](const dist2D dist) 
    {
      return *(x + y.offset + dist.width + dist.height * y.width);
    }
    
    const value_type operator[](const dist2D dist) const
    {
      return *(x + y.offset + dist.width + dist.height * y.width);
    }
    
  };

  /// Width of image.
  int getWidth(void) const { return width; }
  /// Height of image.
  int getHeight(void) const { return height; }

  /// Size
  int getSize(void) const { return width * height; }

  /** Get const-reference to a pixel.
      This method includes clipping! Specifying out-of-image
      coordinates will result in a const-reference to the dummy
      pixel.
      @param x x-coordinate
      @param y y-coordinate
      @see pixel
      @see dummy */
  const T &getPixel( int x, int y ) const {
    if ( x>=0 && y>=0 && x<getWidth() && y<getHeight() )
      return data[ y * getWidth() + x ];
    else
      return dummy;
  }

  /** Get const-reference to a pixel.
      No clipping!
      @param x x-coordinate
      @param y y-coordinate
      @see getPixel */
  const T& pixel(  int x, int y ) const {
    assert( x>=0 && y>=0 && x<getWidth() && y<getHeight() );
    return data[ y * getWidth() + x ];
  }

  /// Read-only access to data.
  const T *rawData( void ) const {
    return data;
  }

  ///
  bool initialised( void ) const {
    assert( ( data != NULL ) == ( width > 0 && height > 0 ) );
    return data != NULL;
  }

  //to return const values
  inline const_iterator upperLeft(void) const
  {
    return const_iterator(data,getWidth());
  }
  
  inline const_iterator lowerRight(void) const
  {
    const_iterator t(data,getWidth());
    t.x += getWidth();
    t.y += getHeight();
    return t;
  }

  inline const_iterator ul(void) const
  {
    return const_iterator(data,getWidth());
  }
      
  inline const_iterator lr(void) const
  {
    const_iterator t(data,getWidth());
    t.x += getWidth();
    t.y += getHeight();
    return t;
  }
protected:
  /** Data-pointer.
      Pointer to the data in row-major format. */
  TPtr data;
  int width;
  int height;
  T dummy;
};

/** Wrapper to access image-data.
    This class is used for wrapping raw data for the purpose of making it
    accessible to the Mimas-library. */
template< typename T >
class image_ref: public const_image_ref< T, T* >
{
public:
  /**iterator
     implementation of iterator for images as described in the paper
     STL-style generic programming with images
     by Ullrich Kothe
     
     Still not thoroughly tested
     @author Manuel Boissenin */
  class iterator {
  public:
    typedef T value_type;
    typedef value_type * MoveX;
    
    struct MoveY{         
      MoveY(unsigned int width = 0):
        width(width),offset(0) {
        //          std::cerr<< "constructor MoveY"<<std::endl;
      }

      void operator++(void){
        offset += width;
      }
      
      //postfix operator. There is no difference as we don't return any value
      //but avoid any warning message
      void operator++(int) {
        offset += width;
      }
      
      void operator--(void) {
        offset -= width;
      }

      //postfix operator. There is no difference as we don't return any value
      //but avoid any warning message
      void operator--(int) {
        offset -= width;
      }
          
      MoveY & operator+=(int n)
      {
        offset += width * n;
        return *this;
      }

      MoveY & operator-=(int n)
      {
        offset -= width * n;
        return *this;
      }
      
      int operator-(const MoveY &y2) const
      {
        return (offset - y2.offset)/width;
      }
      
      MoveY & operator=(const MoveY &y2) 
      {
        offset = y2.offset;
        width = y2.width;
        return *this;
      }
        
      bool operator==(const MoveY &y2) const
      {
        return (offset == y2.offset) && (width == y2.width);
      }

      bool operator<(const MoveY &y2) const
      {
        return offset < y2.offset;
      }
      
      unsigned int width;
      unsigned int offset;
    };
          
    MoveX x;
    MoveY y;
          
    iterator(value_type *data = NULL, unsigned int width = 0):
      x(data), y(width) {}
      
    iterator(const iterator & rhs)
    {
      x = rhs.x;
      y = MoveY(rhs.y.width);  
      y.offset = rhs.y.offset;
    }
    
    iterator & operator=(const iterator & rhs)
    {
      //std::cerr<< "operator= iterator"<<std::endl;
      x = rhs.x;
      y = rhs.y;
      return *this;
    }

    iterator & operator+=(const dist2D & dist)
    {
      x += dist.width;
      y += dist.height;
      return *this;
    }
  
    iterator & operator-=(const dist2D & dist)
    {
      x -= dist.width;
      y -= dist.height;
      return *this;
    }
          
    iterator operator+(const dist2D & dist) const 
    {
      iterator k(*this);
      k += dist;
      return k;
    }
    
    iterator operator-(const dist2D & dist) const
    {
      iterator k(*this);
      k -= dist;
      return k;
    }

    //mechanism to check the iterator of both images are of same width.
    dist2D operator-(const iterator & rhs) const
    {
#ifndef NDEBUG
      if(y.width != rhs.y.width){
        std::cerr<<"Image iterator (in image.h): you cannot compare"
          "2 iterators if both images don't have the same width" <<std::endl;
      }
#endif
      return dist2D(x - rhs.x, (y.offset - rhs.y.offset)/ rhs.y.width);
    }
    
    bool operator==(const iterator & imIt) const
    {
      return (x == imIt.x) && (y == imIt);
    }
    
    value_type & operator*()
    {
      return *(x + y.offset);
    }
    
    value_type operator*() const 
    {
      return *(x + y.offset);
    }

    value_type &operator[](const dist2D dist)
    {
      return *(x + y.offset + dist.width + (signed)( dist.height * y.width ) );
    }

    value_type operator[](const dist2D dist) const
    {
      return *(x + y.offset + dist.width + (signed)( dist.height * y.width ) );
    }
          
  };

  /** Default constructor.
      Creates an empty image. */
  image_ref(void) {}
  /** Copy constructor.
      The data-pointer of the other object is copied (i.e. the data will be
      shared). */
  explicit image_ref( const image_ref< T > &_imgref ):
    const_image_ref< T, T* >( _imgref ) {}

  ///
  image_ref( T *_data, int _width, int _height ):
    const_image_ref< T, T* >( _data, _width, _height ) {}

  /// Fill all pixel with a value.
  virtual void fill( const T &initVal ) {
    int size = image_ref< T >::getSize();
    std::fill( image_ref< T >::data,
               image_ref< T >::data + size, initVal );
  };

  /** Get reference to a pixel.
      This method includes clipping! Specifying out-of-image
      coordinates will result in a reference to a dummy pixel.
      @param x x-coordinate
      @param y y-coordinate
      @see pixel
      @see dummy */
  T &getPixel( int x, int y ) {
    if ( x>=0 && y>=0 &&
         x<image_ref< T >::getWidth() &&
         y<image_ref< T >::getHeight() )
      return image_ref< T >::data
        [ y * image_ref< T >::getWidth() + x ];
    else
      return image_ref< T >::dummy;
  }

  /** Get const-reference to a pixel.
      This method includes clipping! Specifying out-of-image
      coordinates will result in a const-reference to the dummy
      pixel.
      @param x x-coordinate
      @param y y-coordinate
      @see pixel
      @see dummy */
  const T &getPixel( int x, int y ) const {
    if ( x>=0 && y>=0 &&
         x<image_ref< T >::getWidth() &&
         y<image_ref< T >::getHeight() )
      return image_ref< T >::data
        [ y * image_ref< T >::getWidth() + x ];
    else
      return image_ref< T >::dummy;
  }

  /** Set value of a pixel.
      This method includes clipping!
      @param x x-coordinate
      @param y y-coordinate
      @param val New value.
      @see pixel */
  virtual void setPixel( int x, int y, T val ) {
    if ( x>=0 &&
         y>=0 &&
         x<image_ref< T >::getWidth() &&
         y<image_ref< T >::getHeight() )
      image_ref< T >::data
        [ y * image_ref< T >::getWidth() + x ]  =  val;
  }

  /** Get reference to a pixel.
      No clipping!
      @param x x-coordinate
      @param y y-coordinate
      @see setPixel */
  T& pixel(  int x, int y ) {
    assert( x>=0 && y>=0 && x<image_ref< T >::getWidth()&& y<image_ref< T >::getHeight() );
    return image_ref< T >::data
      [ y * image_ref< T >::getWidth() + x ];
  }

  /** Get const-reference to a pixel.
      No clipping!
      @param x x-coordinate
      @param y y-coordinate
      @see getPixel */
  const T& pixel(  int x, int y ) const {
    assert( x>=0 && y>=0 &&
            x<image_ref< T >::getWidth() &&
            y<image_ref< T >::getHeight() );
    return image_ref< T >::data
      [ y * image_ref< T >::getWidth() + x ];
  }

  /// Read-only access to data.
  const T *rawData( void ) const {
    return image_ref< T >::data;
  }

  /// Access to aggregated data.
  T *rawData(void) {
    return image_ref< T >::data;
  }

  inline iterator upperLeft(void)
  {
    return iterator(image_ref< T >::data,
                    image_ref< T >::getWidth());
  }
  
  inline iterator lowerRight(void)
  {
    iterator t(image_ref< T >::data,
               image_ref< T >::getWidth());
    t.x += image_ref< T >::getWidth();
    t.y += image_ref< T >::getHeight();
    return t;
  }
  
  //For lazy guys/pals/mates/fellows only!!!
  //the same as uperLeft
  inline iterator ul(void)
  {
    return iterator(image_ref< T >::data,
                    image_ref< T >::getWidth());
  }
  
  //the same as lowerRight
  inline iterator lr(void)
  {
    iterator t(image_ref< T >::data,
               image_ref< T >::getWidth());
    t.x += image_ref< T >::getWidth();
    t.y += image_ref< T >::getHeight();
    return t;
  }
  
};

}

#endif