/*
 * Copyright (C) 2007,2008   Alex Shulgin
 *
 * This file is part of png++ the C++ wrapper for libpng.  PNG++ is free
 * software; the exact copying conditions are as follows:
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice,
 * this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 * notice, this list of conditions and the following disclaimer in the
 * documentation and/or other materials provided with the distribution.
 *
 * 3. The name of the author may not be used to endorse or promote products
 * derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
 * NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
 * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
#ifndef PNGPP_PIXEL_BUFFER_HPP_INCLUDED
#define PNGPP_PIXEL_BUFFER_HPP_INCLUDED

#include <cassert>
#include <cstddef>
#include <stdexcept>
#include <vector>

#include "packed_pixel.hpp"
#include "gray_pixel.hpp"
#include "index_pixel.hpp"

namespace png
{

    /**
     * \brief The pixel row traits class template.  Provides a common
     * way to get starting address of the row for packed and unpacked
     * row types.
     *
     * Not implemented--see specializations.
     */
    template< typename row > class row_traits;

    /**
     * \brief The basic class template to represent image pixel data.
     */
    template< typename pixel,
              typename row,
              class traits = row_traits< row > >
    class basic_pixel_buffer
    {
    public:
        /**
         * \brief A row of pixel data.
         */
        typedef row row_type;
        typedef row_type& row_access;
        typedef row_type const& row_const_access;
        typedef traits row_traits;

        /**
         * \brief Constructs an empty 0x0 pixel buffer object.
         */
        basic_pixel_buffer()
            : m_width(0),
              m_height(0)
        {
        }

        /**
         * \brief Constructs an empty pixel buffer object.
         */
        basic_pixel_buffer(uint_32 width, uint_32 height)
            : m_width(0),
              m_height(0)
        {
            resize(width, height);
        }

        uint_32 get_width() const
        {
            return m_width;
        }

        uint_32 get_height() const
        {
            return m_height;
        }

        /**
         * \brief Resizes the pixel buffer.
         *
         * If new width or height is greater than the original,
         * expanded pixels are filled with value of \a pixel().
         */
        void resize(uint_32 width, uint_32 height)
        {
            m_width = width;
            m_height = height;
            m_rows.resize(height);
            for (typename row_vec::iterator r = m_rows.begin();
                 r != m_rows.end();
                 ++r)
            {
                r->resize(width);
            }
        }

        /**
         * \brief Returns a reference to the row of image data at
         * specified index.
         *
         * Checks the index before returning a row: an instance of
         * std::out_of_range is thrown if \c index is greater than \c
         * height.
         */
        row_access get_row(size_t index)
        {
            return m_rows.at(index);
        }

        /**
         * \brief Returns a const reference to the row of image data at
         * specified index.
         *
         * The checking version.
         */
        row_const_access get_row(size_t index) const
        {
            return m_rows.at(index);
        }

        /**
         * \brief The non-checking version of get_row() method.
         */
        row_access operator[](size_t index)
        {
            return m_rows[index];
        }

        /**
         * \brief The non-checking version of get_row() method.
         */
        row_const_access operator[](size_t index) const
        {
            return m_rows[index];
        }

        /**
         * \brief Replaces the row at specified index.
         */
        void put_row(size_t index, row_type const& r)
        {
            assert(r.size() == m_width);
            m_rows.at(index) = r;
        }

        /**
         * \brief Returns a pixel at (x,y) position.
         */
        pixel get_pixel(size_t x, size_t y) const
        {
            return get_row(y).at(x);
        }

        /**
         * \brief Replaces a pixel at (x,y) position.
         */
        void set_pixel(size_t x, size_t y, pixel p)
        {
            get_row(y).at(x) = p;
        }

    protected:
        uint_32 m_width;
        uint_32 m_height;
        typedef std::vector< row_type > row_vec;
        row_vec m_rows;
    };

    /**
     * \brief The row_traits specialization for unpacked pixel rows.
     */
    template< typename pixel >
    class row_traits< std::vector< pixel > >
    {
    public:
        /**
         * \brief Returns the starting address of the row.
         */
        static pixel* get_data(std::vector< pixel >& vec)
        {
            assert(vec.size());
            return & vec[0];
        }
    };

    /**
     * The pixel_buffer specialization for unpacked pixels.
     */
    template< typename pixel >
    class pixel_buffer
        : public basic_pixel_buffer< pixel, std::vector< pixel > >
    {
    public:
        pixel_buffer()
        {
        }

        pixel_buffer(uint_32 width, uint_32 height)
            : basic_pixel_buffer< pixel, std::vector< pixel > >(width, height)
        {
        }
    };

    namespace detail
    {

        template< class pixel, typename reference >
        class basic_packed_pixel_proxy
        {
        public:
            explicit basic_packed_pixel_proxy(reference ref)
                : m_ref(ref),
                  m_shift(0)
            {
            }

            basic_packed_pixel_proxy(reference ref, size_t index)
                : m_ref(ref),
                  m_shift(get_shift(index))
            {
            }

            operator pixel() const
            {
                return pixel((m_ref >> m_shift) & pixel::get_bit_mask());
            }

        protected:
            /*
             * bits: . .   .
             *    1: 7 6 5 4 3 2 1 0
             *    2:   6   4   2   0
             *    4:       4       0
             */
            static size_t get_shift(size_t index)
            {
                int const bits = pixel::get_bit_depth();
                return (8 - bits) - (index % get_pixels_per_byte()) * bits;
            }

            static size_t get_pixels_per_byte()
            {
                return 8 / pixel::get_bit_depth();
            }

            reference m_ref;
            size_t m_shift;
        };

        template< class pixel >
        class const_packed_pixel_proxy
            : public basic_packed_pixel_proxy< pixel, byte const& >
        {
        public:
            const_packed_pixel_proxy(byte const& ref, size_t index)
                : basic_packed_pixel_proxy< pixel, byte const& >(ref, index)
            {
            }
        };

        template< class pixel >
        class packed_pixel_proxy
            : public basic_packed_pixel_proxy< pixel, byte& >
        {
        public:
            typedef basic_packed_pixel_proxy< pixel, byte& > basic_proxy;

            packed_pixel_proxy(byte& ref, size_t index)
                : basic_proxy(ref, index)
            {
            }

            packed_pixel_proxy(packed_pixel_proxy const& other)
                : basic_proxy(other.m_ref)
            {
                this->m_shift = other.m_shift;
            }

            packed_pixel_proxy& operator=(packed_pixel_proxy const& other)
            {
                return *this = static_cast< pixel >(other);
            }

            template< typename reference >
            packed_pixel_proxy&
            operator=(basic_packed_pixel_proxy< pixel, reference > const& other)
            {
                return *this = static_cast< pixel >(other);
            }

            packed_pixel_proxy& operator=(pixel p)
            {
                this->m_ref = (this->m_ref
                    & ~(pixel::get_bit_mask() << this->m_shift))
                    | (p << this->m_shift);

                return *this;
            }
        };

    } // namespace detail

    /**
     * \brief The packed pixel row class template.
     *
     * Stores the pixel row as a std::vector of byte-s, providing
     * access to individual packed pixels via proxy objects.
     */
    template< class pixel >
    class packed_pixel_row
    {
    public:
        /**
         * \brief Constructs a pixel row object for \c size packed pixels.
         */
        explicit packed_pixel_row(size_t size = 0)
        {
            resize(size);
        }

        size_t size() const
        {
            return m_size;
        }

        /**
         * \brief Resizes the pixel row to hold up to \c size packed pixels.
         */
        void resize(size_t size)
        {
            m_vec.resize(size / get_pixels_per_byte()
                         + (size % get_pixels_per_byte() ? 1 : 0));
            m_size = size;
        }

        /**
         * \brief The immutable packed pixel proxy type.
         */
        typedef detail::const_packed_pixel_proxy< pixel > const_pixel_proxy;

        /**
         * \brief The mutable packed pixel proxy type.
         */
        typedef detail::packed_pixel_proxy< pixel > pixel_proxy;

        /**
         * \brief Returns an immutable proxy the to the pixel at \c
         * index.
         */
        const_pixel_proxy at(size_t index) const
        {
            return const_pixel_proxy(m_vec.at(index / get_pixels_per_byte()),
                                     index);
        }

        /**
         * \brief Returns a mutable proxy the to the pixel at \c
         * index.
         */
        pixel_proxy at(size_t index)
        {
            return pixel_proxy(m_vec.at(index / get_pixels_per_byte()),
                               index);
        }

        /**
         * \brief Returns an immutable proxy the to the pixel at \c
         * index.  The non-checking version.
         */
        const_pixel_proxy operator[](size_t index) const
        {
            return const_pixel_proxy(m_vec[index / get_pixels_per_byte()],
                                     index);
        }

        /**
         * \brief Returns n mutable proxy the to the pixel at \c
         * index.  The non-checking version.
         */
        pixel_proxy operator[](size_t index)
        {
            return pixel_proxy(m_vec[index / get_pixels_per_byte()],
                               index);
        }

        /**
         * \brief Returns the starting address of the row.
         */
        byte* get_data()
        {
            assert(m_vec.size());
            return & m_vec[0];
        }

    private:
        static size_t get_pixels_per_byte()
        {
            return 8 / pixel::get_bit_depth();
        }

        std::vector< byte > m_vec;
        size_t m_size;
    };

    /**
     * \brief The row_traits class template specialization for packed
     * pixel row type.
     */
    template< typename pixel >
    class row_traits< packed_pixel_row< pixel > >
    {
    public:
        /**
         * \brief Returns the starting address of the row.
         */
        static byte* get_data(packed_pixel_row< pixel >& row)
        {
            return row.get_data();
        }
    };

    /**
     * \brief The pixel buffer class template specialization for the
     * packed_gray_pixel type.
     */
    template< int bits >
    class pixel_buffer< packed_gray_pixel< bits > >
        : public basic_pixel_buffer< packed_gray_pixel< bits >,
                                     packed_pixel_row< packed_gray_pixel
                                                       < bits > > >
    {
    public:
        typedef packed_gray_pixel< bits > pixel_type;
        typedef packed_pixel_row< pixel_type > pixel_row_type;

        pixel_buffer()
        {
        }

        pixel_buffer(uint_32 width, uint_32 height)
            : basic_pixel_buffer< pixel_type,
                                  pixel_row_type >(width, height)
        {
        }
    };

    /**
     * \brief The pixel buffer class template specialization for the
     * packed_index_pixel type.
     */
    template< int bits >
    class pixel_buffer< packed_index_pixel< bits > >
        : public basic_pixel_buffer< packed_index_pixel< bits >,
                                     packed_pixel_row< packed_index_pixel
                                                       < bits > > >
    {
    public:
        typedef packed_index_pixel< bits > pixel_type;
        typedef packed_pixel_row< pixel_type > pixel_row_type;

        pixel_buffer()
        {
        }

        pixel_buffer(uint_32 width, uint_32 height)
            : basic_pixel_buffer< pixel_type,
                                  pixel_row_type >(width, height)
        {
        }
    };

} // namespace png

#endif // PNGPP_PIXEL_BUFFER_HPP_INCLUDED
