#include "png_decoder.h"
#ifdef USE_RUNTIME_IMAGE_PNG

#include "esphome/components/display/display_buffer.h"
#include "esphome/core/application.h"
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"

static const char *const TAG = "image_decoder.png";

namespace esphome::runtime_image {

/**
 * @brief Callback method that will be called by the PNGLE engine when the basic
 * data of the image is received (i.e. width and height);
 *
 * @param pngle The PNGLE object, including the context data.
 * @param w The width of the image.
 * @param h The height of the image.
 */
static void init_callback(pngle_t *pngle, uint32_t w, uint32_t h) {
  PngDecoder *decoder = (PngDecoder *) pngle_get_user_data(pngle);
  decoder->set_size(w, h);
}

/**
 * @brief Callback method that will be called by the PNGLE engine when a chunk
 * of the image is decoded.
 *
 * @param pngle The PNGLE object, including the context data.
 * @param x The X coordinate to draw the rectangle on.
 * @param y The Y coordinate to draw the rectangle on.
 * @param w The width of the rectangle to draw.
 * @param h The height of the rectangle to draw.
 * @param rgba The color to paint the rectangle in.
 */
static void draw_callback(pngle_t *pngle, uint32_t x, uint32_t y, uint32_t w, uint32_t h, const uint8_t rgba[4]) {
  PngDecoder *decoder = (PngDecoder *) pngle_get_user_data(pngle);
  Color color(rgba[0], rgba[1], rgba[2], rgba[3]);
  decoder->draw(x, y, w, h, color);

  // Feed watchdog periodically to avoid triggering during long decode operations.
  // Feed every 1024 pixels to balance efficiency and responsiveness.
  uint32_t pixels = w * h;
  decoder->increment_pixels_decoded(pixels);
  if ((decoder->get_pixels_decoded() % 1024) < pixels) {
    App.feed_wdt();
  }
}

PngDecoder::PngDecoder(RuntimeImage *image) : ImageDecoder(image) {
  {
    RAMAllocator<pngle_t> allocator;
    pngle_t *pngle = allocator.allocate(1, PNGLE_T_SIZE);
    if (!pngle) {
      ESP_LOGE(TAG, "Failed to allocate memory for PNGLE engine!");
      return;
    }
    memset(pngle, 0, PNGLE_T_SIZE);
    pngle_reset(pngle);
    this->pngle_ = pngle;
  }
}

PngDecoder::~PngDecoder() {
  if (this->pngle_) {
    pngle_reset(this->pngle_);
    RAMAllocator<pngle_t> allocator;
    allocator.deallocate(this->pngle_, PNGLE_T_SIZE);
  }
}

int PngDecoder::prepare(size_t expected_size) {
  ImageDecoder::prepare(expected_size);
  if (!this->pngle_) {
    ESP_LOGE(TAG, "PNG decoder engine not initialized!");
    return DECODE_ERROR_OUT_OF_MEMORY;
  }
  pngle_set_user_data(this->pngle_, this);
  pngle_set_init_callback(this->pngle_, init_callback);
  pngle_set_draw_callback(this->pngle_, draw_callback);
  return 0;
}

int HOT PngDecoder::decode(uint8_t *buffer, size_t size) {
  if (!this->pngle_) {
    ESP_LOGE(TAG, "PNG decoder engine not initialized!");
    return DECODE_ERROR_OUT_OF_MEMORY;
  }
  // PNG can be decoded progressively, but wait for a reasonable chunk
  if (size < 256 && this->expected_size_ > 0 && size < this->expected_size_ - this->decoded_bytes_) {
    ESP_LOGD(TAG, "Waiting for more data");
    return 0;
  }
  auto fed = pngle_feed(this->pngle_, buffer, size);
  if (fed < 0) {
    ESP_LOGE(TAG, "Error decoding image: %s", pngle_error(this->pngle_));
  } else {
    this->decoded_bytes_ += fed;
  }
  return fed;
}

}  // namespace esphome::runtime_image

#endif  // USE_RUNTIME_IMAGE_PNG
