package com.tytv.twiliovideo;

import android.Manifest;
import android.app.Activity;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.media.AudioAttributes;
import android.media.AudioFocusRequest;
import android.media.AudioManager;
import android.os.Build;
import android.widget.Toast;

import androidx.annotation.NonNull;
import androidx.core.content.ContextCompat;

import com.tytv.twiliovideo.utils.CameraCapturerCompat;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.Callback;
import com.facebook.react.bridge.PromiseImpl;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.bridge.WritableArray;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.bridge.WritableNativeArray;
import com.facebook.react.bridge.WritableNativeMap;
import com.facebook.react.modules.permissions.PermissionsModule;
import com.facebook.react.uimanager.ThemedReactContext;
import com.facebook.react.views.view.ReactViewGroup;
import com.twilio.video.AudioCodec;
import com.twilio.video.CameraCapturer;
import com.twilio.video.ConnectOptions;
import com.twilio.video.EncodingParameters;
import com.twilio.video.IsacCodec;
import com.twilio.video.LocalAudioTrack;
import com.twilio.video.LocalParticipant;
import com.twilio.video.LocalVideoTrack;
import com.twilio.video.RemoteParticipant;
import com.twilio.video.Room;
import com.twilio.video.TwilioException;
import com.twilio.video.Video;
import com.twilio.video.VideoCodec;
import com.twilio.video.Vp8Codec;
import java.util.Collections;
import java.util.List;

import javax.annotation.Nonnull;

public class TwilioRoomView extends ReactViewGroup {
    private static final int CAMERA_MIC_PERMISSION_REQUEST_CODE = 1;

    /*
     * Audio and video tracks can be created with names. This feature is useful for categorizing
     * tracks of participants. For example, if one participant publishes a video track with
     * ScreenCapturer and CameraCapturer with the names "screen" and "camera" respectively then
     * other participants can use RemoteVideoTrack#getName to determine which video track is
     * produced from the other participant's screen or camera.
     */
    private static final String LOCAL_AUDIO_TRACK_NAME = "mic";
    private static final String LOCAL_VIDEO_TRACK_NAME = "camera";

    /*
     * You must provide a Twilio Access Token to connect to the Video service
     */
//    private static final String TWILIO_ACCESS_TOKEN = BuildConfig.TWILIO_ACCESS_TOKEN;
//    private static final String ACCESS_TOKEN_SERVER = BuildConfig.TWILIO_ACCESS_TOKEN_SERVER;

    /*
     * Access token used to connect. This field will be set either from the console generated token
     * or the request to the token server.
     */
    private String accessToken;


    private LocalAudioTrack localAudioTrack;
    private LocalVideoTrack localVideoTrack;
    private CameraCapturerCompat cameraCapturerCompat;

    private Preview previewView;
    /*
     * A Room represents communication between a local participant and one or more participants.
     */
    Room room;
    LocalParticipant localParticipant;
    List<RemoteParticipant> participants;
    /*
     * AudioCodec and VideoCodec represent the preferred codec for encoding and decoding audio and
     * video.
     */
    private AudioCodec audioCodec;
    private VideoCodec videoCodec;

    /*
     * Encoding parameters represent the sender side bandwidth constraints.
     */
    private EncodingParameters encodingParameters;


    /*
     * Android shared preferences used for settings
     */
    private SharedPreferences preferences;

    private AudioManager audioManager;

    TwilioRoomViewManager  manager;


    private int previousAudioMode;
    private boolean previousMicrophoneMute;

    private boolean disconnectedFromOnDestroy = true;
    private boolean isSpeakerPhoneEnabled = true;
    private boolean enableAutomaticSubscription = false;

    private boolean shareVideo = true;
    private boolean shareAudio = true;

    private ThemedReactContext reactContext;
    private PermissionsModule reactPerms;

    public TwilioRoomView(Context context, TwilioRoomViewManager manager){
        super(context);
        this.manager = manager;
        reactContext = (ThemedReactContext) context;
        reactPerms = reactContext.getNativeModule(PermissionsModule.class);
        Activity activity = reactContext.getCurrentActivity();
        activity.setVolumeControlStream(AudioManager.STREAM_VOICE_CALL);
        audioManager = (AudioManager) activity.getSystemService(Context.AUDIO_SERVICE);
        audioManager.setSpeakerphoneOn(isSpeakerPhoneEnabled);
        /*
         * Check camera and microphone permissions. Needed in Android M.
         */
        if (!checkPermissionForCameraAndMicrophone()) {
            requestPermissionForCameraAndMicrophone();
        } else {
            createAudioAndVideoTracks();

        }
    }

    private boolean checkPermissionForCameraAndMicrophone() {
        Activity activity = reactContext.getCurrentActivity();
        int resultCamera = ContextCompat.checkSelfPermission(activity, Manifest.permission.CAMERA);
        int resultMic = ContextCompat.checkSelfPermission(activity, Manifest.permission.RECORD_AUDIO);
        return resultCamera == PackageManager.PERMISSION_GRANTED &&
                resultMic == PackageManager.PERMISSION_GRANTED;
    }

