// Copyright 2019 Google Inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at:
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

import TensorFlowLiteCMetal

/// A delegate that uses the `Metal` framework for performing TensorFlow Lite graph operations with
/// GPU acceleration.
///
/// - Important: This is an experimental interface that is subject to change.
public final class MetalDelegate: Delegate {
  /// The configuration options for the `MetalDelegate`.
  public let options: Options

  // Conformance to the `Delegate` protocol.
  public private(set) var cDelegate: CDelegate

  /// Creates a new instance configured with the given `options`.
  ///
  /// - Parameters:
  ///   - options: Configurations for the delegate. The default is a new instance of
  ///       `MetalDelegate.Options` with the default configuration values.
  public init(options: Options = Options()) {
    self.options = options
    var delegateOptions = TFLGpuDelegateOptions()
    delegateOptions.allow_precision_loss = options.isPrecisionLossAllowed
    delegateOptions.wait_type = options.waitType.cWaitType
    delegateOptions.enable_quantization = options.isQuantizationEnabled
    cDelegate = TFLGpuDelegateCreate(&delegateOptions)
  }

  deinit {
    TFLGpuDelegateDelete(cDelegate)
  }
}

extension MetalDelegate {
  /// Options for configuring the `MetalDelegate`.
  public struct Options: Equatable, Hashable {
    /// Indicates whether the GPU delegate allows precision loss, such as allowing `Float16`
    /// precision for a `Float32` computation. The default is `false`.
    public var isPrecisionLossAllowed = false

    @available(*, deprecated: 2.4, renamed: "isPrecisionLossAllowed")
    public var allowsPrecisionLoss: Bool {
      get { return isPrecisionLossAllowed }
      set(value) { isPrecisionLossAllowed = value }
    }

    /// A type indicating how the current thread should wait for work on the GPU to complete. The
    /// default is `passive`.
    public var waitType: ThreadWaitType = .passive

    /// Indicates whether the GPU delegate allows execution of an 8-bit quantized model. The default
    /// is `true`.
    public var isQuantizationEnabled = true

    /// Creates a new instance with the default values.
    public init() {}
  }
}

/// A type indicating how the current thread should wait for work scheduled on the GPU to complete.
public enum ThreadWaitType: Equatable, Hashable {
  /// The thread does not wait for the work to complete. Useful when the output of the work is used
  /// with the GPU pipeline.
  case none
  /// The thread waits until the work is complete.
  case passive
  /// The thread waits for the work to complete with minimal latency, which may require additional
  /// CPU resources.
  case active
  /// The thread waits for the work while trying to prevent the GPU from going into sleep mode.
  case aggressive

  /// The C `TFLGpuDelegateWaitType` for the current `ThreadWaitType`.
  var cWaitType: TFLGpuDelegateWaitType {
    switch self {
    case .none:
      return TFLGpuDelegateWaitTypeDoNotWait
    case .passive:
      return TFLGpuDelegateWaitTypePassive
    case .active:
      return TFLGpuDelegateWaitTypeActive
    case .aggressive:
      return TFLGpuDelegateWaitTypeAggressive
    }
  }
}
