//
// Copyright 2007-2012 Christian Henning, Andreas Pokorny, Lubomir Bourdev
//
// 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_IO_PNG_DETAIL_WRITE_HPP
#define BOOST_GIL_EXTENSION_IO_PNG_DETAIL_WRITE_HPP

#include <boost/gil/extension/io/png/detail/writer_backend.hpp>

#include <boost/gil/io/device.hpp>
#include <boost/gil/io/detail/dynamic.hpp>
#include <boost/gil/io/row_buffer_helper.hpp>
#include <boost/gil/detail/mp11.hpp>

#include <type_traits>

namespace boost { namespace gil {

#if BOOST_WORKAROUND(BOOST_MSVC, >= 1400)
#pragma warning(push)
#pragma warning(disable:4512) //assignment operator could not be generated
#endif

namespace detail {

struct png_write_is_supported
{
    template< typename View >
    struct apply
        : public is_write_supported< typename get_pixel_type< View >::type
                                   , png_tag
                                   >
    {};
};

} // namespace detail

///
/// PNG Writer
///
template< typename Device >
class writer< Device
            , png_tag
            >
    : public writer_backend< Device
                           , png_tag
                           >
{

public:

    using backend_t = writer_backend<Device, png_tag>;

    writer( const Device&                      io_dev
          , const image_write_info< png_tag >& info
          )
    : backend_t( io_dev
               , info
               )
    {}


    template< typename View >
    void apply( const View& view )
    {
        io_error_if( view.width() == 0 && view.height() == 0
                   , "png format cannot handle empty views."
                   );

        this->write_header( view );

        write_view( view
                  , typename is_bit_aligned< typename View::value_type >::type()
                  );
    }

private:

    template<typename View>
    void write_view( const View& view
                   ,  std::false_type       // is bit aligned
                   )
    {
        using pixel_t = typename get_pixel_type<View>::type;

        using png_rw_info = detail::png_write_support
            <
                typename channel_type<pixel_t>::type,
                typename color_space_type<pixel_t>::type
            >;

        if( little_endian() )
        {
            set_swap< png_rw_info >();
        }

        std::vector< pixel< typename channel_type< View >::type
                          , layout<typename color_space_type< View >::type >
                          >
                   > row_buffer( view.width() );

        for( int y = 0; y != view.height(); ++ y)
        {
            std::copy( view.row_begin( y )
                     , view.row_end  ( y )
                     , row_buffer.begin()
                     );

            png_write_row( this->get_struct()
                         , reinterpret_cast< png_bytep >( row_buffer.data() )
                         );
        }

        png_write_end( this->get_struct()
                     , this->get_info()
                     );
    }

    template<typename View>
    void write_view( const View& view
                   , std::true_type         // is bit aligned
                   )
    {
        using png_rw_info = detail::png_write_support
            <
                typename kth_semantic_element_type<typename View::value_type, 0>::type,
                typename color_space_type<View>::type
            >;

        if (little_endian() )
        {
            set_swap< png_rw_info >();
        }

        detail::row_buffer_helper_view< View > row_buffer( view.width()
                                                         , false
                                                         );

        for( int y = 0; y != view.height(); ++y )
        {
            std::copy( view.row_begin( y )
                     , view.row_end  ( y )
                     , row_buffer.begin()
                     );

            png_write_row( this->get_struct()
                         , reinterpret_cast< png_bytep >( row_buffer.data() )
                         );
        }

        png_free_data( this->get_struct()
                     , this->get_info()
                     , PNG_FREE_UNKN
                     , -1
                     );

        png_write_end( this->get_struct()
                     , this->get_info()
                     );
    }

    template<typename Info>
    struct is_less_than_eight : mp11::mp_less
        <
            std::integral_constant<int, Info::_bit_depth>,
            std::integral_constant<int, 8>
        >
    {};

    template<typename Info>
    struct is_equal_to_sixteen : mp11::mp_and
        <
            mp11::mp_not
            <
                mp11::mp_less
                <
                    std::integral_constant<int, Info::_bit_depth>,
                    std::integral_constant<int, 16>
                >
            >,
            mp11::mp_not
            <
                mp11::mp_less
                <
                    std::integral_constant<int, 16>,
                    std::integral_constant<int, Info::_bit_depth>
                >
            >
        >
    {};

    template <typename Info>
    void set_swap(typename std::enable_if<is_less_than_eight<Info>::value>::type* /*ptr*/ = 0)
    {
        png_set_packswap(this->get_struct());
    }

    template <typename Info>
    void set_swap(typename std::enable_if<is_equal_to_sixteen<Info>::value>::type* /*ptr*/ = 0)
    {
        png_set_swap(this->get_struct());
    }

    template <typename Info>
    void set_swap(
        typename std::enable_if
        <
            mp11::mp_and
            <
                mp11::mp_not<is_less_than_eight<Info>>,
                mp11::mp_not<is_equal_to_sixteen<Info>>
            >::value
        >::type* /*ptr*/ = nullptr)
    {
    }
};

///
/// PNG Dynamic Image Writer
///
template< typename Device >
class dynamic_image_writer< Device
                          , png_tag
                          >
    : public writer< Device
                   , png_tag
                   >
{
    using parent_t = writer<Device, png_tag>;

public:

    dynamic_image_writer( const Device&                      io_dev
                        , const image_write_info< png_tag >& info
)
    : parent_t( io_dev
              , info
              )
    {}

    template< typename ...Views >
    void apply( const any_image_view< Views... >& views )
    {
        detail::dynamic_io_fnobj< detail::png_write_is_supported
                                , parent_t
                                > op( this );

        variant2::visit( op, views );
    }
};

#if BOOST_WORKAROUND(BOOST_MSVC, >= 1400)
#pragma warning(pop)
#endif

} // namespace gil
} // namespace boost

#endif