    private void requestPermissionForCameraAndMicrophone() {
        final Callback onPermissionGranted = new Callback() {
            @Override
            public void invoke(Object... args) {
                WritableNativeMap result = (WritableNativeMap) args[0];
                String cameraGranted = result.getString(Manifest.permission.CAMERA);
                String recordAudioGranted = result.getString(Manifest.permission.RECORD_AUDIO);
                if (recordAudioGranted.compareTo("granted") == 0 && cameraGranted.compareTo("granted") == 0) {
                    createAudioAndVideoTracks();
                } else {

                }
            }
        };

        final Callback onPermissionDenied = new Callback() {
            @Override
            public void invoke(Object... args) {
                Toast.makeText(getContext(), "deny", Toast.LENGTH_LONG);
            }
        };

        ReadableArray readableArray = Arguments.createArray();
        ((WritableArray) readableArray).pushString(Manifest.permission.CAMERA);
        ((WritableArray) readableArray).pushString(Manifest.permission.RECORD_AUDIO);
        reactPerms.requestMultiplePermissions(readableArray, new PromiseImpl(onPermissionGranted, onPermissionDenied));

    }
    public void onRequestPermissionsResult(int requestCode,
                                           @NonNull String[] permissions,
                                           @NonNull int[] grantResults) {
        if (requestCode == CAMERA_MIC_PERMISSION_REQUEST_CODE) {
            boolean cameraAndMicPermissionGranted = true;

            for (int grantResult : grantResults) {
                cameraAndMicPermissionGranted &= grantResult == PackageManager.PERMISSION_GRANTED;
            }

            if (cameraAndMicPermissionGranted) {
                createAudioAndVideoTracks();

            } else {
                Context context = getContext();
                Toast.makeText(context, "test", Toast.LENGTH_LONG);
            }
        }
    }

    private void createAudioAndVideoTracks() {
        // Share your microphone
        localAudioTrack = LocalAudioTrack.create(getContext(), true, LOCAL_AUDIO_TRACK_NAME);

        // Share your camera
        cameraCapturerCompat = new CameraCapturerCompat(getContext(), getAvailableCameraSource());
        localVideoTrack = LocalVideoTrack.create(getContext(),
                true,
                cameraCapturerCompat.getVideoCapturer(),
                LOCAL_VIDEO_TRACK_NAME);
        if(previewView != null){
            previewView.addRenderer(localVideoTrack);
        }
    }

    private CameraCapturer.CameraSource getAvailableCameraSource() {
        return (CameraCapturer.isSourceAvailable(CameraCapturer.CameraSource.FRONT_CAMERA)) ?
                (CameraCapturer.CameraSource.FRONT_CAMERA) :
                (CameraCapturer.CameraSource.BACK_CAMERA);
    }
    public void connectToRoom(String accessToken, String roomName) {
        this.accessToken = accessToken;
        configureAudio(true);
        ConnectOptions.Builder connectOptionsBuilder = new ConnectOptions.Builder(accessToken)
                .roomName(roomName);

        /*
         * Add local audio track to connect options to share with participants.
         */
        if (localAudioTrack != null) {
            connectOptionsBuilder
                    .audioTracks(Collections.singletonList(localAudioTrack));
        }

        /*
         * Add local video track to connect options to share with participants.
         */
        if (localVideoTrack != null) {
            connectOptionsBuilder.videoTracks(Collections.singletonList(localVideoTrack));
        }

        /*
         * Set the preferred audio and video codec for media.
         */
        connectOptionsBuilder.preferAudioCodecs(Collections.singletonList(new IsacCodec()));
        connectOptionsBuilder.preferVideoCodecs(Collections.singletonList(new Vp8Codec()));

        /*
         * Set the sender side encoding parameters.
         */
        connectOptionsBuilder.encodingParameters(encodingParameters);

        /*
         * Toggles automatic track subscription. If set to false, the LocalParticipant will receive
         * notifications of track publish events, but will not automatically subscribe to them. If
         * set to true, the LocalParticipant will automatically subscribe to tracks as they are
         * published. If unset, the default is true. Note: This feature is only available for Group
         * Rooms. Toggling the flag in a P2P room does not modify subscription behavior.
         */
        connectOptionsBuilder.enableAutomaticSubscription(enableAutomaticSubscription);

        room = Video.connect(getContext(), connectOptionsBuilder.build(), roomListener());
    }


