// 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.

import 'dart:math' as math;
import 'dart:typed_data';

import 'package:ui/ui.dart' as ui;

import '../util.dart';
import '../validators.dart';
import 'canvaskit_api.dart';
import 'image.dart';
import 'initialization.dart';
import 'skia_object_cache.dart';

abstract class CkShader extends ManagedSkiaObject<SkShader>
    implements ui.Shader {
  SkShader withQuality(ui.FilterQuality contextualQuality) => skiaObject;

  @override
  void delete() {
    rawSkiaObject?.delete();
  }
}

class CkGradientSweep extends CkShader implements ui.Gradient {
  CkGradientSweep(this.center, this.colors, this.colorStops, this.tileMode,
      this.startAngle, this.endAngle, this.matrix4)
      : assert(offsetIsValid(center)),
        assert(colors != null), // ignore: unnecessary_null_comparison
        assert(tileMode != null), // ignore: unnecessary_null_comparison
        assert(startAngle != null), // ignore: unnecessary_null_comparison
        assert(endAngle != null), // ignore: unnecessary_null_comparison
        assert(startAngle < endAngle),
        assert(matrix4 == null || matrix4IsValid(matrix4)) {
    validateColorStops(colors, colorStops);
  }

  final ui.Offset center;
  final List<ui.Color> colors;
  final List<double>? colorStops;
  final ui.TileMode tileMode;
  final double startAngle;
  final double endAngle;
  final Float32List? matrix4;

  @override
  SkShader createDefault() {
    const double toDegrees = 180.0 / math.pi;
    return canvasKit.Shader.MakeSweepGradient(
      center.dx,
      center.dy,
      toFlatColors(colors),
      toSkColorStops(colorStops),
      toSkTileMode(tileMode),
      matrix4 != null ? toSkMatrixFromFloat32(matrix4!) : null,
      0,
      toDegrees * startAngle,
      toDegrees * endAngle,
    );
  }

  @override
  SkShader resurrect() {
    return createDefault();
  }
}

class CkGradientLinear extends CkShader implements ui.Gradient {
  CkGradientLinear(
    this.from,
    this.to,
    this.colors,
    this.colorStops,
    this.tileMode,
    Float32List? matrix,
  )   : assert(offsetIsValid(from)),
        assert(offsetIsValid(to)),
        assert(colors != null), // ignore: unnecessary_null_comparison
        assert(tileMode != null), // ignore: unnecessary_null_comparison
        matrix4 = matrix {
    if (assertionsEnabled) {
      assert(matrix4 == null || matrix4IsValid(matrix4!));
      validateColorStops(colors, colorStops);
    }
  }

  final ui.Offset from;
  final ui.Offset to;
  final List<ui.Color> colors;
  final List<double>? colorStops;
  final ui.TileMode tileMode;
  final Float32List? matrix4;

  @override
  SkShader createDefault() {
    assert(useCanvasKit);

    return canvasKit.Shader.MakeLinearGradient(
      toSkPoint(from),
      toSkPoint(to),
      toFlatColors(colors),
      toSkColorStops(colorStops),
      toSkTileMode(tileMode),
      matrix4 != null ? toSkMatrixFromFloat32(matrix4!) : null,
    );
  }

  @override
  SkShader resurrect() => createDefault();
}

class CkGradientRadial extends CkShader implements ui.Gradient {
  CkGradientRadial(this.center, this.radius, this.colors, this.colorStops,
      this.tileMode, this.matrix4);

  final ui.Offset center;
  final double radius;
  final List<ui.Color> colors;
  final List<double>? colorStops;
  final ui.TileMode tileMode;
  final Float32List? matrix4;

  @override
  SkShader createDefault() {
    assert(useCanvasKit);

    return canvasKit.Shader.MakeRadialGradient(
      toSkPoint(center),
      radius,
      toFlatColors(colors),
      toSkColorStops(colorStops),
      toSkTileMode(tileMode),
      matrix4 != null ? toSkMatrixFromFloat32(matrix4!) : null,
      0,
    );
  }

  @override
  SkShader resurrect() => createDefault();
}

class CkGradientConical extends CkShader implements ui.Gradient {
  CkGradientConical(this.focal, this.focalRadius, this.center, this.radius,
      this.colors, this.colorStops, this.tileMode, this.matrix4);

  final ui.Offset focal;
  final double focalRadius;
  final ui.Offset center;
  final double radius;
  final List<ui.Color> colors;
  final List<double>? colorStops;
  final ui.TileMode tileMode;
  final Float32List? matrix4;

  @override
  SkShader createDefault() {
    assert(useCanvasKit);
    return canvasKit.Shader.MakeTwoPointConicalGradient(
      toSkPoint(focal),
      focalRadius,
      toSkPoint(center),
      radius,
      toFlatColors(colors),
      toSkColorStops(colorStops),
      toSkTileMode(tileMode),
      matrix4 != null ? toSkMatrixFromFloat32(matrix4!) : null,
      0,
    );
  }

  @override
  SkShader resurrect() => createDefault();
}

class CkImageShader extends CkShader implements ui.ImageShader {
  CkImageShader(ui.Image image, this.tileModeX, this.tileModeY, this.matrix4,
      this.filterQuality)
      : _image = image as CkImage;

  final ui.TileMode tileModeX;
  final ui.TileMode tileModeY;
  final Float64List matrix4;
  final ui.FilterQuality? filterQuality;
  final CkImage _image;

  ui.FilterQuality? _cachedQuality;
  @override
  SkShader withQuality(ui.FilterQuality contextualQuality) {
    final ui.FilterQuality quality = filterQuality ?? contextualQuality;
    SkShader? shader = rawSkiaObject;
    if (_cachedQuality != quality || shader == null) {
      if (quality == ui.FilterQuality.high) {
        shader = _image.skImage.makeShaderCubic(
          toSkTileMode(tileModeX),
          toSkTileMode(tileModeY),
          1.0 / 3.0,
          1.0 / 3.0,
          toSkMatrixFromFloat64(matrix4),
        );
      } else {
        shader = _image.skImage.makeShaderOptions(
          toSkTileMode(tileModeX),
          toSkTileMode(tileModeY),
          toSkFilterMode(quality),
          toSkMipmapMode(quality),
          toSkMatrixFromFloat64(matrix4),
        );
      }
      _cachedQuality = quality;
      rawSkiaObject = shader;
    }
    return shader;
  }

  @override
  SkShader createDefault() => withQuality(ui.FilterQuality.none);

  @override
  SkShader resurrect() => withQuality(_cachedQuality ?? ui.FilterQuality.none);

  @override
  void delete() {
    rawSkiaObject?.delete();
  }
}
