/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#include "2D.h"
#include "Filters.h"
#include "SIMD.h"

namespace mozilla {
namespace gfx {

template <TurbulenceType Type, bool Stitch, typename f32x4_t, typename i32x4_t,
          typename u8x16_t>
class SVGTurbulenceRenderer {
 public:
  SVGTurbulenceRenderer(const Size& aBaseFrequency, int32_t aSeed,
                        int aNumOctaves, const Rect& aTileRect);

  already_AddRefed<DataSourceSurface> Render(const IntSize& aSize,
                                             const Point& aOffset) const;

 private:
  /* The turbulence calculation code is an adapted version of what
     appears in the SVG 1.1 specification:
         http://www.w3.org/TR/SVG11/filters.html#feTurbulence
  */

  struct StitchInfo {
    int32_t width;  // How much to subtract to wrap for stitching.
    int32_t height;
    int32_t wrapX;  // Minimum value to wrap.
    int32_t wrapY;
  };

  const static int sBSize = 0x100;
  const static int sBM = 0xff;
  void InitFromSeed(int32_t aSeed);
  void AdjustBaseFrequencyForStitch(const Rect& aTileRect);
  IntPoint AdjustForStitch(IntPoint aLatticePoint,
                           const StitchInfo& aStitchInfo) const;
  StitchInfo CreateStitchInfo(const Rect& aTileRect) const;
  f32x4_t Noise2(Point aVec, const StitchInfo& aStitchInfo) const;
  i32x4_t Turbulence(const Point& aPoint) const;
  Point EquivalentNonNegativeOffset(const Point& aOffset) const;

  Size mBaseFrequency;
  int32_t mNumOctaves;
  StitchInfo mStitchInfo;
  bool mStitchable;
  TurbulenceType mType;
  uint8_t mLatticeSelector[sBSize];
  f32x4_t mGradient[sBSize][2];
};

namespace {

struct RandomNumberSource {
  explicit RandomNumberSource(int32_t aSeed) : mLast(SetupSeed(aSeed)) {}
  int32_t Next() {
    mLast = Random(mLast);
    return mLast;
  }

 private:
  static const int32_t RAND_M = 2147483647; /* 2**31 - 1 */
  static const int32_t RAND_A = 16807;      /* 7**5; primitive root of m */
  static const int32_t RAND_Q = 127773;     /* m / a */
  static const int32_t RAND_R = 2836;       /* m % a */

  /* Produces results in the range [1, 2**31 - 2].
     Algorithm is: r = (a * r) mod m
     where a = 16807 and m = 2**31 - 1 = 2147483647
     See [Park & Miller], CACM vol. 31 no. 10 p. 1195, Oct. 1988
     To test: the algorithm should produce the result 1043618065
     as the 10,000th generated number if the original seed is 1.
  */

  static int32_t SetupSeed(int32_t aSeed) {
    if (aSeed <= 0) aSeed = -(aSeed % (RAND_M - 1)) + 1;
    if (aSeed > RAND_M - 1) aSeed = RAND_M - 1;
    return aSeed;
  }

  static int32_t Random(int32_t aSeed) {
    int32_t result = RAND_A * (aSeed % RAND_Q) - RAND_R * (aSeed / RAND_Q);
    if (result <= 0) result += RAND_M;
    return result;
  }

