#include "iwa_lineargradientfx.h"

#include "tspectrumparam.h"
#include "gradients.h"
#include "tparamuiconcept.h"
#include "traster.h"

//------------------------------------------------------------

Iwa_LinearGradientFx::Iwa_LinearGradientFx()
    : m_startPoint(TPointD(-50.0, 0.0))
    , m_endPoint(TPointD(50.0, 0.0))
    , m_startColor(TPixel32::Black)
    , m_endColor(TPixel32::White)
    , m_curveType(new TIntEnumParam(EaseInOut, "Ease In-Out"))
    , m_wave_amplitude(0.0)
    , m_wave_freq(0.0)
    , m_wave_phase(0.0) {
  m_startPoint->getX()->setMeasureName("fxLength");
  m_startPoint->getY()->setMeasureName("fxLength");
  m_endPoint->getX()->setMeasureName("fxLength");
  m_endPoint->getY()->setMeasureName("fxLength");
  bindParam(this, "startPoint", m_startPoint);
  bindParam(this, "endPoint", m_endPoint);

  m_curveType->addItem(Linear, "Linear");
  m_curveType->addItem(EaseIn, "Ease In");
  m_curveType->addItem(EaseOut, "Ease Out");
  bindParam(this, "curveType", m_curveType);

  m_wave_amplitude->setValueRange(0, std::numeric_limits<double>::max());
  m_wave_amplitude->setMeasureName("fxLength");
  bindParam(this, "wave_amplitude", m_wave_amplitude);
  bindParam(this, "wave_frequency", m_wave_freq);
  bindParam(this, "wave_phase", m_wave_phase);

  bindParam(this, "startColor", m_startColor);
  bindParam(this, "endColor", m_endColor);

  enableComputeInFloat(true);
}

//------------------------------------------------------------

bool Iwa_LinearGradientFx::doGetBBox(double frame, TRectD &bBox,
                                     const TRenderSettings &ri) {
  bBox = TConsts::infiniteRectD;
  return true;
}

//------------------------------------------------------------

namespace {
template <typename RASTER, typename PIXEL>
void doLinearGradientT(RASTER ras, TDimensionI dim, TPointD startPos,
                       TPointD endPos, const TSpectrumT<PIXEL> &spectrum,
                       GradientCurveType type, double w_amplitude,
                       double w_freq, double w_phase, const TAffine &affInv) {
  auto getFactor = [&](double t) {
    if (t > 1.0)
      t = 1.0;
    else if (t < 0.0)
      t = 0.0;

    double factor;
    switch (type) {
    case Linear:
      factor = t;
      break;
    case EaseIn:
      factor = t * t;
      break;
    case EaseOut:
      factor = 1.0 - (1.0 - t) * (1.0 - t);
      break;
    case EaseInOut:
    default:
      factor = (-2 * t + 3) * (t * t);
      break;
    }
    return factor;
  };
  startPos      = affInv * startPos;
  endPos        = affInv * endPos;
  TPointD seVec = endPos - startPos;

  if (seVec == TPointD()) {
    ras->fill(spectrum.getPremultipliedValue(0.0));
    return;
  }

  TPointD posTrasf = -startPos;
  double seVecLen2 = seVec.x * seVec.x + seVec.y * seVec.y;
  double seVecLen  = sqrt(seVecLen2);
  double amplitude = w_amplitude / seVecLen;
  TPointD auxVec(-seVec.y / seVecLen, seVec.x / seVecLen);

  ras->lock();
  for (int j = 0; j < ras->getLy(); j++) {
    TPointD posAux = posTrasf;

    PIXEL *pix    = ras->pixels(j);
    PIXEL *endPix = pix + ras->getLx();

    while (pix < endPix) {
      double t = (seVec.x * posAux.x + seVec.y * posAux.y) / seVecLen2;
      if (amplitude) {
        double distance = posAux.x * auxVec.x + posAux.y * auxVec.y;
        t += amplitude * sin(w_freq * distance + w_phase);
      }
      double factor = getFactor(t);
      *pix++        = spectrum.getPremultipliedValue(factor);

      posAux.x += affInv.a11;
      posAux.y += affInv.a21;
    }
    posTrasf.x += affInv.a12;
    posTrasf.y += affInv.a22;
  }
  ras->unlock();
}
}  // namespace

//------------------------------------------------------------

void Iwa_LinearGradientFx::doCompute(TTile &tile, double frame,
                                     const TRenderSettings &ri) {
  if (!((TRaster32P)tile.getRaster()) && !((TRaster64P)tile.getRaster()) &&
      !((TRasterFP)tile.getRaster())) {
    throw TRopException("unsupported input pixel type");
  }

  // convert shape position to render region coordinate
  TAffine aff = ri.m_affine;
  TDimensionI dimOut(tile.getRaster()->getLx(), tile.getRaster()->getLy());
  TPointD dimOffset((float)dimOut.lx / 2.0f, (float)dimOut.ly / 2.0f);
  TPointD startPos = aff * m_startPoint->getValue(frame) -
                     (tile.m_pos + tile.getRaster()->getCenterD()) + dimOffset;
  TPointD endPos = aff * m_endPoint->getValue(frame) -
                   (tile.m_pos + tile.getRaster()->getCenterD()) + dimOffset;

  double w_amplitude = m_wave_amplitude->getValue(frame) / ri.m_shrinkX;
  double w_freq      = m_wave_freq->getValue(frame) * ri.m_shrinkX;
  double w_phase     = m_wave_phase->getValue(frame);
  w_freq *= 0.01 * M_PI_180;

  std::vector<TSpectrum::ColorKey> colors = {
      TSpectrum::ColorKey(0, m_startColor->getValue(frame)),
      TSpectrum::ColorKey(1, m_endColor->getValue(frame))};

  TSpectrumParamP m_colors = TSpectrumParamP(colors);

  tile.getRaster()->clear();
  TRaster32P outRas32 = (TRaster32P)tile.getRaster();
  TRaster64P outRas64 = (TRaster64P)tile.getRaster();
  TRasterFP outRasF   = (TRasterFP)tile.getRaster();
  if (outRas32)
    doLinearGradientT<TRaster32P, TPixel32>(
        outRas32, dimOut, startPos, endPos, m_colors->getValue(frame),
        (GradientCurveType)m_curveType->getValue(), w_amplitude, w_freq,
        w_phase, aff.inv());
  else if (outRas64)
    doLinearGradientT<TRaster64P, TPixel64>(
        outRas64, dimOut, startPos, endPos, m_colors->getValue64(frame),
        (GradientCurveType)m_curveType->getValue(), w_amplitude, w_freq,
        w_phase, aff.inv());
  else if (outRasF)
    doLinearGradientT<TRasterFP, TPixelF>(
        outRasF, dimOut, startPos, endPos, m_colors->getValueF(frame),
        (GradientCurveType)m_curveType->getValue(), w_amplitude, w_freq,
        w_phase, aff.inv());
}

//------------------------------------------------------------

void Iwa_LinearGradientFx::getParamUIs(TParamUIConcept *&concepts,
                                       int &length) {
  concepts = new TParamUIConcept[length = 1];

  concepts[0].m_type  = TParamUIConcept::LINEAR_RANGE;
  concepts[0].m_label = "";
  concepts[0].m_params.push_back(m_startPoint);
  concepts[0].m_params.push_back(m_endPoint);
}

//------------------------------------------------------------

FX_PLUGIN_IDENTIFIER(Iwa_LinearGradientFx, "iwa_LinearGradientFx");
