//
// Copyright 2012 Christian Henning
//
// Distributed under the Boost Software License, Version 1.0
// See accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt
//
#ifndef BOOST_GIL_EXTENSION_TOOLBOX_IMAGE_TYPES_INDEXED_IMAGE_HPP
#define BOOST_GIL_EXTENSION_TOOLBOX_IMAGE_TYPES_INDEXED_IMAGE_HPP

#include <boost/gil/extension/toolbox/metafunctions/is_bit_aligned.hpp>

#include <boost/gil/image.hpp>
#include <boost/gil/point.hpp>
#include <boost/gil/virtual_locator.hpp>
#include <boost/gil/detail/is_channel_integral.hpp>
#include <boost/gil/detail/mp11.hpp>

#include <cstddef>
#include <memory>

namespace boost { namespace gil {

template< typename Locator >
struct get_pixel_type_locator
{
    using type = mp11::mp_if
        <
            typename is_bit_aligned<typename Locator::value_type>::type,
            typename Locator::reference,
            typename Locator::value_type
        >;
};

// used for virtual locator
template< typename IndicesLoc
        , typename PaletteLoc
        >
struct indexed_image_deref_fn_base
{
    using indices_locator_t = IndicesLoc;
    using palette_locator_t = PaletteLoc;
    //using index_t = typename get_pixel_type_locator<indices_locator_t>::type;

    using const_t = indexed_image_deref_fn_base<IndicesLoc, PaletteLoc>;
    using value_type = typename PaletteLoc::value_type;
    using reference = value_type;
    using const_reference = value_type;
    using argument_type = point_t;
    using result_type = reference;

    static const bool is_mutable = false;

    indexed_image_deref_fn_base() {}

    indexed_image_deref_fn_base( const indices_locator_t& indices_loc
                               , const palette_locator_t& palette_loc
                               )
    : _indices_loc( indices_loc )
    , _palette_loc( palette_loc )
    {}

    void set_indices( const indices_locator_t& indices_loc ) { _indices_loc = indices_loc; }
    void set_palette( const palette_locator_t& palette_loc ) { _palette_loc = palette_loc; }

    const indices_locator_t& indices() const { return _indices_loc; }
    const palette_locator_t& palette() const { return _palette_loc; }

protected:

    indices_locator_t _indices_loc;
    palette_locator_t _palette_loc;
};


// used for virtual locator
template< typename IndicesLoc
        , typename PaletteLoc
        , typename Enable = void // there is specialization for integral indices
        >
struct indexed_image_deref_fn : indexed_image_deref_fn_base< IndicesLoc
                                                           , PaletteLoc
                                                           >
{
    using base_t = indexed_image_deref_fn_base
        <
            IndicesLoc,
            PaletteLoc
        >;

    indexed_image_deref_fn()
    : base_t()
    {}

    indexed_image_deref_fn( const typename base_t::indices_locator_t& indices_loc
                          , const typename base_t::palette_locator_t& palette_loc
                          )
    : base_t( indices_loc
            , palette_loc
            )
    {}

    typename base_t::result_type operator()( point_t const& p ) const
    {
        return * this->_palette_loc.xy_at( at_c<0>( *this->_indices_loc.xy_at( p )), 0 );
    }
};


template <typename IndicesLoc, typename PaletteLoc>
struct indexed_image_deref_fn
<
    IndicesLoc,
    PaletteLoc,
    typename std::enable_if
    <
        detail::is_channel_integral<typename IndicesLoc::value_type>::value
    >::type
> : indexed_image_deref_fn_base<IndicesLoc, PaletteLoc>
{
    using base_t = indexed_image_deref_fn_base<IndicesLoc, PaletteLoc>;

    indexed_image_deref_fn() : base_t() {}

    indexed_image_deref_fn(
        typename base_t::indices_locator_t const& indices_loc,
        typename base_t::palette_locator_t const& palette_loc)
        : base_t(indices_loc, palette_loc)
    {
    }

    typename base_t::result_type operator()(point_t const& p) const
    {
        return *this->_palette_loc.xy_at(*this->_indices_loc.xy_at(p), 0);
    }
};

template< typename IndicesLoc
        , typename PaletteLoc
        >
struct indexed_image_locator_type
{
    using type = virtual_2d_locator
        <
            indexed_image_deref_fn<IndicesLoc, PaletteLoc>,
            false
        >;
};

template< typename Locator > // indexed_image_locator_type< ... >::type
class indexed_image_view : public image_view< Locator >
{
public:

    using deref_fn_t = typename Locator::deref_fn_t;
    using indices_locator_t = typename deref_fn_t::indices_locator_t;
    using palette_locator_t = typename deref_fn_t::palette_locator_t;

    using const_t = indexed_image_view<Locator>;

    using indices_view_t = image_view<indices_locator_t>;
    using palette_view_t = image_view<palette_locator_t>;

    indexed_image_view()
    : image_view< Locator >()
    , _num_colors( 0 )
    {}

    indexed_image_view( point_t const& dimensions
                      , std::size_t    num_colors
                      , const Locator& locator
                      )
    : image_view< Locator >( dimensions, locator )
    , _num_colors( num_colors )
    {}

    template< typename IndexedView >
    indexed_image_view( const IndexedView& iv )
    : image_view< Locator >( iv )
    , _num_colors( iv._num_colors )
    {}

    std::size_t num_colors() const { return _num_colors; }


    const indices_locator_t& indices() const { return get_deref_fn().indices(); }
    const palette_locator_t& palette() const { return get_deref_fn().palette(); }

