// Copyright (C) 2008  Davis E. King (davis@dlib.net), Nils Labugt
// License: Boost Software License   See LICENSE.txt for the full license.
#ifndef DLIB_PNG_IMPORT
#define DLIB_PNG_IMPORT

#include <memory>
#include <cstring>
#include <functional>
#include <istream>
#include <fstream>

#include "png_loader_abstract.h"
#include "image_loader.h"
#include "../pixel.h"
#include "../type_traits.h"
#include "../test_for_odr_violations.h"
#include "../dir_nav.h"

namespace dlib
{

// ----------------------------------------------------------------------------------------

    class png_loader : noncopyable
    {
    public:

        png_loader( std::istream& in );
        png_loader( const char* filename );
        png_loader( const std::string& filename );
        png_loader( const dlib::file& f );
        png_loader( const unsigned char* image_buffer, std::size_t buffer_size );

        bool is_gray()              const;
        bool is_graya()             const;
        bool is_rgb()               const;
        bool is_rgba()              const;
        unsigned int bit_depth ()   const;
        long nr()                   const;
        long nc()                   const;

        template<class image_type>
        void get_image(image_type& img) const 
        {
#ifndef DLIB_PNG_SUPPORT
            static_assert(sizeof(image_type) == 0, "You are getting this error because you are trying to use the png_loader object but you haven't defined DLIB_PNG_SUPPORT.  You must do so to use this object.   You must also make sure you set your build environment to link against the libpng library.");
#else
            using pixel_type = pixel_type_t<image_type>;
            auto t = make_image_view(img);

            t.set_size( height, width );

            const auto assign_gray = [&](const auto** lines) 
            {
                for ( int n = 0; n < height; ++n )
                    for ( int m = 0; m < width; ++m )
                        assign_pixel( t[n][m], lines[n][m]);
            };

            const auto assign_gray_alpha = [&](const auto** lines) 
            {
                for ( int n = 0; n < height; ++n )
                {
                    for ( int m = 0; m < width; ++m )
                    {
                        if (!pixel_traits<pixel_type>::has_alpha)
                        {
                            assign_pixel(t[n][m], lines[n][m*2]);
                        }
                        else
                        {
                            rgb_alpha_pixel pix;
                            assign_pixel(pix,       lines[n][m*2]);
                            assign_pixel(pix.alpha, lines[n][m*2+1]);
                            assign_pixel(t[n][m], pix);
                        }
                    }
                }
            };

            const auto assign_rgb = [&](const auto** lines) 
            {
                for ( int n = 0; n < height;++n )
                {
                    for ( int m = 0; m < width;++m )
                    {
                        rgb_pixel p;
                        p.red   = static_cast<uint8>(lines[n][m*3]);
                        p.green = static_cast<uint8>(lines[n][m*3+1]);
                        p.blue  = static_cast<uint8>(lines[n][m*3+2]);
                        assign_pixel( t[n][m], p );
                    }
                }
            };

            const auto assign_rgba = [&](const auto** lines) 
            {
                if (!pixel_traits<pixel_type>::has_alpha)
                    assign_all_pixels(t,0);

                for ( int n = 0; n < height; ++n )
                {
                    for ( int m = 0; m < width; ++m )
                    {
                        rgb_alpha_pixel p;
                        p.red   = static_cast<uint8>(lines[n][m*4]);
                        p.green = static_cast<uint8>(lines[n][m*4+1]);
                        p.blue  = static_cast<uint8>(lines[n][m*4+2]);
                        p.alpha = static_cast<uint8>(lines[n][m*4+3]);
                        assign_pixel( t[n][m], p );
                    }
                }
            };

            const auto assign = [&](const auto** lines)
            {
                if (is_gray())
                    assign_gray(lines);

                else if (is_graya())
                    assign_gray_alpha(lines);
                
                else if (is_rgb())
                    assign_rgb(lines);

                else if (is_rgba())
                    assign_rgba(lines);
            };

            if (bit_depth_ == 8)
                assign((const uint8_t**)(rows));
            
            else if (bit_depth_ == 16)
                assign((const uint16_t**)(rows));
#endif
        }

    private:
        void load(std::function<std::size_t(char*,std::size_t)> clb);
        void load(std::istream& in);

        int             height{0};
        int             width{0};
        int             bit_depth_{0};
        int             color_type{0};
        unsigned char** rows{nullptr};
        std::shared_ptr<void> finalizer;
    };

// ----------------------------------------------------------------------------------------

    template <class image_type>
    void load_png (
        image_type& img,
        std::istream& in
    )
    {
        png_loader(in).get_image(img);
    }

    template <class image_type>
    void load_png (
        image_type& img,
        const std::string& file_name
    )
    {
        png_loader(file_name).get_image(img);
    }

    template <
      class image_type,
      class Byte,
      std::enable_if_t<is_byte<Byte>::value, bool> = true
    >
    void load_png (
        image_type& img,
        const Byte* image_buffer,
        std::size_t buffer_size
    )
    {
        png_loader((const unsigned char*)image_buffer, buffer_size).get_image(img);
    }

// ----------------------------------------------------------------------------------------

}

#ifdef NO_MAKEFILE
#include "png_loader.cpp"
#endif 

#endif // DLIB_PNG_IMPORT
