// 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:async';
import 'dart:io';
import 'dart:typed_data';
import 'dart:ui';

import 'package:litetest/litetest.dart';
import 'package:path/path.dart' as path;

const int _kWidth = 10;
const int _kRadius = 2;

const Color _kBlack = Color.fromRGBO(0, 0, 0, 1.0);
const Color _kGreen = Color.fromRGBO(0, 255, 0, 1.0);

void main() {
  test('decodeImageFromPixels float32', () async {
    const int width = 2;
    const int height = 2;
    final Float32List pixels = Float32List(width * height * 4);
    final List<List<double>> pixels2d = <List<double>>[
      <double>[1, 0, 0, 1],
      <double>[0, 1, 0, 1],
      <double>[0, 0, 1, 1],
      <double>[1, 1, 1, 0],
    ];
    int offset = 0;
    for (final List<double> color in pixels2d) {
      pixels[offset + 0] = color[0];
      pixels[offset + 1] = color[1];
      pixels[offset + 2] = color[2];
      pixels[offset + 3] = color[3];
      offset += 4;
    }

    final Completer<Image> completer = Completer<Image>();
    decodeImageFromPixels(
        Uint8List.view(pixels.buffer), width, height, PixelFormat.rgbaFloat32,
        (Image result) {
      completer.complete(result);
    });

    final Image image = await completer.future;
    final ByteData data =
        (await image.toByteData(format: ImageByteFormat.rawStraightRgba))!;
    final Uint32List readPixels = Uint32List.view(data.buffer);
    expect(width * height, readPixels.length);
    expect(readPixels[0], 0xff0000ff);
    expect(readPixels[1], 0xff00ff00);
    expect(readPixels[2], 0xffff0000);
    expect(readPixels[3], 0x00ffffff);
  });

  test('Image.toByteData RGBA format works with simple image', () async {
    final Image image = await Square4x4Image.image;
    final ByteData data = (await image.toByteData())!;
    expect(Uint8List.view(data.buffer), Square4x4Image.bytes);
  });

  test('Image.toByteData RGBA format converts grayscale images', () async {
    final Image image = await GrayscaleImage.load();
    final ByteData data = (await image.toByteData())!;
    final Uint8List bytes = data.buffer.asUint8List();
    expect(bytes, hasLength(16));
    expect(bytes, GrayscaleImage.bytesAsRgba);
  });

  test('Image.toByteData RGBA format works with transparent image', () async {
    final Image image = await TransparentImage.load();
    final ByteData data = (await image.toByteData())!;
    final Uint8List bytes = data.buffer.asUint8List();
    expect(bytes, hasLength(64));
    expect(bytes, TransparentImage.bytesAsPremultipliedRgba);
  });

  test('Image.toByteData Straight RGBA format works with transparent image', () async {
    final Image image = await TransparentImage.load();
    final ByteData data = (await image.toByteData(format: ImageByteFormat.rawStraightRgba))!;
    final Uint8List bytes = data.buffer.asUint8List();
    expect(bytes, hasLength(64));
    expect(bytes, TransparentImage.bytesAsStraightRgba);
  });

  test('Image.toByteData Unmodified format works with simple image', () async {
    final Image image = await Square4x4Image.image;
    final ByteData data = (await image.toByteData(format: ImageByteFormat.rawUnmodified))!;
    expect(Uint8List.view(data.buffer), Square4x4Image.bytes);
  });

  test('Image.toByteData Unmodified format works with grayscale images', () async {
    final Image image = await GrayscaleImage.load();
    final ByteData data = (await image.toByteData(format: ImageByteFormat.rawUnmodified))!;
    final Uint8List bytes = data.buffer.asUint8List();
    expect(bytes, hasLength(4));
    expect(bytes, GrayscaleImage.bytesUnmodified);
  });

  test('Image.toByteData PNG format works with simple image', () async {
    final Image image = await Square4x4Image.image;
    final ByteData data = (await image.toByteData(format: ImageByteFormat.png))!;
    final List<int> expected = await readFile('square.png');
    expect(Uint8List.view(data.buffer), expected);
  });

  test('Image.toByteData ExtendedRGBA128', () async {
    final Image image = await Square4x4Image.image;
    final ByteData data = (await image.toByteData(format: ImageByteFormat.rawExtendedRgba128))!;
    expect(image.width, _kWidth);
    expect(image.height, _kWidth);
    expect(data.lengthInBytes, _kWidth * _kWidth * 4 * 4);
    // Top-left pixel should be black.
    final Float32List floats = Float32List.view(data.buffer);
    expect(floats[0], 0.0);
    expect(floats[1], 0.0);
    expect(floats[2], 0.0);
    expect(floats[3], 1.0);
    expect(image.colorSpace, ColorSpace.sRGB);
  });
}

