// Aseprite
// Copyright (C) 2019-2020  Igara Studio S.A.
// Copyright (C) 2001-2018  David Capello
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include "app/color_picker.h"

#include "app/doc.h"
#include "app/pref/preferences.h"
#include "app/site.h"
#include "app/util/wrap_point.h"
#include "doc/cel.h"
#include "doc/image.h"
#include "doc/layer_tilemap.h"
#include "doc/primitives.h"
#include "doc/sprite.h"
#include "doc/tileset.h"
#include "gfx/point.h"
#include "render/get_sprite_pixel.h"

#define PICKER_TRACE(...) // TRACE

namespace app {

namespace {

const int kOpacityThreshold = 1;

bool get_cel_pixel(const Cel* cel,
                   const double x,
                   const double y,
                   const frame_t frame,
                   color_t& output)
{
  gfx::RectF celBounds;
  if (cel->layer()->isReference())
    celBounds = cel->boundsF();
  else
    celBounds = cel->bounds();

  const doc::Image* image = cel->image();
  gfx::PointF pos(x, y);
  if (!celBounds.contains(pos))
    return false;

  // For tilemaps
  if (image->pixelFormat() == IMAGE_TILEMAP) {
    ASSERT(cel->layer()->isTilemap());

    auto layerTilemap = static_cast<doc::LayerTilemap*>(cel->layer());
    doc::Grid grid = layerTilemap->tileset()->grid();
    grid.origin(grid.origin() + cel->position());

    gfx::Point tilePos = grid.canvasToTile(gfx::Point(pos));
    PICKER_TRACE("PICKER: tilePos=(%d %d)\n", tilePos.x,tilePos.y);
    if (!image->bounds().contains(tilePos))
      return false;

    const doc::tile_index ti =
      get_pixel(image, tilePos.x, tilePos.y);

    PICKER_TRACE("PICKER: tile index=%d\n", ti);

    doc::ImageRef tile = layerTilemap->tileset()->get(ti);
    if (!tile)
      return false;

    const gfx::Point ipos =
      gfx::Point(pos) - grid.tileToCanvas(tilePos);

    PICKER_TRACE("PICKER: ipos=%d %d\n", ipos.x, ipos.y);

    output = get_pixel(tile.get(), ipos.x, ipos.y);
    PICKER_TRACE("PICKER: output=%d\n", output);
    return true;
  }
  // Regular images
  else {
    pos.x = (pos.x-celBounds.x)*image->width()/celBounds.w;
    pos.y = (pos.y-celBounds.y)*image->height()/celBounds.h;
    const gfx::Point ipos(pos);
    if (!image->bounds().contains(ipos))
      return false;

    output = get_pixel(image, ipos.x, ipos.y);
    return true;
  }
}

}

ColorPicker::ColorPicker()
  : m_tile(doc::tile_i_notile)
  , m_alpha(0)
  , m_layer(nullptr)
{
}

void ColorPicker::pickColor(const Site& site,
                            const gfx::PointF& _pos,
                            const render::Projection& proj,
                            const Mode mode)
{
  const doc::Sprite* sprite = site.sprite();
  gfx::PointF pos = _pos;

  m_alpha = 255;
  m_color = app::Color::fromMask();

  // Check tiled mode
  if (sprite && site.document()) {
    auto doc = static_cast<const Doc*>(site.document());
    DocumentPreferences& docPref = Preferences::instance().document(doc);

    pos = wrap_pointF(docPref.tiled.mode(),
                      site.sprite()->size(), pos);
  }

  // Get the color from the image
  switch (mode) {

    // Pick from the composed image
    case FromComposition: {
      doc::CelList cels;
      sprite->pickCels(pos.x, pos.y, site.frame(), kOpacityThreshold,
                       sprite->allVisibleLayers(), cels);
      if (!cels.empty())
        m_layer = cels.front()->layer();

      if (site.tilemapMode() == TilemapMode::Tiles) {
        if (!cels.empty()) {
          const gfx::Point tilePos = site.grid().canvasToTile(gfx::Point(pos));
          if (cels.front()->image()->bounds().contains(tilePos)) {
            m_tile = doc::get_pixel(cels.front()->image(), tilePos.x, tilePos.y);
            m_color = app::Color::fromIndex(m_tile);
          }
        }
      }
      else if (site.tilemapMode() == TilemapMode::Pixels) {
        m_color = app::Color::fromImage(
          sprite->pixelFormat(),
          render::get_sprite_pixel(sprite, pos.x, pos.y,
                                   site.frame(), proj,
                                   Preferences::instance().experimental.newBlend()));
      }
      break;
    }

    // Pick from the current layer
    case FromActiveLayer: {
      const Cel* cel = site.cel();
      if (!cel)
        return;

      if (site.tilemapMode() == TilemapMode::Tiles) {
        const gfx::Point tilePos = site.grid().canvasToTile(gfx::Point(pos));
        if (cel->image()->bounds().contains(tilePos)) {
          m_tile = doc::get_pixel(cel->image(), tilePos.x, tilePos.y);
          m_color = app::Color::fromIndex(m_tile);
        }
      }
      else if (site.tilemapMode() == TilemapMode::Pixels) {
        doc::color_t imageColor;
        if (!get_cel_pixel(cel, pos.x, pos.y,
                           site.frame(), imageColor))
          return;

        doc::PixelFormat pixelFormat =
          (cel->layer()->isTilemap() ? sprite->pixelFormat():
                                       cel->image()->pixelFormat());
        switch (pixelFormat) {
          case IMAGE_RGB:
            m_alpha = doc::rgba_geta(imageColor);
            break;
          case IMAGE_GRAYSCALE:
            m_alpha = doc::graya_geta(imageColor);
            break;
        }

        m_color = app::Color::fromImage(pixelFormat, imageColor);
        m_layer = const_cast<Layer*>(site.layer());
      }
      break;
    }

    case FromFirstReferenceLayer: {
      doc::CelList cels;
      sprite->pickCels(pos.x, pos.y, site.frame(), kOpacityThreshold,
                       sprite->allVisibleReferenceLayers(), cels);

      for (const Cel* cel : cels) {
        doc::color_t imageColor;
        if (get_cel_pixel(cel, pos.x, pos.y,
                          site.frame(), imageColor)) {
          m_color = app::Color::fromImage(
            cel->image()->pixelFormat(), imageColor);
          m_layer = cel->layer();
          break;
        }
      }
      break;
    }
  }
}

} // namespace app