  int32_t mLast;
};

}  // unnamed namespace

template <TurbulenceType Type, bool Stitch, typename f32x4_t, typename i32x4_t,
          typename u8x16_t>
SVGTurbulenceRenderer<Type, Stitch, f32x4_t, i32x4_t, u8x16_t>::
    SVGTurbulenceRenderer(const Size& aBaseFrequency, int32_t aSeed,
                          int aNumOctaves, const Rect& aTileRect)
    : mBaseFrequency(aBaseFrequency),
      mNumOctaves(aNumOctaves),
      mStitchInfo(),
      mStitchable(false),
      mType(TURBULENCE_TYPE_TURBULENCE) {
  InitFromSeed(aSeed);
  if (Stitch) {
    AdjustBaseFrequencyForStitch(aTileRect);
    mStitchInfo = CreateStitchInfo(aTileRect);
  }
}

template <TurbulenceType Type, bool Stitch, typename f32x4_t, typename i32x4_t,
          typename u8x16_t>
void SVGTurbulenceRenderer<Type, Stitch, f32x4_t, i32x4_t,
                           u8x16_t>::InitFromSeed(int32_t aSeed) {
  RandomNumberSource rand(aSeed);

  float gradient[4][sBSize][2];
  for (int32_t k = 0; k < 4; k++) {
    for (int32_t i = 0; i < sBSize; i++) {
      float a, b;
      do {
        a = float((rand.Next() % (sBSize + sBSize)) - sBSize) / sBSize;
        b = float((rand.Next() % (sBSize + sBSize)) - sBSize) / sBSize;
      } while (a == 0 && b == 0);
      float s = sqrt(a * a + b * b);
      gradient[k][i][0] = a / s;
      gradient[k][i][1] = b / s;
    }
  }

  for (int32_t i = 0; i < sBSize; i++) {
    mLatticeSelector[i] = i;
  }
  for (int32_t i1 = sBSize - 1; i1 > 0; i1--) {
    int32_t i2 = rand.Next() % sBSize;
    std::swap(mLatticeSelector[i1], mLatticeSelector[i2]);
  }

  for (int32_t i = 0; i < sBSize; i++) {
    // Contrary to the code in the spec, we build the first lattice selector
    // lookup into mGradient so that we don't need to do it again for every
    // pixel.
    // We also change the order of the gradient indexing so that we can process
    // all four color channels at the same time.
    uint8_t j = mLatticeSelector[i];
    mGradient[i][0] =
        simd::FromF32<f32x4_t>(gradient[2][j][0], gradient[1][j][0],
                               gradient[0][j][0], gradient[3][j][0]);
    mGradient[i][1] =
        simd::FromF32<f32x4_t>(gradient[2][j][1], gradient[1][j][1],
                               gradient[0][j][1], gradient[3][j][1]);
  }
}

// Adjust aFreq such that aLength * AdjustForLength(aFreq, aLength) is integer
// and as close to aLength * aFreq as possible.
static inline float AdjustForLength(float aFreq, float aLength) {
  float lowFreq = floor(aLength * aFreq) / aLength;
  float hiFreq = ceil(aLength * aFreq) / aLength;
  if (aFreq / lowFreq < hiFreq / aFreq) {
    return lowFreq;
  }
  return hiFreq;
}

template <TurbulenceType Type, bool Stitch, typename f32x4_t, typename i32x4_t,
          typename u8x16_t>
void SVGTurbulenceRenderer<Type, Stitch, f32x4_t, i32x4_t, u8x16_t>::
    AdjustBaseFrequencyForStitch(const Rect& aTileRect) {
  mBaseFrequency =
      Size(AdjustForLength(mBaseFrequency.width, aTileRect.Width()),
           AdjustForLength(mBaseFrequency.height, aTileRect.Height()));
}

template <TurbulenceType Type, bool Stitch, typename f32x4_t, typename i32x4_t,
          typename u8x16_t>
typename SVGTurbulenceRenderer<Type, Stitch, f32x4_t, i32x4_t,
                               u8x16_t>::StitchInfo
SVGTurbulenceRenderer<Type, Stitch, f32x4_t, i32x4_t,
                      u8x16_t>::CreateStitchInfo(const Rect& aTileRect) const {
  StitchInfo stitch;
  stitch.width =
      int32_t(floorf(aTileRect.Width() * mBaseFrequency.width + 0.5f));
  stitch.height =
      int32_t(floorf(aTileRect.Height() * mBaseFrequency.height + 0.5f));
  stitch.wrapX = int32_t(aTileRect.X() * mBaseFrequency.width) + stitch.width;
  stitch.wrapY = int32_t(aTileRect.Y() * mBaseFrequency.height) + stitch.height;
  return stitch;
}

static MOZ_ALWAYS_INLINE Float SCurve(Float t) { return t * t * (3 - 2 * t); }

static MOZ_ALWAYS_INLINE Point SCurve(Point t) {
  return Point(SCurve(t.x), SCurve(t.y));
}

template <typename f32x4_t>
static MOZ_ALWAYS_INLINE f32x4_t BiMix(const f32x4_t& aa, const f32x4_t& ab,
                                       const f32x4_t& ba, const f32x4_t& bb,
                                       Point s) {
  return simd::MixF32(simd::MixF32(aa, ab, s.x), simd::MixF32(ba, bb, s.x),
                      s.y);
}

template <TurbulenceType Type, bool Stitch, typename f32x4_t, typename i32x4_t,
          typename u8x16_t>
IntPoint
SVGTurbulenceRenderer<Type, Stitch, f32x4_t, i32x4_t, u8x16_t>::AdjustForStitch(
    IntPoint aLatticePoint, const StitchInfo& aStitchInfo) const {
  if (Stitch) {
    if (aLatticePoint.x >= aStitchInfo.wrapX) {
      aLatticePoint.x -= aStitchInfo.width;
    }
    if (aLatticePoint.y >= aStitchInfo.wrapY) {
      aLatticePoint.y -= aStitchInfo.height;
    }
  }
  return aLatticePoint;
}

template <TurbulenceType Type, bool Stitch, typename f32x4_t, typename i32x4_t,
          typename u8x16_t>
f32x4_t SVGTurbulenceRenderer<Type, Stitch, f32x4_t, i32x4_t, u8x16_t>::Noise2(
    Point aVec, const StitchInfo& aStitchInfo) const {
  // aVec is guaranteed to be non-negative, so casting to int32_t always
  // rounds towards negative infinity.
  IntPoint topLeftLatticePoint(int32_t(aVec.x), int32_t(aVec.y));
  Point r = aVec - topLeftLatticePoint;  // fractional offset

  IntPoint b0 = AdjustForStitch(topLeftLatticePoint, aStitchInfo);
  IntPoint b1 = AdjustForStitch(b0 + IntPoint(1, 1), aStitchInfo);

  uint8_t i = mLatticeSelector[b0.x & sBM];
  uint8_t j = mLatticeSelector[b1.x & sBM];

  const f32x4_t* qua = mGradient[(i + b0.y) & sBM];
  const f32x4_t* qub = mGradient[(i + b1.y) & sBM];
  const f32x4_t* qva = mGradient[(j + b0.y) & sBM];
  const f32x4_t* qvb = mGradient[(j + b1.y) & sBM];

  return BiMix(simd::WSumF32(qua[0], qua[1], r.x, r.y),
               simd::WSumF32(qva[0], qva[1], r.x - 1.f, r.y),
               simd::WSumF32(qub[0], qub[1], r.x, r.y - 1.f),
               simd::WSumF32(qvb[0], qvb[1], r.x - 1.f, r.y - 1.f), SCurve(r));
}

template <typename f32x4_t, typename i32x4_t, typename u8x16_t>
static inline i32x4_t ColorToBGRA(f32x4_t aUnscaledUnpreFloat) {
  // Color is an unpremultiplied float vector where 1.0f means white. We will
  // convert it into an integer vector where 255 means white.
  f32x4_t alpha = simd::SplatF32<3>(aUnscaledUnpreFloat);
  f32x4_t scaledUnpreFloat =
      simd::MulF32(aUnscaledUnpreFloat, simd::FromF32<f32x4_t>(255));
  i32x4_t scaledUnpreInt = simd::F32ToI32(scaledUnpreFloat);

  // Multiply all channels with alpha.
  i32x4_t scaledPreInt = simd::F32ToI32(simd::MulF32(scaledUnpreFloat, alpha));

  // Use the premultiplied color channels and the unpremultiplied alpha channel.
  i32x4_t alphaMask = simd::From32<i32x4_t>(0, 0, 0, -1);
  return simd::Pick(alphaMask, scaledPreInt, scaledUnpreInt);
}

template <TurbulenceType Type, bool Stitch, typename f32x4_t, typename i32x4_t,
          typename u8x16_t>
i32x4_t SVGTurbulenceRenderer<Type, Stitch, f32x4_t, i32x4_t,
                              u8x16_t>::Turbulence(const Point& aPoint) const {
  StitchInfo stitchInfo = mStitchInfo;
  f32x4_t sum = simd::FromF32<f32x4_t>(0);
  Point vec(aPoint.x * mBaseFrequency.width, aPoint.y * mBaseFrequency.height);
  f32x4_t ratio = simd::FromF32<f32x4_t>(1);

  for (int octave = 0; octave < mNumOctaves; octave++) {
    f32x4_t thisOctave = Noise2(vec, stitchInfo);
    if (Type == TURBULENCE_TYPE_TURBULENCE) {
      thisOctave = simd::AbsF32(thisOctave);
    }
    sum = simd::AddF32(sum, simd::DivF32(thisOctave, ratio));
    vec = vec * 2;
    ratio = simd::MulF32(ratio, simd::FromF32<f32x4_t>(2));

    if (Stitch) {
      stitchInfo.width *= 2;
      stitchInfo.wrapX *= 2;
      stitchInfo.height *= 2;
      stitchInfo.wrapY *= 2;
    }
  }

  if (Type == TURBULENCE_TYPE_FRACTAL_NOISE) {
    sum = simd::DivF32(simd::AddF32(sum, simd::FromF32<f32x4_t>(1)),
                       simd::FromF32<f32x4_t>(2));
  }
  return ColorToBGRA<f32x4_t, i32x4_t, u8x16_t>(sum);
}

static inline Float MakeNonNegative(Float aValue, Float aIncrementSize) {
  if (aIncrementSize == 0) {
    return 0;
  }
  if (aValue >= 0) {
    return aValue;
  }
  return aValue + ceilf(-aValue / aIncrementSize) * aIncrementSize;
}

static inline Float FiniteDivide(Float aValue, Float aDivisor) {
  if (aDivisor == 0) {
    return 0;
  }
  return aValue / aDivisor;
}

template <TurbulenceType Type, bool Stitch, typename f32x4_t, typename i32x4_t,
          typename u8x16_t>
Point SVGTurbulenceRenderer<Type, Stitch, f32x4_t, i32x4_t, u8x16_t>::
    EquivalentNonNegativeOffset(const Point& aOffset) const {
  Size basePeriod = Stitch ? Size(mStitchInfo.width, mStitchInfo.height)
                           : Size(sBSize, sBSize);
  Size repeatingSize(FiniteDivide(basePeriod.width, mBaseFrequency.width),
                     FiniteDivide(basePeriod.height, mBaseFrequency.height));
  return Point(MakeNonNegative(aOffset.x, repeatingSize.width),
               MakeNonNegative(aOffset.y, repeatingSize.height));
}

template <TurbulenceType Type, bool Stitch, typename f32x4_t, typename i32x4_t,
          typename u8x16_t>
already_AddRefed<DataSourceSurface>
SVGTurbulenceRenderer<Type, Stitch, f32x4_t, i32x4_t, u8x16_t>::Render(
    const IntSize& aSize, const Point& aOffset) const {
  RefPtr<DataSourceSurface> target =
      Factory::CreateDataSourceSurface(aSize, SurfaceFormat::B8G8R8A8);
  if (!target) {
    return nullptr;
  }

  DataSourceSurface::ScopedMap map(target, DataSourceSurface::READ_WRITE);
  uint8_t* targetData = map.GetData();
  uint32_t stride = map.GetStride();

  Point startOffset = EquivalentNonNegativeOffset(aOffset);

  for (int32_t y = 0; y < aSize.height; y++) {
    for (int32_t x = 0; x < aSize.width; x += 4) {
      int32_t targIndex = y * stride + x * 4;
      i32x4_t a = Turbulence(startOffset + Point(x, y));
      i32x4_t b = Turbulence(startOffset + Point(x + 1, y));
      i32x4_t c = Turbulence(startOffset + Point(x + 2, y));
      i32x4_t d = Turbulence(startOffset + Point(x + 3, y));
      u8x16_t result1234 = simd::PackAndSaturate32To8(a, b, c, d);
      simd::Store8(&targetData[targIndex], result1234);
    }
  }

  return target.forget();
}

}  // namespace gfx
}  // namespace mozilla
