package com.castlabs.reactnative.utils;

import android.annotation.SuppressLint;
import android.os.Build;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import com.castlabs.android.drm.Drm;
import com.castlabs.android.player.DashDescriptor;
import com.castlabs.android.player.PlayerController.State;
import com.castlabs.android.player.models.AudioTrack;
import com.castlabs.android.player.models.SubtitleTrack;
import com.castlabs.android.player.models.Track;
import com.castlabs.android.player.models.VideoTrack;
import com.castlabs.android.player.models.VideoTrackQuality;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.WritableArray;
import com.facebook.react.bridge.WritableMap;
import java.util.Base64;
import java.util.List;
import java.util.Map;
import javax.annotation.Nonnull;

/**
 * Serialises player types into a JSON representation.
 */
public class BridgeSerializer {

  /**
   * Serializes a player state to a string.
   *
   * @param state the player state
   * @return the string representation of the player state
   * @throws IllegalArgumentException if an unknown player state
   */
  public static String toState(State state) throws IllegalArgumentException {
    switch (state) {
      case Idle:
        return "Idle";
      case Preparing:
        return "Opening";
      case Buffering:
        return "Buffering";
      case Playing:
        return "Playing";
      case Pausing:
        return "Pausing";
      case Finished:
        return "Ended";
      default:
        throw new IllegalArgumentException("Unknown state");
    }
  }

  /**
   * Maps the data to the JavaScript video tracks.
   *
   * @param videoTracks the video tracks
   * @param currentVideoTrack the selected video track
   * @param currentVideoQuality the selected video quality
   * @return the JavaScript video tracks
   */
  public static WritableArray toVideoTracks(
      @NonNull List<VideoTrack> videoTracks,
      @Nullable VideoTrack currentVideoTrack,
      @Nullable VideoTrackQuality currentVideoQuality
  ) {
    WritableArray results = Arguments.createArray();

    for (int i = 0; i < videoTracks.size(); i++) {
      VideoTrack videoTrack = videoTracks.get(i);

      WritableMap result = Arguments.createMap();
      result.putString("id", String.valueOf(i));
      result.putBoolean("active", videoTrack == currentVideoTrack);
      result.putArray("renditions", BridgeSerializer.toVideoRenditions(
          i,
          videoTrack.getQualities(),
          currentVideoQuality
      ));

      results.pushMap(result);
    }

    return results;
  }

  /**
   * Maps video renditions from Android to JavaScript structure.
   *
   * @param videoTrackIndex the parent video track index
   * @param videoTrackQualities the video qualities
   * @param currentVideoQuality the select video quality
   * @return the JavaScript video renditions
   */
  public static WritableArray toVideoRenditions(
      int videoTrackIndex,
      @NonNull List<VideoTrackQuality> videoTrackQualities,
      @Nullable VideoTrackQuality currentVideoQuality
  ) {
    WritableArray results = Arguments.createArray();

    for (VideoTrackQuality videoTrackQuality : videoTrackQualities) {
      StringBuilder idBuilder = new StringBuilder();
      idBuilder.append(videoTrackIndex);
      idBuilder.append("-");
      idBuilder.append(videoTrackQualities.indexOf(videoTrackQuality));

      WritableMap result = Arguments.createMap();
      result.putString("id", idBuilder.toString());
      result.putBoolean("active", videoTrackQuality == currentVideoQuality);
      result.putInt("width", videoTrackQuality.getWidth());
      result.putInt("height", videoTrackQuality.getHeight());
      result.putInt("bitrate", videoTrackQuality.getBitrate());
      result.putString( "codec", videoTrackQuality.getCodecs());

      results.pushMap(result);
    }

    return results;
  }

