/*
 *  Copyright 2017 The WebRTC project authors. All Rights Reserved.
 *
 *  Use of this source code is governed by a BSD-style license
 *  that can be found in the LICENSE file in the root of the source
 *  tree. An additional intellectual property rights grant can be found
 *  in the file PATENTS.  All contributing project authors may
 *  be found in the AUTHORS file in the root of the source tree.
 */

package tee3.webrtc;

import android.media.MediaCodecInfo;
import android.media.MediaCodecInfo.CodecCapabilities;
import android.media.MediaCodecList;
import android.os.Build;

import java.util.ArrayList;
import java.util.List;

import javax.annotation.Nullable;

/** Factory for Android hardware VideoDecoders. */
@SuppressWarnings("deprecation") // API level 16 requires use of deprecated methods.
public class HardwareVideoDecoderFactory implements VideoDecoderFactory {
  private static final String TAG = "HardwareVideoDecoderFactory";

  private final EglBase.Context sharedContext;
  private final boolean fallbackToSoftware;

  /** Creates a HardwareVideoDecoderFactory that does not use surface textures. */
  @Deprecated // Not removed yet to avoid breaking callers.
  public HardwareVideoDecoderFactory() {
    this(null);
  }

  /**
   * Creates a HardwareVideoDecoderFactory that supports surface texture rendering using the given
   * shared context.  The context may be null.  If it is null, then surface support is disabled.
   */
  public HardwareVideoDecoderFactory(EglBase.Context sharedContext) {
    this(sharedContext, true /* fallbackToSoftware */);
  }

  HardwareVideoDecoderFactory(EglBase.Context sharedContext, boolean fallbackToSoftware) {
    this.sharedContext = sharedContext;
    this.fallbackToSoftware = fallbackToSoftware;
    Logging.d(TAG, "ctor context:"+ sharedContext + " fallbackToSoftware:" + fallbackToSoftware);
  }

  @Nullable
  @Override
  public VideoDecoder createDecoder(String codecType) {
    VideoCodecType type = VideoCodecType.valueOf(codecType);
    MediaCodecInfo info = findCodecForType(type);

    if (info == null) {
      // No hardware support for this type.
      // TODO(andersc): This is for backwards compatibility. Remove when clients have migrated to
      // new DefaultVideoEncoderFactory.
      if (fallbackToSoftware) {
        SoftwareVideoDecoderFactory softwareVideoDecoderFactory = new SoftwareVideoDecoderFactory();
        return softwareVideoDecoderFactory.createDecoder(codecType);
      } else {
        return null;
      }
    }

    CodecCapabilities capabilities = info.getCapabilitiesForType(type.mimeType());
    return new HardwareVideoDecoder(info.getName(), type,
        MediaCodecUtils.selectColorFormat(MediaCodecUtils.DECODER_COLOR_FORMATS, capabilities),
        sharedContext);
  }

  @Override
  public VideoCodecInfo[] getSupportedCodecs() {
    List<VideoCodecInfo> supportedCodecInfos = new ArrayList<VideoCodecInfo>();
    // Generate a list of supported codecs in order of preference:
    // VP8, VP9, H264 (high profile), and H264 (baseline profile).
    for (VideoCodecType type :
        new VideoCodecType[] {VideoCodecType.VP8, VideoCodecType.VP9, VideoCodecType.H264, VideoCodecType.H265}) {
      MediaCodecInfo codec = findCodecForType(type);
      if (codec != null) {
        String name = type.name();
        if (type == VideoCodecType.H264 && isH264HighProfileSupported(codec)) {
          supportedCodecInfos.add(new VideoCodecInfo(
              name, MediaCodecUtils.getCodecProperties(type, /* highProfile= */ true)));
        }

        supportedCodecInfos.add(new VideoCodecInfo(
            name, MediaCodecUtils.getCodecProperties(type, /* highProfile= */ false)));
      }
    }

    // TODO(andersc): This is for backwards compatibility. Remove when clients have migrated to
    // new DefaultVideoEncoderFactory.
    if (fallbackToSoftware) {
      for (VideoCodecInfo info : SoftwareVideoDecoderFactory.supportedCodecs()) {
        if (!supportedCodecInfos.contains(info)) {
          supportedCodecInfos.add(info);
        }
      }
    }

    return supportedCodecInfos.toArray(new VideoCodecInfo[supportedCodecInfos.size()]);
  }

