package com.phongnguyen93.rn;

import android.content.Context;
import android.media.AudioManager;
import android.os.Handler;
import android.os.Message;
import android.util.AttributeSet;
import android.util.Log;
import com.facebook.react.bridge.LifecycleEventListener;
import com.facebook.react.uimanager.ThemedReactContext;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.ExoPlaybackException;
import com.google.android.exoplayer2.ExoPlayer;
import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.mediacodec.MediaCodecRenderer;
import com.google.android.exoplayer2.mediacodec.MediaCodecUtil;
import com.google.android.exoplayer2.source.BehindLiveWindowException;
import com.google.android.exoplayer2.source.MediaSource;
import com.google.android.exoplayer2.source.MergingMediaSource;
import com.vtvcab.ui.VtvDRMView;
import java.util.ArrayList;

public class DrmPlayerWrapper extends VtvDRMView
    implements LifecycleEventListener, ExoPlayer.EventListener,
    AudioManager.OnAudioFocusChangeListener {

  private static final int SHOW_PROGRESS = 1;

  private int resumeWindow;
  private long resumePosition;
  private boolean isBuffering;
  private boolean loadVideoStarted;
  private boolean playerNeedsSource;
  private boolean isInitialized = false;
  private boolean isPaused;
  private String srcUri;
  private String token;
  private boolean isInBackground;
  private float mProgressUpdateInterval = 250.0f;

  private AudioManager audioManager;
  private VideoEventEmitter eventEmitter;
  private ThemedReactContext themedReactContext;

  private final Handler progressHandler = new Handler() {
    @Override
    public void handleMessage(Message msg) {
      switch (msg.what) {
        case SHOW_PROGRESS:
          if (getCorePlayer() != null
              && getCorePlayer().getPlaybackState() == ExoPlayer.STATE_READY
              && getCorePlayer().getPlayWhenReady()
          ) {
            long pos = getCorePlayer().getCurrentPosition();
            long bufferedDuration = getCorePlayer().getBufferedPercentage() * getCorePlayer().getDuration() / 100;
            eventEmitter.progressChanged(pos, bufferedDuration, getCorePlayer().getDuration());
            msg = obtainMessage(SHOW_PROGRESS);
            sendMessageDelayed(msg, Math.round(mProgressUpdateInterval));
          }
          break;
      }
    }
  };

  public DrmPlayerWrapper(ThemedReactContext context) {
    super(context);
    init(context);
  }

  public DrmPlayerWrapper(ThemedReactContext context, AttributeSet attrs) {
    super(context, attrs);
    init(context);
  }

  public DrmPlayerWrapper(ThemedReactContext context, AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
    init(context);
  }

  public void setSrc(String uri){
    boolean isOriginalSourceNull = srcUri == null;
    boolean isSourceEqual = uri.equals(srcUri);

    this.srcUri = uri;
    if (isOriginalSourceNull || !isSourceEqual) {
      reloadSource();
    }
  }

  public void setToken(String token){
    this.token = token;
   if(isInitialized) refreshToken(this.token);
  }



  public void setPausedModifier(boolean paused) {
    isPaused = paused;
    if (getCorePlayer() != null) {
      if (!paused) {
        startPlayback();
      } else {
        pausePlayback();
      }
    }
  }

  public void setMutedModifier(boolean muted) {
    if (getCorePlayer() != null && getCorePlayer().getAudioComponent()!=null) {
      getCorePlayer().getAudioComponent().setVolume(muted ? 0 : 1);
    }
  }


  public void setVolumeModifier(float volume) {
    if (getCorePlayer() != null && getCorePlayer().getAudioComponent()!=null) {
      getCorePlayer().getAudioComponent().setVolume(volume);
    }
  }

  public void setProgressUpdateInterval(final float progressUpdateInterval) {
    mProgressUpdateInterval = progressUpdateInterval;
  }

  public void setResizeModeModifier(@ResizeMode.Mode int resizeMode) {
   if(getPlayerView()!=null) getPlayerView().setResizeMode(resizeMode);
  }

  public void seekTo(long positionMs) {
    if (getCorePlayer() != null) {
      eventEmitter.seek(getCorePlayer().getCurrentPosition(), positionMs);
      getCorePlayer().seekTo(positionMs);
    }
  }

  public void cleanUpResources() {
    stopPlayback();
  }

  private void init(ThemedReactContext context) {
    this.themedReactContext = context;
    this.eventEmitter = new VideoEventEmitter(context);
    audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
    this.themedReactContext.addLifecycleEventListener(this);
  }

  protected Player getCorePlayer(){
    return getPlayerView().getPlayer();
  }

  private void initializePlayer() {
    if (playerNeedsSource &&srcUri != null && getCorePlayer()!=null) {
      boolean haveResumePosition = resumeWindow != C.INDEX_UNSET;
      if (haveResumePosition) {
        getCorePlayer().seekTo(resumeWindow, resumePosition);
      }

      if(!isInitialized){
        init(this.themedReactContext.getCurrentActivity(),token,srcUri,PORTRAIT);
        getCorePlayer().addListener(this);
        isInitialized = true;
      }else{
        refreshToken(token);
        updateUrl(srcUri);
      }


      playerNeedsSource = false;
      eventEmitter.loadStart();
      loadVideoStarted = true;
    }
  }

  private void reloadSource() {
    playerNeedsSource = true;
    initializePlayer();
  }


  private void updateResumePosition() {
    resumeWindow = getCorePlayer().getCurrentWindowIndex();
    resumePosition = getCorePlayer().isCurrentWindowSeekable() ? Math.max(0, getCorePlayer().getCurrentPosition())
        : C.TIME_UNSET;
  }

  private void clearResumePosition() {
    resumeWindow = C.INDEX_UNSET;
    resumePosition = C.TIME_UNSET;
  }

  private void releasePlayer() {
    if (getCorePlayer() != null) {
      updateResumePosition();
      release();
    }
    progressHandler.removeMessages(SHOW_PROGRESS);
    themedReactContext.removeLifecycleEventListener(this);
  }

  private boolean requestAudioFocus() {
    int result = audioManager.requestAudioFocus(this,
        AudioManager.STREAM_MUSIC,
        AudioManager.AUDIOFOCUS_GAIN);
    return result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED;
  }

  private void setPlayWhenReady(boolean playWhenReady) {
    if (getCorePlayer() == null) {
      return;
    }

    if (playWhenReady) {
      boolean hasAudioFocus = requestAudioFocus();
      if (hasAudioFocus) {
        getCorePlayer().setPlayWhenReady(true);
      }
    } else {
      getCorePlayer().setPlayWhenReady(false);
    }
  }

  private void startPlayback() {
    if (getCorePlayer() != null) {
      switch (getCorePlayer().getPlaybackState()) {
        //case ExoPlayer.STATE_IDLE:
        //case ExoPlayer.STATE_ENDED:
        //  initializePlayer();
        //  break;
        case ExoPlayer.STATE_BUFFERING:
        case ExoPlayer.STATE_READY:
          if (!getCorePlayer().getPlayWhenReady()) {
            setPlayWhenReady(true);
          }
          break;
        default:
          break;
      }

    }
    setKeepScreenOn(true);
  }

  private void pausePlayback() {
    if (getCorePlayer() != null) {
      if (getCorePlayer().getPlayWhenReady()) {
        setPlayWhenReady(false);
      }
    }
    setKeepScreenOn(false);
  }

  private void stopPlayback() {
    onStopPlayback();
    releasePlayer();
  }

  private void onStopPlayback() {
    setKeepScreenOn(false);
    audioManager.abandonAudioFocus(this);
  }

  private void onBuffering(boolean buffering) {
    if (isBuffering == buffering) {
      return;
    }

    isBuffering = buffering;
    if (buffering) {
      eventEmitter.buffering(true);
    } else {
      eventEmitter.buffering(false);
    }
  }

  private void startProgressHandler() {
    progressHandler.sendEmptyMessage(SHOW_PROGRESS);
  }

  private void videoLoaded() {
    if (loadVideoStarted) {
      loadVideoStarted = false;
      eventEmitter.load(getCorePlayer().getDuration(), getCorePlayer().getCurrentPosition());
    }
  }

  private static boolean isBehindLiveWindow(ExoPlaybackException e) {
    if (e.type != ExoPlaybackException.TYPE_SOURCE) {
      return false;
    }
    Throwable cause = e.getSourceException();
    while (cause != null) {
      if (cause instanceof BehindLiveWindowException) {
        return true;
      }
      cause = cause.getCause();
    }
    return false;
  }

  @Override
  public void setId(int id) {
    super.setId(id);
    eventEmitter.setViewId(id);
  }

  @Override public void onHostResume() {
    if (!isInBackground) {
      setPlayWhenReady(!isPaused);
    }
    isInBackground = false;
  }

  @Override public void onHostPause() {
    isInBackground = true;
    setPlayWhenReady(false);
  }

  @Override public void onHostDestroy() {
    this.stopPlayback();
  }

  @Override public void onLoadingChanged(boolean isLoading) {

  }

  @Override public void onPlayerStateChanged(boolean playWhenReady, int playbackState) {
    String text = "onStateChanged: playWhenReady=" + playWhenReady + ", playbackState=";
    switch (playbackState) {
      case ExoPlayer.STATE_IDLE:
        text += "idle";
        eventEmitter.idle();
        break;
      case ExoPlayer.STATE_BUFFERING:
        text += "buffering";
        onBuffering(true);
        break;
      case ExoPlayer.STATE_READY:
        text += "ready";
        eventEmitter.ready();
        onBuffering(false);
        startProgressHandler();
        videoLoaded();
        break;
      case ExoPlayer.STATE_ENDED:
        text += "ended";
        eventEmitter.end();
        onStopPlayback();
        break;
      default:
        text += "unknown";
        break;
    }
    Log.d(TAG, text);
  }

  @Override public void onPlayerError(ExoPlaybackException e) {
    String errorString = null;
    Exception ex = e;
    if (e.type == ExoPlaybackException.TYPE_RENDERER) {
      Exception cause = e.getRendererException();
      if (cause instanceof MediaCodecRenderer.DecoderInitializationException) {
        // Special case for decoder initialization failures.
        MediaCodecRenderer.DecoderInitializationException decoderInitializationException =
            (MediaCodecRenderer.DecoderInitializationException) cause;
        if (decoderInitializationException.decoderName == null) {
          if (decoderInitializationException.getCause() instanceof MediaCodecUtil.DecoderQueryException) {
            errorString = getResources().getString(R.string.error_querying_decoders);
          } else if (decoderInitializationException.secureDecoderRequired) {
            errorString = getResources().getString(R.string.error_no_secure_decoder,
                decoderInitializationException.mimeType);
          } else {
            errorString = getResources().getString(R.string.error_no_decoder,
                decoderInitializationException.mimeType);
          }
        } else {
          errorString = getResources().getString(R.string.error_instantiating_decoder,
              decoderInitializationException.decoderName);
        }
      }
    }
    else if (e.type == ExoPlaybackException.TYPE_SOURCE) {
      ex = e.getSourceException();
      errorString = getResources().getString(R.string.unrecognized_media_format);
    }
    if (errorString != null) {
      eventEmitter.error(errorString, ex);
    }
    playerNeedsSource = true;
    if (isBehindLiveWindow(e)) {
      clearResumePosition();
      initializePlayer();
    } else {
      updateResumePosition();
    }
  }

  @Override
  public void onPositionDiscontinuity(int reason) {
    if (playerNeedsSource) {
      // This will only occur if the user has performed a seek whilst in the error state. Update the
      // resume position so that if the user then retries, playback will resume from the position to
      // which they seeked.
      updateResumePosition();
    }
    // When repeat is turned on, reaching the end of the video will not cause a state change
    // so we need to explicitly detect it.
    if (reason == ExoPlayer.DISCONTINUITY_REASON_PERIOD_TRANSITION
        && getCorePlayer().getRepeatMode() == Player.REPEAT_MODE_ONE) {
      eventEmitter.end();
    }
  }

  @Override public void onAudioFocusChange(int focusChange) {
    switch (focusChange) {
      case AudioManager.AUDIOFOCUS_LOSS:
        eventEmitter.audioFocusChanged(false);
        break;
      case AudioManager.AUDIOFOCUS_GAIN:
        eventEmitter.audioFocusChanged(true);
        break;
      default:
        break;
    }

    if (getCorePlayer() != null && getCorePlayer().getAudioComponent()!=null) {
      if (focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK) {
        // Lower the volume
        getCorePlayer().getAudioComponent().setVolume(0.8f);
      } else if (focusChange == AudioManager.AUDIOFOCUS_GAIN) {
        // Raise it back to normal
        getCorePlayer().getAudioComponent().setVolume(1);
      }
    }
  }
}
