// 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 'package:camera_platform_interface/camera_platform_interface.dart';
import 'package:flutter/foundation.dart';

// TODO(stuartmorgan): Remove all of these classes in a breaking change, and
// vend the platform interface versions directly. See
// https://github.com/flutter/flutter/issues/104188

/// A single color plane of image data.
///
/// The number and meaning of the planes in an image are determined by the
/// format of the Image.
class Plane {
  Plane._fromPlatformInterface(CameraImagePlane plane)
      : bytes = plane.bytes,
        bytesPerPixel = plane.bytesPerPixel,
        bytesPerRow = plane.bytesPerRow,
        height = plane.height,
        width = plane.width;

  // Only used by the deprecated codepath that's kept to avoid breaking changes.
  // Never called by the plugin itself.
  Plane._fromPlatformData(Map<dynamic, dynamic> data)
      : bytes = data['bytes'] as Uint8List,
        bytesPerPixel = data['bytesPerPixel'] as int?,
        bytesPerRow = data['bytesPerRow'] as int,
        height = data['height'] as int?,
        width = data['width'] as int?;

  /// Bytes representing this plane.
  final Uint8List bytes;

  /// The distance between adjacent pixel samples on Android, in bytes.
  ///
  /// Will be `null` on iOS.
  final int? bytesPerPixel;

  /// The row stride for this color plane, in bytes.
  final int bytesPerRow;

  /// Height of the pixel buffer on iOS.
  ///
  /// Will be `null` on Android
  final int? height;

  /// Width of the pixel buffer on iOS.
  ///
  /// Will be `null` on Android.
  final int? width;
}

/// Describes how pixels are represented in an image.
class ImageFormat {
  ImageFormat._fromPlatformInterface(CameraImageFormat format)
      : group = format.group,
        raw = format.raw;

  // Only used by the deprecated codepath that's kept to avoid breaking changes.
  // Never called by the plugin itself.
  ImageFormat._fromPlatformData(this.raw) : group = _asImageFormatGroup(raw);

  /// Describes the format group the raw image format falls into.
  final ImageFormatGroup group;

  /// Raw version of the format from the Android or iOS platform.
  ///
  /// On Android, this is an `int` from class `android.graphics.ImageFormat`. See
  /// https://developer.android.com/reference/android/graphics/ImageFormat
  ///
  /// On iOS, this is a `FourCharCode` constant from Pixel Format Identifiers.
  /// See https://developer.apple.com/documentation/corevideo/1563591-pixel_format_identifiers?language=objc
  final dynamic raw;
}

// Only used by the deprecated codepath that's kept to avoid breaking changes.
// Never called by the plugin itself.
ImageFormatGroup _asImageFormatGroup(dynamic rawFormat) {
  if (defaultTargetPlatform == TargetPlatform.android) {
    switch (rawFormat) {
      // android.graphics.ImageFormat.YUV_420_888
      case 35:
        return ImageFormatGroup.yuv420;
      // android.graphics.ImageFormat.JPEG
      case 256:
        return ImageFormatGroup.jpeg;
      // android.graphics.ImageFormat.NV21
      case 17:
        return ImageFormatGroup.nv21;
    }
  }

  if (defaultTargetPlatform == TargetPlatform.iOS) {
    switch (rawFormat) {
      // kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange
      case 875704438:
        return ImageFormatGroup.yuv420;
      // kCVPixelFormatType_32BGRA
      case 1111970369:
        return ImageFormatGroup.bgra8888;
    }
  }

  return ImageFormatGroup.unknown;
}

/// A single complete image buffer from the platform camera.
///
/// This class allows for direct application access to the pixel data of an
/// Image through one or more [Uint8List]. Each buffer is encapsulated in a
/// [Plane] that describes the layout of the pixel data in that plane. The
/// [CameraImage] is not directly usable as a UI resource.
///
/// Although not all image formats are planar on iOS, we treat 1-dimensional
/// images as single planar images.
class CameraImage {
  /// Creates a [CameraImage] from the platform interface version.
  CameraImage.fromPlatformInterface(CameraImageData data)
      : format = ImageFormat._fromPlatformInterface(data.format),
        height = data.height,
        width = data.width,
        planes = List<Plane>.unmodifiable(data.planes.map<Plane>(
            (CameraImagePlane plane) => Plane._fromPlatformInterface(plane))),
        lensAperture = data.lensAperture,
        sensorExposureTime = data.sensorExposureTime,
        sensorSensitivity = data.sensorSensitivity;

  /// Creates a [CameraImage] from method channel data.
  @Deprecated('Use fromPlatformInterface instead')
  CameraImage.fromPlatformData(Map<dynamic, dynamic> data)
      : format = ImageFormat._fromPlatformData(data['format']),
        height = data['height'] as int,
        width = data['width'] as int,
        lensAperture = data['lensAperture'] as double?,
        sensorExposureTime = data['sensorExposureTime'] as int?,
        sensorSensitivity = data['sensorSensitivity'] as double?,
        planes = List<Plane>.unmodifiable((data['planes'] as List<dynamic>)
            .map<Plane>((dynamic planeData) =>
                Plane._fromPlatformData(planeData as Map<dynamic, dynamic>)));

  /// Format of the image provided.
  ///
  /// Determines the number of planes needed to represent the image, and
  /// the general layout of the pixel data in each [Uint8List].
  final ImageFormat format;

  /// Height of the image in pixels.
  ///
  /// For formats where some color channels are subsampled, this is the height
  /// of the largest-resolution plane.
  final int height;

  /// Width of the image in pixels.
  ///
  /// For formats where some color channels are subsampled, this is the width
  /// of the largest-resolution plane.
  final int width;

  /// The pixels planes for this image.
  ///
  /// The number of planes is determined by the format of the image.
  final List<Plane> planes;

  /// The aperture settings for this image.
  ///
  /// Represented as an f-stop value.
  final double? lensAperture;

  /// The sensor exposure time for this image in nanoseconds.
  final int? sensorExposureTime;

  /// The sensor sensitivity in standard ISO arithmetic units.
  final double? sensorSensitivity;
}