  private static boolean firstscan = true;
  private @Nullable MediaCodecInfo findCodecForType(VideoCodecType type) {
    // HW decoding is not supported on builds before KITKAT.
    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
      return null;
    }

    if (firstscan) {
      firstscan = false;
      Logging.d(TAG, "Support Decoder List:");
      for (int i = 0; i < MediaCodecList.getCodecCount(); ++i) {
        MediaCodecInfo info = null;
        try {
          info = MediaCodecList.getCodecInfoAt(i);
        } catch (IllegalArgumentException e) {
          Logging.e(TAG, "Cannot retrieve decoder codec info", e);
        }
        if (info == null || info.isEncoder()) {
          continue;
        }
        Logging.d(TAG, "  i:" + i + "  "+ info.getName());
      }
    }

    for (int i = 0; i < MediaCodecList.getCodecCount(); ++i) {
      MediaCodecInfo info = null;
      try {
        info = MediaCodecList.getCodecInfoAt(i);
      } catch (IllegalArgumentException e) {
        Logging.e(TAG, "Cannot retrieve encoder codec info", e);
      }

      if (info == null || info.isEncoder()) {
        continue;
      }

      if (isSupportedCodec(info, type)) {
        return info;
      }
    }
    return null; // No support for this type.
  }

  // Returns true if the given MediaCodecInfo indicates a supported encoder for the given type.
  private boolean isSupportedCodec(MediaCodecInfo info, VideoCodecType type) {
    if (!MediaCodecUtils.codecSupportsType(info, type)) {
      return false;
    }
    // Check for a supported color format.
    if (MediaCodecUtils.selectColorFormat(
            MediaCodecUtils.DECODER_COLOR_FORMATS, info.getCapabilitiesForType(type.mimeType()))
        == null) {
      return false;
    }
    return isHardwareSupported(info, type);
  }

  private boolean isHardwareSupported(MediaCodecInfo info, VideoCodecType type) {
    String name = info.getName();
    boolean issupported = false;
    switch (type) {
      case VP8:
        // QCOM, Intel, Exynos, and Nvidia all supported for VP8.
        issupported = name.startsWith(MediaCodecUtils.QCOM_PREFIX) || name.startsWith(MediaCodecUtils.INTEL_PREFIX)
            || name.startsWith(MediaCodecUtils.EXYNOS_PREFIX) || name.startsWith(MediaCodecUtils.NVIDIA_PREFIX);
        break;
      case VP9:
        // QCOM and Exynos supported for VP9.
        issupported = name.startsWith(MediaCodecUtils.QCOM_PREFIX) || name.startsWith(MediaCodecUtils.EXYNOS_PREFIX);
        break;
      case H264:
        // QCOM, Intel, and Exynos supported for H264.
        issupported = name.startsWith(MediaCodecUtils.QCOM_PREFIX) || name.startsWith(MediaCodecUtils.INTEL_PREFIX)
            || name.startsWith(MediaCodecUtils.EXYNOS_PREFIX) || MediaCodecUtils.isInAVD_PREFIXS(info);
        break;
      case H265:
        // QCOM, Intel, and Exynos supported for H264.
        issupported = name.startsWith(MediaCodecUtils.QCOM_PREFIX) || name.startsWith(MediaCodecUtils.INTEL_PREFIX)
                || name.startsWith(MediaCodecUtils.EXYNOS_PREFIX) || MediaCodecUtils.isInAVD_PREFIXS(info);
        break;
      default:
        break;
    }
    Logging.d(TAG, "isHardwareSupported type:" + type +" supported:" +issupported+" info:" +info.getName());
    return issupported;
  }

  private boolean isH264HighProfileSupported(MediaCodecInfo info) {
    String name = info.getName();
    // Support H.264 HP decoding on QCOM chips for Android L and above.
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && name.startsWith(MediaCodecUtils.QCOM_PREFIX)) {
      return true;
    }
    // Support H.264 HP decoding on Exynos chips for Android M and above.
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && name.startsWith(MediaCodecUtils.EXYNOS_PREFIX)) {
      return true;
    }
    return false;
  }
}
