// -*- mode: c++ -*-
//-----------------------------------------------------------------------------
//
// Copyright(C) 2016 Zohar Malamant
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
// 02111-1307, USA.
//
//-----------------------------------------------------------------------------

#ifndef __IMP_IMAGE__96112498
#define __IMP_IMAGE__96112498

#include <istream>
#include "Pixel"

namespace imp {
  namespace gfx {
    class Image;

    struct ImageError : std::runtime_error
    { using std::runtime_error::runtime_error; };

    struct ImageLoadError: ImageError
    { using ImageError::ImageError; };

    struct ImageSaveError: ImageError
    { using ImageError::ImageError; };

    struct ImageFormatIO {
        virtual bool is_format(std::istream&) const = 0;
        virtual Image load(std::istream&) const = 0;
        virtual void save(std::ostream&, Image const&) const = 0;
        virtual StringView mimetype() const = 0;
    };

    struct SpriteOffsets {
        int x = 0;
        int y = 0;
    };

    /**
     * \brief A container type for images
     */
    class Image {
        const PixelInfo* mTraits = nullptr;
        uint16 mWidth = 0;
        uint16 mHeight = 0;
        std::unique_ptr<byte[]> mData = nullptr;
        std::shared_ptr<const Palette> mPalette = nullptr;
        SpriteOffsets mOffsets;
        uint16 mTransparentIdx = static_cast<uint16>(-1);

        size_t _area() const
        { return mWidth * mHeight; }

    public:
        Image() = default;

        Image(const Image &);

        Image(Image&&) noexcept = default;

        Image(PixelFormat format, uint16 width, uint16 height, byte *data);

        explicit Image(PixelFormat format, uint16 width, uint16 height, std::unique_ptr<byte[]> data);

        explicit Image(PixelFormat format, uint16 width, uint16 height, noinit_tag);

        Image(std::istream&);

        Image(std::istream&, StringView mimetype);

        Image& operator=(const Image &);

        Image& operator=(Image&& other) noexcept = default;

        void load(std::istream&);

        void load(std::istream&, StringView mimetype);

        void save(std::ostream& s, StringView mimetype) const;

        Image clone() const
        {
            return Image(*this);
        }

        void reset()
        { *this = Image(); }

        byte* data_ptr()
        { return mData.get(); }

        const byte* data_ptr() const
        { return mData.get(); }

        byte* scanline_ptr(uint16 index);

        const byte* scanline_ptr(uint16 index) const;

        template <class SrcT, class DstT = SrcT>
        PixelMap<SrcT, DstT> scanline_map(uint16 idx)
        {
            test_pixel_format<SrcT>(mTraits);

            auto ptr = reinterpret_cast<SrcT*>(scanline_ptr(idx));
            return { ptr, ptr + mWidth };
        };

        template <class SrcT, class DstT = SrcT>
        PixelMap<const SrcT, const DstT> scanline_map(uint16 idx) const
        {
            test_pixel_format<SrcT>(mTraits);

            auto ptr = reinterpret_cast<const SrcT*>(scanline_ptr(idx));
            return { ptr, ptr + mWidth };
        };

        byte* pixel_ptr(uint16 x, uint16 y);

        const byte* pixel_ptr(uint16 x, uint16 y) const;

        template <class T = Rgb>
        T pixel(uint16 x, uint16 y) const
        {
            auto ptr = pixel_ptr(x, y);
            T px;
            copy_pixel(format(), mPalette.get(), ptr,
                       pixel_traits<T>::format, nullptr,
                       reinterpret_cast<byte *>(&px));
            return px;
        }

        template <class T = Rgb>
        void set_pixel(uint16 x, uint16 y, const T &px)
        {
            auto ptr = pixel_ptr(x, y);
            copy_pixel(pixel_traits<T>::format, nullptr, reinterpret_cast<const byte*>(&px),
                       format(), mPalette.get(), ptr);
        }

        Image& convert(PixelFormat);

        Image& resize(uint16 width, uint16 height);

        Image& scale(uint16 width, uint16 height);

        template<class SrcT, class DstT = SrcT>
        PixelMap<SrcT, DstT> map()
        {
            test_pixel_format<SrcT>(mTraits);

            auto ptr = reinterpret_cast<SrcT *>(data_ptr());
            return { ptr, ptr + _area() };
        }

        template<class SrcT, class DstT = SrcT>
        PixelMap<const SrcT, const DstT> map() const
        {
            test_pixel_format<SrcT>(mTraits);

            auto ptr = reinterpret_cast<const SrcT *>(data_ptr());
            return { ptr, ptr + _area() };
        }

        const PixelInfo &traits() const
        { return *mTraits; }

        PixelFormat format() const
        { return mTraits->format; }

        uint16 width() const
        { return mWidth; }

        uint16 height() const
        { return mHeight; }

        std::shared_ptr<const Palette> palette() const
        { return mPalette; }

        PixelFormat palette_format() const
        { return mPalette ? mPalette->format() : PixelFormat::none; }

        void set_palette(Palette palette)
        { mPalette = std::make_shared<Palette>(std::move(palette)); }

        void set_palette(const std::shared_ptr<const Palette> &palette)
        { mPalette = palette; }

        uint16 trans() const
        { return mTransparentIdx; }

        void set_trans(uint16 idx)
        { mTransparentIdx = idx; }

        const SpriteOffsets &offsets() const
        { return mOffsets; }

        void set_offsets(const SpriteOffsets &offsets)
        {
            mOffsets = offsets;
        }

        bool is_indexed() const
        { return !mTraits->color; }
    };

    /**
     * \brief Equality operator for Image
     *
     * \return true if images are isomorphic, false otherwise
     *
     * Compares two Image classes. Only returns true if `lhs` is isomorphic to `rhs`.
     * That is, if `lhs` can be losslessly converted to `rhs` and vice-versa.
     */
    bool operator==(const Image &lhs, const Image &rhs);

    /**
     * \brief Inequality operator for Image
     *
     * \return true if images are not isomorphic, false otherwise
     *
     * Compares two Image classes. Only returns false if `lhs` is isomorphic to `rhs`.
     * That is, if `lhs` can be losslessly converted to `rhs` and vice-versa.
     */
    bool operator!=(const Image &lhs, const Image &rhs);
  }
}

#endif //__IMP_IMAGE__96112498
