// Aseprite Document Library
// Copyright (c) 2024-2025 Igara Studio S.A.
// Copyright (c) 2001-2015 David Capello
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.

#ifndef DOC_BLEND_INTERNALS_H_INCLUDED
#define DOC_BLEND_INTERNALS_H_INCLUDED
#pragma once

#include "../../third_party/pixman/pixman/pixman-combine32.h"

#if !defined(MUL_UN8) || !defined(DIV_UN8)
  #error Invalid Pixman library
#endif

#include "doc/blend_funcs.h"
#include "doc/blend_mode.h"
#include "doc/color.h"
#include "doc/image.h"
#include "doc/image_traits.h"
#include "doc/palette.h"

namespace doc {

template<class DstTraits, class SrcTraits>
class BlenderHelper {
  BlendMode m_blendMode;
  BlendFunc m_blendFunc;
  color_t m_maskColor;

public:
  BlenderHelper(Image* dst,
                const Image* src,
                const Palette* pal,
                const BlendMode blendMode,
                const bool newBlend)
  {
    m_blendMode = blendMode;
    m_blendFunc = SrcTraits::get_blender(blendMode, newBlend);
    m_maskColor = src->maskColor();
  }

  inline typename DstTraits::pixel_t operator()(typename DstTraits::pixel_t dst,
                                                typename SrcTraits::pixel_t src,
                                                int opacity)
  {
    if (m_blendMode == BlendMode::SRC)
      return src;
    if (src != m_maskColor)
      return (*m_blendFunc)(dst, src, opacity);
    else
      return dst;
  }
};

//////////////////////////////////////////////////////////////////////
// X -> Rgb

template<>
class BlenderHelper<RgbTraits, GrayscaleTraits> {
  BlendMode m_blendMode;
  BlendFunc m_blendFunc;
  color_t m_maskColor;

public:
  BlenderHelper(Image* dst,
                const Image* src,
                const Palette* pal,
                const BlendMode blendMode,
                const bool newBlend)
  {
    m_blendMode = blendMode;
    m_blendFunc = RgbTraits::get_blender(blendMode, newBlend);
    m_maskColor = src->maskColor();
  }

  inline RgbTraits::pixel_t operator()(RgbTraits::pixel_t dst,
                                       GrayscaleTraits::pixel_t src,
                                       int opacity)
  {
    if (m_blendMode == BlendMode::SRC)
      return src;
    if (src != m_maskColor) {
      int v = graya_getv(src);
      return (*m_blendFunc)(dst, rgba(v, v, v, graya_geta(src)), opacity);
    }
    else
      return dst;
  }
};

template<>
class BlenderHelper<RgbTraits, IndexedTraits> {
  const Palette* m_pal;
  BlendMode m_blendMode;
  BlendFunc m_blendFunc;
  color_t m_maskColor;

public:
  BlenderHelper(Image* dst,
                const Image* src,
                const Palette* pal,
                const BlendMode blendMode,
                const bool newBlend)
  {
    m_blendMode = blendMode;
    m_blendFunc = RgbTraits::get_blender(blendMode, newBlend);
    m_maskColor = src->maskColor();
    m_pal = pal;
  }

  inline RgbTraits::pixel_t operator()(RgbTraits::pixel_t dst,
                                       IndexedTraits::pixel_t src,
                                       int opacity)
  {
    if (m_blendMode == BlendMode::SRC) {
      return m_pal->getEntry(src);
    }
    else {
      if (src != m_maskColor) {
        return (*m_blendFunc)(dst, m_pal->getEntry(src), opacity);
      }
      else
        return dst;
    }
  }
};

//////////////////////////////////////////////////////////////////////
// X -> Grayscale

template<>
class BlenderHelper<GrayscaleTraits, RgbTraits> {
  BlendFunc m_blendFunc;

public:
  BlenderHelper(Image* dst,
                const Image* src,
                const Palette* pal,
                const BlendMode blendMode,
                const bool newBlend)
  {
    m_blendFunc = RgbTraits::get_blender(blendMode, newBlend);
  }

  inline GrayscaleTraits::pixel_t operator()(GrayscaleTraits::pixel_t dst,
                                             RgbTraits::pixel_t src,
                                             int opacity)
  {
    // TODO we should be able to configure this function
    return rgba_to_graya_using_luma(src);
  }
};

//////////////////////////////////////////////////////////////////////
// X -> Indexed

template<>
class BlenderHelper<IndexedTraits, RgbTraits> {
  const Palette* m_pal;
  BlendMode m_blendMode;
  BlendFunc m_blendFunc;
  color_t m_maskColor;

public:
  BlenderHelper(Image* dst,
                const Image* src,
                const Palette* pal,
                const BlendMode blendMode,
                const bool newBlend)
  {
    m_blendMode = blendMode;
    m_blendFunc = RgbTraits::get_blender(blendMode, newBlend);
    m_pal = pal;

    if (m_blendMode == BlendMode::SRC)
      m_maskColor = -1;
    else
      m_maskColor = dst->maskColor();
  }

  inline IndexedTraits::pixel_t operator()(IndexedTraits::pixel_t dst,
                                           RgbTraits::pixel_t src,
                                           int opacity)
  {
    if (dst != m_maskColor) {
      src = (*m_blendFunc)(m_pal->getEntry(dst), src, opacity);
    }
    return m_pal->findBestfit(rgba_getr(src),
                              rgba_getg(src),
                              rgba_getb(src),
                              rgba_geta(src),
                              m_maskColor);
  }
};

template<>
class BlenderHelper<IndexedTraits, IndexedTraits> {
  BlendMode m_blendMode;
  color_t m_maskColor;
  int m_paletteSize;

public:
  BlenderHelper(Image* dst,
                const Image* src,
                const Palette* pal,
                const BlendMode blendMode,
                const bool newBlend)
  {
    m_blendMode = blendMode;
    m_maskColor = src->maskColor();
    m_paletteSize = pal->size();
  }

  inline IndexedTraits::pixel_t operator()(IndexedTraits::pixel_t dst,
                                           IndexedTraits::pixel_t src,
                                           int opacity)
  {
    if (m_blendMode == BlendMode::SRC) {
      return src;
    }
    else if (m_blendMode == BlendMode::DST_OVER) {
      if (dst != m_maskColor)
        return dst;
      else
        return src;
    }
    else {
      if (src != m_maskColor && src < m_paletteSize)
        return src;
      else
        return dst;
    }
  }
};

} // namespace doc

#endif
