// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef GRADIENT_GLSL_
#define GRADIENT_GLSL_

#include <impeller/texture.glsl>
#include <impeller/transform.glsl>

mat3 IPMapToUnitX(vec2 p0, vec2 p1) {
  // Returns a matrix that maps [p0, p1] to [(0, 0), (1, 0)]. Results are
  // undefined if p0 = p1.
  return mat3(0.0, -1.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0) *
         IPMat3Inverse(mat3(p1.y - p0.y, p0.x - p1.x, 0.0, p1.x - p0.x,
                            p1.y - p0.y, 0.0, p0.x, p0.y, 1.0));
}

vec2 IPComputeConicalTRadial(vec2 c0, float r0, vec2 c1, float r1, vec2 pos) {
  float d_radius = r1 - r0;
  float scale = 1.0 / d_radius;
  float scale_sign = sign(d_radius);
  float bias = r0 / d_radius;

  vec2 pt = (pos - c0) * scale;
  float t = length(pt) * scale_sign - bias;
  return vec2(t, 1.0);
}

vec2 IPComputeConicalTStrip(vec2 c0, float r0, vec2 c1, float r1, vec2 pos) {
  float d_center = distance(c0, c1);
  mat3 transform = IPMapToUnitX(c0, c1);
  float r = r0 / d_center;
  float r_2 = r * r;

  vec2 pt = (transform * vec3(pos.xy, 1.0)).xy;
  float t = r_2 - pt.y * pt.y;
  if (t < 0.0) {
    return vec2(0.0, -1.0);
  }
  t = pt.x + sqrt(t);
  return vec2(t, 1.0);
}

vec2 IPComputeConicalTConical(vec2 c0, float r0, vec2 c1, float r1, vec2 pos) {
  const float scalar_nearly_zero = 1.0 / float(1 << 12);
  float d_center = distance(c0, c1);
  // See https://skia.org/docs/dev/design/conical/ for details on how this
  // algorithm works. Calculate f and swap inputs if necessary (steps 1 and
  // 2).
  float f = r0 / (r0 - r1);

  bool is_swapped = abs(f - 1.0) < scalar_nearly_zero;
  if (is_swapped) {
    vec2 tmp_pt = c0;
    c0 = c1;
    c1 = tmp_pt;
    f = 0.0;
  }

  // Apply mapping from [Cf, C1] to unit x, and apply the precalculations from
  // steps 3 and 4, all in the same transform.
  vec2 cf = c0 * (1.0 - f) + c1 * f;
  mat3 transform = IPMapToUnitX(cf, c1);

  float scale_x = abs(1.0 - f);
  float scale_y = scale_x;
  float local_r1 = abs(r1 - r0) / d_center;
  bool is_focal_on_circle = abs(local_r1 - 1.0) < scalar_nearly_zero;
  if (is_focal_on_circle) {
    scale_x *= 0.5;
    scale_y *= 0.5;
  } else {
    scale_x *= local_r1 / (local_r1 * local_r1 - 1.0);
    scale_y /= sqrt(abs(local_r1 * local_r1 - 1.0));
  }
  transform =
      mat3(scale_x, 0.0, 0.0, 0.0, scale_y, 0.0, 0.0, 0.0, 1.0) * transform;

  vec2 pt = (transform * vec3(pos.xy, 1.0)).xy;

  // Continue with step 5 onward.
  float inv_r1 = 1.0 / local_r1;
  float d_radius_sign = sign(1.0 - f);
  bool is_well_behaved = !is_focal_on_circle && local_r1 > 1.0;

  float x_t = -1.0;
  if (is_focal_on_circle) {
    x_t = dot(pt, pt) / pt.x;
  } else if (is_well_behaved) {
    x_t = length(pt) - pt.x * inv_r1;
  } else {
    float temp = pt.x * pt.x - pt.y * pt.y;
    if (temp >= 0.0) {
      if (is_swapped || d_radius_sign < 0.0) {
        x_t = -sqrt(temp) - pt.x * inv_r1;
      } else {
        x_t = sqrt(temp) - pt.x * inv_r1;
      }
    }
  }

  if (!is_well_behaved && x_t < 0.0) {
    return vec2(0.0, -1.0);
  }

  float t = f + d_radius_sign * x_t;
  if (is_swapped) {
    t = 1.0 - t;
  }
  return vec2(t, 1.0);
}

/// Compute the t value for a conical gradient at point `p` between the 2
/// circles defined by (c0, r0) and (c1, r1). The returned vec2 encapsulates 't'
/// as its x component and validity status as its y component, with positive y
/// indicating a valid result.
///
/// The code is migrated from Skia Graphite. See
/// https://github.com/google/skia/blob/ddf987d2ab3314ee0e80ac1ae7dbffb44a87d394/src/sksl/sksl_graphite_frag.sksl#L541-L666.
vec2 IPComputeConicalT(float kind,
                       vec2 c0,
                       float r0,
                       vec2 c1,
                       float r1,
                       vec2 pos) {
  if (kind == 0.0) {
    return vec2(0.0, -1.0);
  } else if (kind == 1.0) {
    return IPComputeConicalTRadial(c0, r0, c1, r1, pos);
  } else if (kind == 2.0) {
    return IPComputeConicalTStrip(c0, r0, c1, r1, pos);
  } else {
    return IPComputeConicalTConical(c0, r0, c1, r1, pos);
  }
}

/// Compute the indexes and mix coefficient used to mix colors for an
/// arbitrarily sized color gradient.
///
/// The returned values are the lower index, upper index, and mix
/// coefficient.
vec3 IPComputeFixedGradientValues(float t, float colors_length) {
  float rough_index = (colors_length - 1) * t;
  float lower_index = floor(rough_index);
  float upper_index = ceil(rough_index);
  float scale = rough_index - lower_index;

  return vec3(lower_index, upper_index, scale);
}

#endif