  /**
   * Maps audio tracks from Android to JavaScript structure.
   *
   * @param audioTracks the audio tracks
   * @param currentAudioTrack the selected audio track
   * @return the JavaScript audio tracks
   */
  public static WritableArray toAudioTracks(
      final @NonNull List<AudioTrack> audioTracks,
      final @Nullable AudioTrack currentAudioTrack
  ) {
    final WritableArray results = Arguments.createArray();

    for (int i = 0; i < audioTracks.size(); i++) {
      AudioTrack audioTrack = audioTracks.get(i);

      WritableMap result = Arguments.createMap();
      result.putString("id", String.valueOf(i));
      result.putBoolean("active", audioTrack == currentAudioTrack);
      result.putString("language", audioTrack.getLanguage());
      result.putString("label", audioTrack.getLabel());
      int bitrate = audioTrack.getBitrate();
      if (bitrate > -1) {
        result.putInt("bitrate", bitrate);
      }

      result.putString("codecs", audioTrack.getCodecs());
      result.putString("mimeType", audioTrack.getMimeType());
      result.putMap("descriptors", toDescriptors(audioTrack));

      results.pushMap(result);
    }

    return results;
  }

  /**
   * Map subtitle tracks from Android to JavaScript structure.
   *
   * @param subtitleTracks the subtitle tracks
   * @param currentSubtitleTrack the select subtitle track
   * @return the JavaScript subtitle tracks
   */
  public static WritableArray toTextTracks(
      final @NonNull List<SubtitleTrack> subtitleTracks,
      final @Nullable SubtitleTrack currentSubtitleTrack
  ) {
    final WritableArray results = Arguments.createArray();

    for (int i = 0; i < subtitleTracks.size(); i++) {
      SubtitleTrack subtitleTrack = subtitleTracks.get(i);

      WritableMap result = Arguments.createMap();
      result.putString("id", String.valueOf(i));
      result.putBoolean("active", subtitleTrack == currentSubtitleTrack);
      result.putString("language", subtitleTrack.getLanguage());
      result.putString("label", subtitleTrack.getLabel());
      result.putString("mimeType", subtitleTrack.getMimeType());

      results.pushMap(result);
    }

    return results;
  }

  /**
   * Encodes the array of bytes to the base64 string.
   *
   * @param bytes the array of bytes
   * @return the base64 string
   */
  @RequiresApi(api = Build.VERSION_CODES.O)
  public static String toBase64(final @Nullable byte[] bytes) {
    // return empty byte array if null
    if (bytes == null) {
      return "";
    }
    return Base64.getEncoder().encodeToString(bytes);
  }

  /**
   * Maps request headers from Android to JavaScript structure.
   *
   * @param headers the Android request headers
   * @return the JavaScript request headers
   */
  public static ReadableMap toRequestHeaders(
      final @Nonnull Map<String, String> headers
  ) {
    final WritableMap jsonHeaders = Arguments.createMap();

    for (String key : headers.keySet()) {
      jsonHeaders.putString(key, headers.get(key));
    }

    return jsonHeaders;
  }

  /**
   * Maps response headers from Android to JavaScript structure.
   *
   * @param headers the Android response headers
   * @return the JavaScript response headers
   */
  public static ReadableMap toResponseHeaders(
      final @Nonnull Map<String, List<String>> headers
  ) {
    final WritableMap jsonHeaders = Arguments.createMap();

    for (String key : headers.keySet()) {
      jsonHeaders.putString(key, String.join(",", headers.get(key)));
    }

    return jsonHeaders;
  }

  /**
   * Maps a key system from Android to JavaScript type.
   *
   * @param drm the Android key system
   * @return the JavaScript key system
   */
  public static String toDrm(
      final @NonNull Drm drm
  ) {
    switch (drm) {
      case Playready:
        return "playready";
      case Widevine:
        return "widevine";
      default:
        throw new IllegalStateException("Unknown DRM");
    }
  }

  @SuppressLint("SwitchIntDef")
  public static WritableMap toDescriptors(Track track) {
    WritableMap descriptors = Arguments.createMap();

    if (track == null || track.getDescriptors() == null) {
      return descriptors;
    }

    for (DashDescriptor src : track.getDescriptors()) {
      String type = null;
      switch (src.getType()) {
        case DashDescriptor.ROLE -> type = "dashRole";
        case DashDescriptor.ACCESSIBILITY -> type = "dashAccessibility";
      }

      if (type != null) {
        WritableMap descriptor = Arguments.createMap();

        descriptor.putString("schemaIdUri", src.getSchemeIdUri());

        if (src.getId() != null) {
          descriptor.putString("id", src.getId());
        }
        if (src.getValue() != null) {
          descriptor.putString("value", src.getValue());
        }

        descriptors.putMap(type, descriptor);
      }
    }

    return descriptors;
  }
}
