// Copyright 2017 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include <memory>
#include <set>
#include <utility>

#include "base/logging.h"
#include "base/memory/ref_counted_memory.h"
#include "base/memory/scoped_refptr.h"
#include "ui/gfx/codec/png_codec.h"
#include "ui/gfx/image/image_platform.h"
#include "ui/gfx/image/image_skia_rep.h"
#include "ui/gfx/image/image_skia_source.h"

namespace gfx {
namespace internal {

namespace {

// Returns a 16x16 red image to visually show error in decoding PNG.
ImageSkia GetErrorImageSkia() {
  SkBitmap bitmap;
  bitmap.allocN32Pixels(16, 16);
  bitmap.eraseARGB(0xff, 0xff, 0, 0);
  return ImageSkia(ImageSkiaRep(bitmap, 1.0f));
}

class PNGImageSource : public ImageSkiaSource {
 public:
  PNGImageSource() {}

  PNGImageSource(const PNGImageSource&) = delete;
  PNGImageSource& operator=(const PNGImageSource&) = delete;

  ~PNGImageSource() override {}

  ImageSkiaRep GetImageForScale(float scale) override {
    if (image_skia_reps_.empty())
      return ImageSkiaRep();

    const ImageSkiaRep* rep = nullptr;
    // gfx::ImageSkia passes one of the resource scale factors. The source
    // should return:
    // 1) The ImageSkiaRep with the highest scale if all available
    //    scales are smaller than |scale|.
    // 2) The ImageSkiaRep with the smallest one that is larger than |scale|.
    // TODO(crbug.com/329953472): Use a predefined threshold.
    for (const auto& image_skia_rep : image_skia_reps_) {
      if (image_skia_rep.scale() == scale) {
        return image_skia_rep;
      }
      if (!rep || rep->scale() < image_skia_rep.scale()) {
        rep = &image_skia_rep;
      }
      if (rep->scale() >= scale)
        break;
    }
    return rep ? *rep : ImageSkiaRep();
  }

  const gfx::Size size() const { return size_; }

  bool AddPNGData(const ImagePNGRep& png_rep) {
    const gfx::ImageSkiaRep rep = ToImageSkiaRep(png_rep);
    if (rep.is_null())
      return false;
    if (size_.IsEmpty())
      size_ = gfx::Size(rep.GetWidth(), rep.GetHeight());
    image_skia_reps_.insert(rep);
    return true;
  }

  static ImageSkiaRep ToImageSkiaRep(const ImagePNGRep& png_rep) {
    scoped_refptr<base::RefCountedMemory> raw_data = png_rep.raw_data;
    CHECK(raw_data.get());
    SkBitmap bitmap = PNGCodec::Decode(*raw_data);
    if (bitmap.isNull()) {
      LOG(ERROR) << "Unable to decode PNG for " << png_rep.scale << ".";
      return ImageSkiaRep();
    }
    return ImageSkiaRep(bitmap, png_rep.scale);
  }

 private:
  struct Compare {
    bool operator()(const ImageSkiaRep& rep1, const ImageSkiaRep& rep2) const {
      return rep1.scale() < rep2.scale();
    }
  };

  typedef std::set<ImageSkiaRep, Compare> ImageSkiaRepSet;
  ImageSkiaRepSet image_skia_reps_;
  gfx::Size size_;
};

}  // namespace

ImageSkia ImageSkiaFromPNG(const std::vector<ImagePNGRep>& image_png_reps) {
  if (image_png_reps.empty())
    return GetErrorImageSkia();
  std::unique_ptr<PNGImageSource> image_source(new PNGImageSource);

  for (const auto& image_png_rep : image_png_reps) {
    if (!image_source->AddPNGData(image_png_rep)) {
      return GetErrorImageSkia();
    }
  }
  const gfx::Size& size = image_source->size();
  DCHECK(!size.IsEmpty());
  if (size.IsEmpty())
    return GetErrorImageSkia();
  return ImageSkia(std::move(image_source), size);
}

scoped_refptr<base::RefCountedMemory> Get1xPNGBytesFromImageSkia(
    const ImageSkia* image_skia) {
  ImageSkiaRep image_skia_rep = image_skia->GetRepresentation(1.0f);
  if (image_skia_rep.scale() != 1.0f) {
    return nullptr;
  }

  std::optional<std::vector<uint8_t>> result = PNGCodec::EncodeBGRASkBitmap(
      image_skia_rep.GetBitmap(), /*discard_transparency=*/false);
  if (!result) {
    return nullptr;
  }

  return base::MakeRefCounted<base::RefCountedBytes>(std::move(result.value()));
}

}  // namespace internal
}  // namespace gfx
