#pragma once

#include <memory>

#include "roo_display.h"
#include "roo_display/color/color.h"
#include "roo_display/color/color_mode_indexed.h"
#include "roo_display/color/color_modes.h"
#include "roo_display/image/png/lib/PNGdec.h"
#include "roo_io/core/multipass_input_stream.h"

#ifdef ARDUINO
#include "roo_io/fs/arduino/file_resource.h"
#else
#include "roo_io/fs/file_resource.h"
#endif

namespace roo_display {

/// PNG decoder (stateful, reusable).
class PngDecoder {
 public:
  /// Construct a PNG decoder instance.
  PngDecoder();

 private:
  friend class PngImage;
  friend int32_t png_read(PNGFILE* pFile, uint8_t* pBuf, int32_t iLen);
  friend int32_t png_seek(PNGFILE* pFile, int32_t iPosition);

  bool getDimensions(const roo_io::MultipassResource& resource, int16_t& width,
                     int16_t& height);

  bool open(const roo_io::MultipassResource& resource, int16_t& width,
            int16_t& height);

  void draw(const roo_io::MultipassResource& resource, const Surface& s,
            uint8_t scale, int16_t& width, int16_t& height);

  void close() { input_ = nullptr; }

  std::unique_ptr<PNGIMAGE> pngdec_;

  std::unique_ptr<roo_io::MultipassInputStream> input_;

  // Used for indexed color modes.
  Palette palette_;
};

/// Drawable PNG image backed by a multipass resource.
class PngImage : public Drawable {
 public:
  /// Create a PNG image using a decoder and resource.
  PngImage(PngDecoder& decoder, roo_io::MultipassResource& resource)
      : decoder_(decoder), resource_(resource) {
    decoder_.getDimensions(resource_, width_, height_);
  }

  Box extents() const override { return Box(0, 0, width_ - 1, height_ - 1); }

 private:
  void drawTo(const Surface& s) const override {
    // We update the width and height during drawing, so that the file does not
    // need to be re-read just to fetch the dimensions.
    decoder_.draw(resource_, s, 0, width_, height_);
  }

  PngDecoder& decoder_;

  roo_io::MultipassResource& resource_;
  mutable int16_t width_;
  mutable int16_t height_;
};

#ifdef ARDUINO
/// Drawable PNG image backed by a file resource (Arduino).
class PngFile : public Drawable {
 public:
  /// Create a PNG file drawable using an Arduino FS and path.
  PngFile(PngDecoder& decoder, ::fs::FS& fs, String path)
      : resource_(fs, path.c_str()), img_(decoder, resource_) {}

  /// Create a PNG file drawable using a roo_io filesystem and Arduino String.
  PngFile(PngDecoder& decoder, roo_io::Filesystem& fs, String path)
      : resource_(fs, path.c_str()), img_(decoder, resource_) {}

  /// Create a PNG file drawable using a roo_io filesystem and std::string.
  PngFile(PngDecoder& decoder, roo_io::Filesystem& fs, std::string path)
      : resource_(fs, path.c_str()), img_(decoder, resource_) {}

  Box extents() const override { return img_.extents(); }

 private:
  void drawTo(const Surface& s) const override { s.drawObject(img_); }

  roo_io::ExtendedArduinoFileResource resource_;
  PngImage img_;
};
#else
/// Drawable PNG image backed by a file resource (non-Arduino).
class PngFile : public Drawable {
 public:
  /// Create a PNG file drawable using a roo_io filesystem and path.
  PngFile(PngDecoder& decoder, roo_io::Filesystem& fs, std::string path)
      : resource_(fs, std::move(path)), img_(decoder, resource_) {}

  Box extents() const override { return img_.extents(); }

 private:
  void drawTo(const Surface& s) const override { s.drawObject(img_); }

  roo_io::FileResource resource_;
  PngImage img_;
};
#endif

}  // namespace roo_display