class Square4x4Image {
  Square4x4Image._();

  static Future<Image> get image async {
    final double width = _kWidth.toDouble();
    final double radius = _kRadius.toDouble();
    final double innerWidth = (_kWidth - 2 * _kRadius).toDouble();

    final PictureRecorder recorder = PictureRecorder();
    final Canvas canvas =
        Canvas(recorder, Rect.fromLTWH(0.0, 0.0, width, width));

    final Paint black = Paint()
      ..strokeWidth = 1.0
      ..color = _kBlack;
    final Paint green = Paint()
      ..strokeWidth = 1.0
      ..color = _kGreen;

    canvas.drawRect(Rect.fromLTWH(0.0, 0.0, width, width), black);
    canvas.drawRect(
        Rect.fromLTWH(radius, radius, innerWidth, innerWidth), green);
    return recorder.endRecording().toImage(_kWidth, _kWidth);
  }

  static List<int> get bytes {
    const int bytesPerChannel = 4;
    final List<int> result = List<int>.filled(
        _kWidth * _kWidth * bytesPerChannel, 0);

    void fillWithColor(Color color, int min, int max) {
      for (int i = min; i < max; i++) {
        for (int j = min; j < max; j++) {
          final int offset = i * bytesPerChannel + j * _kWidth * bytesPerChannel;
          result[offset] = color.red;
          result[offset + 1] = color.green;
          result[offset + 2] = color.blue;
          result[offset + 3] = color.alpha;
        }
      }
    }

    fillWithColor(_kBlack, 0, _kWidth);
    fillWithColor(_kGreen, _kRadius, _kWidth - _kRadius);

    return result;
  }
}

class GrayscaleImage {
  GrayscaleImage._();

  static Future<Image> load() async {
    final Uint8List bytes = await readFile('2x2.png');
    final Completer<Image> completer = Completer<Image>();
    decodeImageFromList(bytes, (Image image) => completer.complete(image));
    return completer.future;
  }

  static List<int> get bytesAsRgba {
    return <int>[
      255, 255, 255, 255,
      127, 127, 127, 255,
      127, 127, 127, 255,
      0, 0, 0, 255,
    ];
  }

  static List<int> get bytesUnmodified => <int>[255, 127, 127, 0];
}

class TransparentImage {
    TransparentImage._();

  static Future<Image> load() async {
    final Uint8List bytes = await readFile('transparent_image.png');
    final Completer<Image> completer = Completer<Image>();
    decodeImageFromList(bytes, (Image image) => completer.complete(image));
    return completer.future;
  }

  static List<int> get bytesAsPremultipliedRgba {
    return <int>[
      //First raw, solid colors
      255, 0, 0, 255,     // red
      0, 255, 0, 255,     // green
      0, 0, 255, 255,     // blue
      136, 136, 136, 255, // grey

      //Second raw, 50% transparent
      127, 0, 0, 127,     // red
      0, 127, 0, 127,     // green
      0, 0, 127, 127,     // blue
      67, 67, 67, 127,    // grey

      //Third raw, 25% transparent
      63, 0, 0, 63,       // red
      0, 63, 0, 63,       // green
      0, 0, 63, 63,       // blue
      33, 33, 33, 63,     // grey

      //Fourth raw, transparent
      0, 0, 0, 0,         // red
      0, 0, 0, 0,         // green
      0, 0, 0, 0,         // blue
      0, 0, 0, 0,         // grey
    ];
  }

  static List<int> get bytesAsStraightRgba {
    return <int>[
      //First raw, solid colors
      255, 0, 0, 255,     // red
      0, 255, 0, 255,     // green
      0, 0, 255, 255,     // blue
      136, 136, 136, 255, // grey

      //Second raw, 50% transparent
      255, 0, 0, 127,     // red
      0, 255, 0, 127,     // green
      0, 0, 255, 127,     // blue
      135, 135, 135, 127, // grey

      //Third raw, 25% transparent
      255, 0, 0, 63,      // red
      0, 255, 0, 63,      // green
      0, 0, 255, 63,      // blue
      134, 134, 134, 63,  // grey

      //Fourth raw, transparent
      0, 0, 0, 0,         // red
      0, 0, 0, 0,         // green
      0, 0, 0, 0,         // blue
      0, 0, 0, 0,         // grey
    ];
  }
}

Future<Uint8List> readFile(String fileName) async {
  final File file = File(path.join('flutter', 'testing', 'resources', fileName));
  return file.readAsBytes();
}