    indices_view_t get_indices_view() const { return indices_view_t(this->dimensions(), indices() );}
    palette_view_t get_palette_view() const { return palette_view_t(point_t(num_colors(), 1), palette());}

private:

    const deref_fn_t& get_deref_fn() const { return this->pixels().deref_fn(); }

private:

    template< typename Locator2 > friend class indexed_image_view;

    std::size_t _num_colors;
};

// build an indexed_image_view from two views
template<typename Index_View, typename Palette_View>
indexed_image_view
<
    typename indexed_image_locator_type
    <
        typename Index_View::locator
        , typename Palette_View::locator
    >::type
>
    view(Index_View iv, Palette_View pv)
{
    using view_t = indexed_image_view
        <
            typename indexed_image_locator_type
                <
                    typename Index_View::locator,
                    typename Palette_View::locator
                >::type
        >;

    using defer_fn_t = indexed_image_deref_fn
        <
            typename Index_View::locator,
            typename Palette_View::locator
        >;

    return view_t(
        iv.dimensions()
        , pv.dimensions().x
        , typename view_t::locator(point_t(0, 0), point_t(1, 1), defer_fn_t(iv.xy_at(0, 0), pv.xy_at(0, 0)))
    );
}

template< typename Index
        , typename Pixel
        , typename IndicesAllocator = std::allocator< unsigned char >
        , typename PalleteAllocator = std::allocator< unsigned char >
        >
class indexed_image
{
public:

    using indices_t = image<Index, false, IndicesAllocator>;
    using palette_t = image<Pixel, false, PalleteAllocator>;

    using indices_view_t = typename indices_t::view_t;
    using palette_view_t = typename palette_t::view_t;

    using indices_const_view_t = typename indices_t::const_view_t;
    using palette_const_view_t = typename palette_t::const_view_t;

    using indices_locator_t = typename indices_view_t::locator;
    using palette_locator_t = typename palette_view_t::locator;

    using locator_t = typename indexed_image_locator_type
        <
            indices_locator_t,
            palette_locator_t
        >::type;

    using x_coord_t = typename indices_t::coord_t;
    using y_coord_t = typename indices_t::coord_t;


    using view_t = indexed_image_view<locator_t>;
    using const_view_t = typename view_t::const_t;

    indexed_image( const x_coord_t   width = 0
                 , const y_coord_t   height = 0
                 , const std::size_t num_colors = 1
                 , const std::size_t indices_alignment = 0
                 , const std::size_t palette_alignment = 0
                 )
    : _indices( width     , height, indices_alignment, IndicesAllocator() )
    , _palette( num_colors,      1, palette_alignment, PalleteAllocator() )
    {
        init( point_t( width, height ), num_colors );
    }

    indexed_image( point_t const&    dimensions
                 , const std::size_t num_colors = 1
                 , const std::size_t indices_alignment = 0
                 , const std::size_t palette_alignment = 0
                 )
    : _indices( dimensions,    indices_alignment, IndicesAllocator() )
    , _palette( num_colors, 1, palette_alignment, PalleteAllocator() )
    {
        init( dimensions, num_colors );
    }

    indexed_image( const indexed_image& img )
    : _indices( img._indices )
    , _palette( img._palette )
    {}

    template <typename Pixel2, typename Index2>
    indexed_image( const indexed_image< Pixel2, Index2 >& img )
    {
        _indices = img._indices;
        _palette = img._palette;
    }

    indexed_image& operator= ( const indexed_image& img )
    {
        _indices = img._indices;
        _palette = img._palette;

        return *this;
    }

    indices_const_view_t get_indices_const_view() const { return static_cast< indices_const_view_t >( _view.get_indices_view()); }
    palette_const_view_t get_palette_const_view() const { return static_cast< palette_const_view_t >( _view.get_palette_view()); }

    indices_view_t get_indices_view() { return _view.get_indices_view(); }
    palette_view_t get_palette_view() { return _view.get_palette_view(); }

public:

    view_t _view;

private:

    void init( point_t const&    dimensions
             , const std::size_t num_colors
             )
    {
        using defer_fn_t = indexed_image_deref_fn
            <
                indices_locator_t,
                palette_locator_t
            >;

        defer_fn_t deref_fn( view( _indices ).xy_at( 0, 0 )
                           , view( _palette ).xy_at( 0, 0 )
                           );

        locator_t locator( point_t( 0, 0 ) // p
                         , point_t( 1, 1 ) // step
                         , deref_fn
                         );

        _view = view_t( dimensions
                      , num_colors
                      , locator
                      );
    }

private:

    indices_t _indices;
    palette_t _palette;
};

template< typename Index
        , typename Pixel
        >
inline
const typename indexed_image< Index, Pixel >::view_t& view( indexed_image< Index, Pixel >& img )
{
    return img._view;
}

template< typename Index
        , typename Pixel
        >
inline
const typename indexed_image< Index, Pixel >::const_view_t const_view( indexed_image< Index, Pixel >& img )
{
    return static_cast< const typename indexed_image< Index, Pixel >::const_view_t>( img._view );
}

// Whole image has one color and all indices are set to 0.
template< typename Locator
        , typename Value
        >
void fill_pixels( const indexed_image_view< Locator >& view
                , const Value&                         value
                )
{
    using view_t = indexed_image_view<Locator>;

    fill_pixels( view.get_indices_view(), typename view_t::indices_view_t::value_type( 0 ));
    *view.get_palette_view().begin() = value;
}

} // namespace gil
} // namespace boost

#endif
