//
// Copyright 2007-2012 Christian Henning, 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_TIFF_DETAIL_WRITE_HPP
#define BOOST_GIL_EXTENSION_IO_TIFF_DETAIL_WRITE_HPP

#include <boost/gil/extension/io/tiff/tags.hpp>
#include <boost/gil/extension/io/tiff/detail/writer_backend.hpp>
#include <boost/gil/extension/io/tiff/detail/device.hpp>

#include <boost/gil/premultiply.hpp>
#include <boost/gil/io/base.hpp>
#include <boost/gil/io/device.hpp>
#include <boost/gil/io/detail/dynamic.hpp>

#include <algorithm>
#include <cstdint>
#include <string>
#include <type_traits>
#include <vector>

extern "C" {
#include "tiff.h"
#include "tiffio.h"
}

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 {

template <typename PixelReference>
struct my_interleaved_pixel_iterator_type_from_pixel_reference
{
private:
    using pixel_t = typename std::remove_reference<PixelReference>::type::value_type;

public:
    using type = typename iterator_type_from_pixel
        <
            pixel_t,
            false,
            false,
            true
        >::type;
};


template< typename Channel
        , typename Layout
        , bool Mutable
        >
struct my_interleaved_pixel_iterator_type_from_pixel_reference< const bit_aligned_pixel_reference< byte_t
                                                                                                 , Channel
                                                                                                 , Layout
                                                                                                 , Mutable
                                                                                                 >
                                                              >
    : public iterator_type_from_pixel< const bit_aligned_pixel_reference< uint8_t
                                                                        , Channel
                                                                        , Layout
                                                                        , Mutable
                                                                        >
                                     ,false
                                     ,false
                                     ,true
                                     > {};

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

} // namespace detail

