#ifndef RGB_ALGORITHMS_HPP
#define RGB_ALGORITHMS_HPP

#include <boost/gil/gil_all.hpp>

namespace boost { namespace gil {
namespace toolbox {

struct calc_negative
{
   template < typename DST_CHANNEL
            , typename SRC_CHANNEL
            > void operator()( DST_CHANNEL& dst 
                             , SRC_CHANNEL& src ) const
   {
      SRC_CHANNEL max = channel_traits< SRC_CHANNEL >::max_value();

      dst = max - src;
   }

   template < typename CHANNEL
            > void operator()( CHANNEL& c ) const
   {
      CHANNEL max = channel_traits< CHANNEL >::max_value();

      c = max - c;
   }
};

template< typename RGB_VIEW >
void negative( const RGB_VIEW& view )
{
   typedef typename channel_type< RGB_VIEW >::type channel_t;
   channel_t max = channel_traits<channel_t>::max_value();

   for( int y=0; y < view.height(); ++y )
   {
      typename RGB_VIEW::x_iterator it = view.row_begin( y );

      for( int x = 0; x < view.width(); ++x )
      {
         static_for_each( it[x], calc_negative() );
      }
   }
}

template< typename RGB_VIEW >
void negative( const RGB_VIEW& src
             , const RGB_VIEW& dst )
{
   // make sure src and dst have same dimensions.

   // for rgba do it only for rgb channels

   typedef typename channel_type< RGB_VIEW >::type channel_t;
   channel_t max = channel_traits<channel_t>::max_value();

   for( int y=0; y < src.height(); ++y )
   {
      typename RGB_VIEW::x_iterator src_it = src.row_begin( y );
      typename RGB_VIEW::x_iterator dst_it = dst.row_begin( y );

      for( int x = 0; x < src.width(); ++x )
      {
         static_for_each( dst_it[x], src_it[x], calc_negative() );
      }
   }
}

struct calc_brightness
{
   float _factor;

   template < typename DST_CHANNEL
            , typename SRC_CHANNEL
            > void operator()( DST_CHANNEL&       dst 
                             , const SRC_CHANNEL& src ) const
   {
      float d = static_cast<float>( src ) * _factor;

      if( d > channel_traits< DST_CHANNEL >::max_value() )
      {
         dst = channel_traits< DST_CHANNEL >::max_value();
      }
      else
      {
         dst = static_cast< DST_CHANNEL >( d );
      }
   }
};



template< typename RGB_VIEW >
void brightness( const RGB_VIEW& src
               , const RGB_VIEW& dst
               , float           factor )
{
   calc_brightness calc;
   calc._factor = factor;

   for( int y=0; y < src.height(); ++y )
   {
      typename RGB_VIEW::x_iterator src_it = src.row_begin( y );
      typename RGB_VIEW::x_iterator dst_it = dst.row_begin( y );

      for( int x = 0; x < src.width(); ++x )
      {
         static_for_each( dst_it[x], src_it[x], calc );
      }
   }
}

template< typename VALUE >
struct calc_brightness_
{
   VALUE _number;

   template < typename DST_CHANNEL
            , typename SRC_CHANNEL
            > void operator()( DST_CHANNEL&       dst 
                             , const SRC_CHANNEL& src ) const
   {
      VALUE d = static_cast< VALUE >( src ) + _number;

      if( d > channel_traits< DST_CHANNEL >::max_value() )
      {
         dst = channel_traits< DST_CHANNEL >::max_value();
      }
      else
      {
         dst = d;
      }
   }
};

template< typename RGB_VIEW
        , typename VALUE >
void brightness_( const RGB_VIEW& src
                , const RGB_VIEW& dst
                , VALUE           number )
{
   calc_brightness_<VALUE> calc;
   calc._number = number;

   for( int y=0; y < src.height(); ++y )
   {
      typename RGB_VIEW::x_iterator src_it = src.row_begin( y );
      typename RGB_VIEW::x_iterator dst_it = dst.row_begin( y );

      for( int x = 0; x < src.width(); ++x )
      {
         static_for_each( dst_it[x], src_it[x], calc );
      }
   }
}

template< typename RGB_VIEW >
void remove_channel( const RGB_VIEW& src
                   , unsigned int    c   )
{
   typedef typename channel_type< RGB_VIEW >::type channel_t;
   channel_t min_value = channel_traits<channel_t>::min_value();

   for( int y = 0; y < src.dimensions().y; ++y )
   {
      typename RGB_VIEW::x_iterator src_it = src.row_begin( y );

      for( int x = 0; x < src.dimensions().x; ++x )
      {
         dynamic_at_c( src_it[x], c ) = min_value;
      }
   }
}

template< typename CHANNEL
        , typename RGB_VIEW >
void remove_channel( const RGB_VIEW& src )
{
   typedef typename channel_type< RGB_VIEW >::type channel_t;
   channel_t min = channel_traits<channel_t>::min_value();

   for( int y = 0; y < src.dimensions().y; ++y )
   {
      typename RGB_VIEW::x_iterator src_it = src.row_begin( y );

      for( int x = 0; x < src.dimensions().x; ++x )
      {
         get_color( src_it[x], CHANNEL() ) = min;
      }
   }
}


template< typename RGB_VIEW >
void difference( const RGB_VIEW& src_1
               , const RGB_VIEW& src_2
               , const RGB_VIEW& dst    )
{
   if( src_1.dimensions() != src_2.dimensions() )
   {
      throw std::runtime_error( "Source image have different dimensions." );
   }

   if( src_1.dimensions() != dst.dimensions() )
   {
      throw std::runtime_error( "Destination image must have equal dimensions as the source images." );
   }

   for( int y = 0; y < src_1.dimensions().y; ++y )
   {
      typename RGB_VIEW::x_iterator src_1_it = src_1.row_begin( y );
      typename RGB_VIEW::x_iterator src_2_it = src_2.row_begin( y );

      typename RGB_VIEW::x_iterator dst_it = dst.row_begin( y );

      typedef typename channel_type<RGB_VIEW>::type channel_t;

      for( int x = 0; x < src_1.dimensions().x; ++x )
      {
			 assert(false);
		//TODO: what is calc_diff?
         //static_for_each( src_1_it[x], src_1_it[x], dst_it[x], calc_diff() );
      }
   }
}

} //namespace toolbox
} //namespace gil
} //namespace boost

#endif // RGB_ALGORITHMS_HPP
