package org.thoughtcrime.securesms.mms;

import android.content.Context;

import androidx.annotation.IntRange;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import org.signal.core.util.Util;
import org.signal.mediasend.MediaConstraints;
import org.signal.mediasend.SentMediaQuality;
import org.thoughtcrime.securesms.dependencies.AppDependencies;
import org.thoughtcrime.securesms.jobs.AttachmentUploadJob;
import org.thoughtcrime.securesms.util.LocaleRemoteConfig;
import org.thoughtcrime.securesms.util.RemoteConfig;
import org.thoughtcrime.securesms.video.TranscodingConfig;
import org.whispersystems.signalservice.api.crypto.AttachmentCipherStreamUtil;

import java.util.Arrays;
import java.util.List;

public class PushMediaConstraints extends MediaConstraints {

  private static final int KB = 1024;
  private static final int MB = 1024 * KB;

  private final MediaConfig      currentConfig;
  private final SentMediaQuality sentMediaQuality;

  public PushMediaConstraints(@Nullable SentMediaQuality sentMediaQuality) {
    this.currentConfig    = getCurrentConfig(AppDependencies.getApplication(), sentMediaQuality);
    this.sentMediaQuality = sentMediaQuality;
  }

  @Override
  public int getImageMaxWidth() {
    return currentConfig.imageSizeTargets[0];
  }

  @Override
  public int getImageMaxHeight() {
    return getImageMaxWidth();
  }

  @Override
  public int getImageMaxSize() {
    return (int) Math.min(currentConfig.maxImageFileSize, getMaxAttachmentSize());
  }

  @Override
  public int[] getImageDimensionTargets() {
    return currentConfig.imageSizeTargets;
  }

  @Override
  public long getGifMaxSize() {
    return Math.min(25 * MB, getMaxAttachmentSize());
  }

  @Override
  public long getVideoMaxSize() {
    return getMaxAttachmentSize();
  }

  @Override
  public long getUncompressedVideoMaxSize() {
    return isVideoTranscodeAvailable() ? RemoteConfig.maxSourceTranscodeVideoSizeBytes()
                                       : getVideoMaxSize();
  }

  @Override
  public long getCompressedVideoMaxSize() {
    long maxCompressedSize = AttachmentCipherStreamUtil.getMaxPlaintextSizeForCiphertext(RemoteConfig.videoTranscodeTargetSizeBytes());

    return Math.min(maxCompressedSize, getMaxAttachmentSize());
  }

  @Override
  public long getAudioMaxSize() {
    return getMaxAttachmentSize();
  }

  @Override
  public long getDocumentMaxSize() {
    return getMaxAttachmentSize();
  }

  @Override
  public long getMaxAttachmentSize() {
    return AttachmentUploadJob.getMaxPlaintextSize();
  }

  @Override
  public int getImageCompressionQualitySetting() {
    return currentConfig.qualitySetting;
  }

  @Override
  public List<TranscodingConfig.QualityTier> getVideoTranscodingSettings() {
   return sentMediaQuality == SentMediaQuality.HIGH ? currentConfig.videoConfigs.getHigh() : currentConfig.videoConfigs.getStandard();
  }

  private static @NonNull MediaConfig getCurrentConfig(@NonNull Context context, @Nullable SentMediaQuality sentMediaQuality) {
    if (Util.isLowMemory(context)) {
      return MediaConfig.LEVEL_1_LOW_MEMORY;
    }

    if (sentMediaQuality == SentMediaQuality.HIGH) {
      return MediaConfig.LEVEL_3;
    }
    return LocaleRemoteConfig.getMediaQualityLevel().orElse(MediaConfig.getDefault(context));
  }

  public enum MediaConfig {
    LEVEL_1_LOW_MEMORY(true, 1, MB, new int[] { 768, 512 }, 70),

    LEVEL_1(false, 1, MB, new int[] { 1600, 1024, 768, 512 }, 70),
    LEVEL_2(false, 2, (int) (1.5 * MB), new int[] { 2048, 1600, 1024, 768, 512 }, 75),
    LEVEL_3(false, 3, (int) (3 * MB), new int[] { 4096, 3072, 2048, 1600, 1024, 768, 512 }, 75);

    private final boolean                           isLowMemory;
    private final int                               level;
    private final int                               maxImageFileSize;
    private final int[]                             imageSizeTargets;
    private final int                               qualitySetting;
    private final TranscodingConfig.TranscodeConfig videoConfigs;

    MediaConfig(boolean isLowMemory,
                int level,
                int maxImageFileSize,
                @NonNull int[] imageSizeTargets,
                @IntRange(from = 0, to = 100) int qualitySetting)
    {
      this.isLowMemory      = isLowMemory;
      this.level            = level;
      this.maxImageFileSize = maxImageFileSize;
      this.imageSizeTargets = imageSizeTargets;
      this.qualitySetting   = qualitySetting;
      this.videoConfigs     = TranscodingConfigProvider.getAllConfigs();
    }

    public int getMaxImageFileSize() {
      return maxImageFileSize;
    }

    public int[] getImageSizeTargets() {
      return imageSizeTargets;
    }

    public int getImageQualitySetting() {
      return qualitySetting;
    }

    public static @Nullable MediaConfig forLevel(int level) {
      boolean isLowMemory = Util.isLowMemory(AppDependencies.getApplication());

      return Arrays.stream(values())
                   .filter(v -> v.level == level && v.isLowMemory == isLowMemory)
                   .findFirst()
                   .orElse(null);
    }

    public static @NonNull MediaConfig getDefault(Context context) {
      return Util.isLowMemory(context) ? LEVEL_1_LOW_MEMORY : LEVEL_1;
    }
  }
}