///
/// TIFF Writer
///
template < typename Device, typename Log >
class writer< Device
            , tiff_tag
            , Log
            >
    : public writer_backend< Device
                           , tiff_tag
                           >
{
private:
    using backend_t = writer_backend<Device, tiff_tag>;

public:

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

    template<typename View>
    void apply( const View& view )
    {
        write_view( view );
    }

private:

    template< typename View >
    void write_view( const View& view )
    {
        using pixel_t = typename View::value_type;
        // get the type of the first channel (heterogeneous pixels might be broken for now!)
        using channel_t = typename channel_traits<typename element_type<pixel_t>::type>::value_type;
        tiff_bits_per_sample::type bits_per_sample = detail::unsigned_integral_num_bits< channel_t >::value;

        tiff_samples_per_pixel::type samples_per_pixel = num_channels< pixel_t >::value;

        this->write_header( view );

        if( this->_info._is_tiled == false )
        {
            write_data( view
                      , (view.width() * samples_per_pixel * bits_per_sample + 7) / 8
                      , typename is_bit_aligned< pixel_t >::type()
                      );
        }
        else
        {
            tiff_tile_width::type  tw = this->_info._tile_width;
            tiff_tile_length::type th = this->_info._tile_length;

            if(!this->_io_dev.check_tile_size( tw, th ))
            {
                io_error( "Tile sizes need to be multiples of 16." );
            }

            // tile related tags
            this->_io_dev.template set_property<tiff_tile_width> ( tw );
            this->_io_dev.template set_property<tiff_tile_length>( th );

            write_tiled_data( view
                            , tw
                            , th
                            , typename is_bit_aligned< pixel_t >::type()
                            );
        }
    }

	//////////////////////////////

	template<typename View>
	void write_bit_aligned_view_to_dev( const View&       view
                                      , const std::size_t row_size_in_bytes
                                      , const std::true_type&    // has_alpha
                                      )
    {
        byte_vector_t row( row_size_in_bytes );

        using x_it_t = typename View::x_iterator;
        x_it_t row_it = x_it_t( &(*row.begin()));

		auto pm_view = premultiply_view <typename View:: value_type> (view);

        for( typename View::y_coord_t y = 0; y < pm_view.height(); ++y )
        {
					std::copy( pm_view.row_begin( y )
										 , pm_view.row_end( y )
										 , row_it
						);


            this->_io_dev.write_scaline( row
                                       , static_cast<std::uint32_t>( y )
                                       , 0
                                       );

            // @todo: do optional bit swapping here if you need to...
        }
    }

	template<typename View>
	void write_bit_aligned_view_to_dev( const View&       view
                                      , const std::size_t row_size_in_bytes
                                      , const std::false_type&    // has_alpha
                                      )
    {
        byte_vector_t row( row_size_in_bytes );

        using x_it_t = typename View::x_iterator;
        x_it_t row_it = x_it_t( &(*row.begin()));

        for( typename View::y_coord_t y = 0; y < view.height(); ++y )
        {
			std::copy( view.row_begin( y )
                     , view.row_end( y )
                     , row_it
				     );


            this->_io_dev.write_scaline( row
                                       , static_cast<std::uint32_t>( y )
                                       , 0
                                       );

            // @todo: do optional bit swapping here if you need to...
        }
    }

    /////////////////////////////

    template< typename View >
    void write_data( const View&   view
                   , std::size_t   row_size_in_bytes
                   , const std::true_type&    // bit_aligned
                   )
    {
        using colour_space_t = typename color_space_type<typename View::value_type>::type;
        using has_alpha_t = mp11::mp_contains<colour_space_t, alpha_t>;

        write_bit_aligned_view_to_dev(view, row_size_in_bytes, has_alpha_t());
    }

    template< typename View>
    void write_tiled_data( const View&            view
                         , tiff_tile_width::type  tw
                         , tiff_tile_length::type th
                         , const std::true_type&    // bit_aligned
                         )
    {
        byte_vector_t row( this->_io_dev.get_tile_size() );

        using x_it_t = typename View::x_iterator;
        x_it_t row_it = x_it_t( &(*row.begin()));

        internal_write_tiled_data(view, tw, th, row, row_it);
    }

    template< typename View >
    void write_data( const View&   view
                   , std::size_t
                   , const std::false_type&    // bit_aligned
                   )
    {
        std::vector< pixel< typename channel_type< View >::type
                          , layout<typename color_space_type< View >::type >
                          >
                   > row( view.size() );

        byte_t* row_addr = reinterpret_cast< byte_t* >( &row.front() );

				// @todo: is there an overhead to doing this when there's no
				// alpha to premultiply by? I'd hope it's optimised out.
				auto pm_view = premultiply_view <typename View:: value_type> (view);

        for( typename View::y_coord_t y = 0; y < pm_view.height(); ++y )
        {
					std::copy( pm_view.row_begin( y )
										 , pm_view.row_end( y )
										 , row.begin()
						);

            this->_io_dev.write_scaline( row_addr
                                       , static_cast<std::uint32_t>( y )
                                       , 0
                                       );

            // @todo: do optional bit swapping here if you need to...
        }
    }

    template< typename View >
    void write_tiled_data( const View&            view
                         , tiff_tile_width::type  tw
                         , tiff_tile_length::type th
                         , const std::false_type&    // bit_aligned
                         )
    {
        byte_vector_t row( this->_io_dev.get_tile_size() );

        using x_iterator = typename detail::my_interleaved_pixel_iterator_type_from_pixel_reference<typename View::reference>::type;
        x_iterator row_it = x_iterator( &(*row.begin()));

        internal_write_tiled_data(view, tw, th, row, row_it);
    }


	//////////////////////////////

	template< typename View
            , typename IteratorType
            >
	void write_tiled_view_to_dev( const View&  view
                                , IteratorType it
                                , const std::true_type& // has_alpha
                                )
    {
        auto pm_view = premultiply_view <typename View:: value_type>( view );

        std::copy( pm_view.begin()
                 , pm_view.end()
                 , it
                 );
    }


	template< typename View
            , typename IteratorType
            >
	void write_tiled_view_to_dev( const View&  view
                                , IteratorType it
                                , const std::false_type& // has_alpha
                                )
    {
        std::copy( view.begin()
                 , view.end()
                 , it
                 );
    }

    /////////////////////////////



    template< typename View,
              typename IteratorType
            >
    void internal_write_tiled_data( const View&            view
                                  , tiff_tile_width::type  tw
                                  , tiff_tile_length::type th
                                  , byte_vector_t&         row
                                  , IteratorType           it
                                  )
    {
        std::ptrdiff_t i = 0, j = 0;
        View tile_subimage_view;
        while( i < view.height() )
        {
            while( j < view.width() )
            {
                if( j + tw < view.width() && i + th < view.height() )
                {
                    // a tile is fully included in the image: just copy values
                    tile_subimage_view = subimage_view( view
                                                      , static_cast< int >( j  )
                                                      , static_cast< int >( i  )
                                                      , static_cast< int >( tw )
                                                      , static_cast< int >( th )
                                                      );

                    using colour_space_t = typename color_space_type<typename View::value_type>::type;
                    using has_alpha_t = mp11::mp_contains<colour_space_t, alpha_t>;

                    write_tiled_view_to_dev(tile_subimage_view, it, has_alpha_t());
                }
                else
                {
                    std::ptrdiff_t width  = view.width();
                    std::ptrdiff_t height = view.height();

                    std::ptrdiff_t current_tile_width  = ( j + tw < width ) ? tw : width  - j;
                    std::ptrdiff_t current_tile_length = ( i + th < height) ? th : height - i;

                    tile_subimage_view = subimage_view( view
                                                      , static_cast< int >( j )
                                                      , static_cast< int >( i )
                                                      , static_cast< int >( current_tile_width )
                                                      , static_cast< int >( current_tile_length )
                                                      );

                    for( typename View::y_coord_t y = 0; y < tile_subimage_view.height(); ++y )
                    {
                        std::copy( tile_subimage_view.row_begin( y )
                                 , tile_subimage_view.row_end( y )
                                 , it
                                 );
                        std::advance(it, tw);
                    }

                    it = IteratorType( &(*row.begin()));
                }

                this->_io_dev.write_tile( row
                                        , static_cast< std::uint32_t >( j )
                                        , static_cast< std::uint32_t >( i )
                                        , 0
                                        , 0
                                        );
                j += tw;
            }
            j = 0;
            i += th;
        }
        // @todo: do optional bit swapping here if you need to...
    }
};

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

public:

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

    template< typename ...Views >
    void apply( const any_image_view< Views... >& views )
    {
        detail::dynamic_io_fnobj< detail::tiff_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