    private void configureAudio(boolean enable) {
        if (enable) {
            previousAudioMode = audioManager.getMode();
            // Request audio focus before making any device switch
            requestAudioFocus();
            /*
             * Use MODE_IN_COMMUNICATION as the default audio mode. It is required
             * to be in this mode when playout and/or recording starts for the best
             * possible VoIP performance. Some devices have difficulties with
             * speaker mode if this is not set.
             */
            audioManager.setMode(AudioManager.MODE_IN_COMMUNICATION);
            /*
             * Always disable microphone mute during a WebRTC call.
             */
            previousMicrophoneMute = audioManager.isMicrophoneMute();
            audioManager.setMicrophoneMute(false);
        } else {
            audioManager.setMode(previousAudioMode);
            audioManager.abandonAudioFocus(null);
            audioManager.setMicrophoneMute(previousMicrophoneMute);
        }
    }

    private void requestAudioFocus() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            AudioAttributes playbackAttributes = new AudioAttributes.Builder()
                    .setUsage(AudioAttributes.USAGE_VOICE_COMMUNICATION)
                    .setContentType(AudioAttributes.CONTENT_TYPE_SPEECH)
                    .build();
            AudioFocusRequest focusRequest =
                    new AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT)
                            .setAudioAttributes(playbackAttributes)
                            .setAcceptsDelayedFocusGain(true)
                            .setOnAudioFocusChangeListener(
                                    i -> {
                                    })
                            .build();
            audioManager.requestAudioFocus(focusRequest);
        } else {
            audioManager.requestAudioFocus(null, AudioManager.STREAM_VOICE_CALL,
                    AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
        }
    }


    /**
     *
     * @return
     */
    public void setupPreview(Preview previewView){
        this.previewView = previewView;
        if(localVideoTrack != null) {
            previewView.addRenderer(localVideoTrack);
        }
    }

    public void disconnect(){
        room.disconnect();
    }

    public void setShareVideo(boolean shareVideo) {
        this.shareVideo = shareVideo;
        if(localVideoTrack != null ) {
            localVideoTrack.enable(this.shareVideo);
        }
    }

    public void setShareAudio(boolean shareAudio) {
        this.shareAudio = shareAudio;
        if(localAudioTrack != null ) {
            localAudioTrack.enable(this.shareAudio);
        }
    }

    public void flipCamera(){
        cameraCapturerCompat.switchCamera();
    }

    /*
     * Room events listener
     */

    private WritableMap convertRemoteParticipants(){
        WritableMap payload = new WritableNativeMap();

        WritableArray participantsPayload = new WritableNativeArray();
        participants.forEach(par -> {
            WritableMap participant = new WritableNativeMap();
            participant.putString("identity", par.getIdentity());
            participantsPayload.pushMap(participant);

        });
        payload.putArray("participants", participantsPayload);
        return payload;
    }

    private Room.Listener roomListener() {
        TwilioRoomView view = this;
        return new Room.Listener() {
            @Override
            public void onConnected(Room room) {
                localParticipant = room.getLocalParticipant();
                participants = room.getRemoteParticipants();

                manager.pushEvent(reactContext, view, TwilioRoomViewManager.onDidConnect, convertRemoteParticipants());
            }

            @Override
            public void onReconnecting(@NonNull Room room, @NonNull TwilioException twilioException) {
                manager.pushEvent(reactContext, view, TwilioRoomViewManager.onReConnecting, new WritableNativeMap());
            }

            @Override
            public void onReconnected(@NonNull Room room) {
                manager.pushEvent(reactContext, view, TwilioRoomViewManager.onDidReConnect, new WritableNativeMap());
            }

            @Override
            public void onConnectFailure(Room room, TwilioException e) {
                manager.pushEvent(reactContext, view, TwilioRoomViewManager.onFailedToConnect, new WritableNativeMap());
            }

            @Override
            public void onDisconnected(Room room, TwilioException e) {
                localParticipant = null;

                // Only reinitialize the UI if disconnect was not called from onDestroy()
                if (!disconnectedFromOnDestroy) {
                    configureAudio(false);
                }
                manager.pushEvent(reactContext, view, TwilioRoomViewManager.onDisConnected, new WritableNativeMap());
            }

            @Override
            public void onParticipantConnected(Room room, RemoteParticipant remoteParticipant) {
                participants.add(remoteParticipant);
                manager.pushEvent(reactContext, view, TwilioRoomViewManager.onParticipantConnected, convertRemoteParticipants());
            }

            @Override
            public void onParticipantDisconnected(Room room, RemoteParticipant remoteParticipant) {
                participants.remove(remoteParticipant);
                manager.pushEvent(reactContext, view, TwilioRoomViewManager.onParticipantDisConnected, convertRemoteParticipants());
            }

            @Override
            public void onRecordingStarted(Room room) {
                /*
                 * Indicates when media shared to a Room is being recorded. Note that
                 * recording is only available in our Group Rooms developer preview.
                 */

            }

            @Override
            public void onRecordingStopped(Room room) {
                /*
                 * Indicates when media shared to a Room is no longer being recorded. Note that
                 * recording is only available in our Group Rooms developer preview.
                 */

            }
        };
    }
}
