/**
 * Copyright 2020 Phenix Real Time Solutions, Inc. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

var phenixWebSdk = 'phenix-web-sdk';
var params = window.location.search.substring(1).split('&');

for (var i = 0; i < params.length; i++) {
    if (params[i] === 'development') {
        phenixWebSdk = './web-sdk';
    }
}

/* global requirejs */
requirejs.config({
    paths: {
        'phenix-web-sdk': phenixWebSdk,
        'phenix-rtc': 'phenix-rtc/dist/phenix-rtc',
        'jquery': 'jquery/dist/jquery.min',
        'lodash': 'lodash/lodash.min',
        'bootstrap': 'bootstrap/dist/js/bootstrap.min',
        'protobuf': 'protobuf/dist/ProtoBuf.min',
        'bootstrap-notify': 'bootstrap-notify/bootstrap-notify.min',
        'fingerprintjs2': 'fingerprintjs2/dist/fingerprint2.min',
        'webrtc-adapter': 'webrtc-adapter/out/adapter',
        'shaka-player': 'shaka-player/dist/shaka-player.compiled',
        'video-player': 'player',
        'app-setup': 'app-setup',
        'phenix-web-lodash-light': 'phenix-web-lodash-light/dist/phenix-web-lodash-light.min',
        'phenix-web-assert': 'phenix-web-assert/dist/phenix-web-assert.min',
        'phenix-web-http': 'phenix-web-http/dist/phenix-web-http.min',
        'phenix-web-logging': 'phenix-web-logging/dist/phenix-web-logging.min',
        'phenix-web-observable': 'phenix-web-observable/dist/phenix-web-observable',
        'phenix-web-reconnecting-websocket': 'phenix-web-reconnecting-websocket/dist/phenix-web-reconnecting-websocket.min',
        'phenix-web-network-connection-monitor': 'phenix-web-network-connection-monitor/dist/phenix-web-network-connection-monitor.min',
        'phenix-web-proto': 'phenix-web-proto/dist/phenix-web-proto.min',
        'phenix-web-event': 'phenix-web-event/dist/phenix-web-event.min',
        'phenix-web-disposable': 'phenix-web-disposable/dist/phenix-web-disposable.min',
        'phenix-web-closest-endpoint-resolver': 'phenix-web-closest-endpoint-resolver/dist/phenix-web-closest-endpoint-resolver.min',
        'phenix-web-player': 'phenix-web-player/dist/phenix-web-player-bundled.min',
        'phenix-web-application-activity-detector': 'phenix-web-application-activity-detector/dist/phenix-web-application-activity-detector.min',
        'phenix-web-global': 'phenix-web-global/dist/phenix-web-global.min',
        'phenix-web-detect-browser': 'phenix-web-detect-browser/dist/phenix-web-detect-browser.min'
    }
});

requirejs([
    'jquery',
    'lodash',
    'phenix-web-sdk',
    'video-player',
    'app-setup'
], function($, _, sdk, Player, app) {
    var isMobileAppleDevice = /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream;
    var publishAndJoinRoomButton = document.getElementById('publishAndJoinRoomButton');
    var stopButton = document.getElementById('stopButton');
    var muteAudio = false;
    var muteVideo = false;
    var muteAudioButton = document.getElementById('muteAudio');
    var muteVideoButton = document.getElementById('muteVideo');
    var publishScreenShareButton = document.getElementById('publishScreenButton');
    var stopScreenShareButton = document.getElementById('stopPublishScreenButton');
    var videoList = document.getElementById('videoList');
    var selfVideoList = document.getElementById('selfVideoList');
    var publisher = null;
    var screenPublisher = null;
    var lowQualityPublisher = null;
    var roomService = null;
    var screenName = 'ScreenName' + Math.floor(Math.random() * 10000) + 1; // Helpful if unique but we don't enforce this. You might set this to be an email or a nickname, or both. Then parse it when joining the room.
    var memberRole = 'Participant';
    var membersStore = [];
    var memberSubscriptions = {};
    var videoSources = [];
    var audioSources = [];
    var sendMessageIntervalId;

    var init = function init() {
        var roomExpress;

        var createRoomExpress = function createPCastExpress() {
            var adminApiProxyClient = new sdk.net.AdminApiProxyClient();

            adminApiProxyClient.setBackendUri(app.getBaseUri() + '/pcast');
            adminApiProxyClient.setAuthenticationData(app.getAuthData());

            roomExpress = new sdk.express.RoomExpress({
                treatBackgroundAsOffline: app.getUrlParameter('treatBackgroundAsOffline') === 'true' || isMobileAppleDevice,
                adminApiProxyClient: adminApiProxyClient,
                uri: app.getUri(),
                shakaLoader: function(callback) {
                    if (!app.getUrlParameter('shaka')) {
                        return callback(null);
                    }

                    requirejs(['shaka-player'], function(shaka) {
                        callback(shaka);
                    });
                },
                webPlayerLoader: function(callback) {
                    if (app.getUrlParameter('shaka')) {
                        return callback(null);
                    }

                    requirejs(['phenix-web-player'], function(webPlayer) {
                        callback(webPlayer);
                    });
                }
            });

            roomExpress.getPCastExpress().getSessionIdObservable().subscribe(function(value) {
                console.log('SessionIdObservable', value);
            });

            if (app.getUrlParameter('debug') === 'true') {
                app.addDebugAppender(roomExpress.getPCastExpress().getPCast());
            }
        };

        sdk.utils.rtc.getSources(function(sources) {
            videoSources = sources.filter(function(source) {
                return source.kind === 'video';
            });
            audioSources = sources.filter(function(source) {
                return source.kind === 'audio';
            });
        });

        function publishVideoAndCameraAtTwoQualitiesAndJoinRoom() {
            if (videoSources.length === 0 || audioSources.length === 0) {
                return setUserMessage('Sources not available yet');
            }

            hideElement(publishAndJoinRoomButton);
            displayElement(stopButton);

            var roomAlias = $('#alias').val();
            var videoElement = createVideo();
            var publishOptions = {
                capabilities: ['fhd', 'prefer-h264'], // Add other capabilities if you like. Prefer-h264 allows publishing/viewing on Safari/IOS 11
                room: {
                    alias: roomAlias,
                    name: app.getUrlParameter('roomName', roomAlias),
                    type: app.getUrlParameter('roomType', 'MultiPartyChat')
                }, // Set alias so that you can always uniquely identify room without accessing the return value
                mediaConstraints: {
                    audio: {deviceId: audioSources[0].id},
                    video: {deviceId: videoSources[0].id}
                }, // Use the same deviceIds for both
                screenName: screenName,
                streamType: 'User',
                memberRole: memberRole
            };

            var highQualityOptions = Object.assign({}, publishOptions, {
                videoElement: videoElement, // Bind local user media to this element (no delay)
                resolution: 720,
                frameRate: 15,
                streamInfo: {quality: 'high'} // Pass custom info here
            });
            var lowQualityOptions = Object.assign({}, publishOptions, {
                resolution: 180,
                frameRate: 15,
                streamInfo: {quality: 'low'} // Pass custom info here
            });

            videoElement.setAttribute('muted', true); // Don't want to hear yourself
            videoElement.muted = true;

            return publishAndHandleErrors(highQualityOptions, function(response) {
                publisher = {
                    publisher: response.publisher,
                    videoElement: videoElement
                };

                // Detecting network RTT for bad network
                publisher.publisher.networkRTT.subscribe(function(rtt) {
                    console.log('networkRTT', rtt);
                    // If RTT is higher than 800 and dont get lower we probably want to resubscribe with 'audio-only'
                });

                selfVideoList.append(videoElement);

                if (!roomService) {
                    roomService = response.roomService;

                    joinRoom();
                }

                if (app.getUrlParameter('multipleQualities')) {
                    publishAndHandleErrors(lowQualityOptions, function(response) {
                        lowQualityPublisher = {publisher: response.publisher};
                    });
                }
            });
        }

        function publishAndHandleErrors(options, callback) {
            return roomExpress.publishToRoom(options, function(error, response) {
                if (error) {
                    setUserMessage('Unable to publish to Room: ' + error.message);

                    throw error;
                }

                if (response.status !== 'ok' && response.status !== 'ended') {
                    setUserMessage('New Status: ' + response.status);

                    throw new Error(response.status);
                }

                if (response.status === 'ok') {
                    setUserMessage('Online & Publishing');

                    callback(response);
                }
            });
        }

        function joinRoom() {
            var roomAlias = $('#alias').val();

            roomExpress.joinRoom({
                alias: roomAlias,
                role: 'Participant' // Set your role for yourself. Participant will view and interact with other members (must have streams)
            }, function joinRoomCallback(error, response) {
                if (error) {
                    setUserMessage('Unable to join room: ' + error.message);
                    leaveRoomAndStopPublisher();

                    throw error;
                }

                if (response.status !== 'ok' && response.status !== 'ended') {
                    setUserMessage('New Status: ' + response.status);

                    throw new Error(response.status);
                }

                displayElement(publishScreenShareButton);

                var chatService = response.roomService.getChatService();

                chatService.start(3);

                chatService.getObservableChatEnabled().subscribe(function(enabled) {
                    if (enabled) {
                        console.log('Chat is enabled');
                    } else {
                        console.log('Chat is DISABLED');
                    }
                }, {initial: 'notify'});

                chatService.getObservableLastChatMessage().subscribe(function(message) {
                    console.log('Received message [' + message.messageId + ']/[' + new Date(message.timestamp) + ']: [' + message.message + ']');
                });

                chatService.getObservableChatMessages().subscribe(function(messages) {
                    console.log('Current chat history is [' + messages.length + '] messages');
                });

                var messageIdx = 0;

                sendMessageIntervalId = setInterval(function sendMessage() {
                    if (!chatService.getObservableChatEnabled().getValue()) {
                        return;
                    }

                    if (!chatService.canSendMessage()) {
                        console.log('Can NOT send messages right now');

                        return;
                    }

                    var message = 'Test message ' + messageIdx++ + ':' + _.now();

                    chatService.sendMessageToRoom(message, function(error, result) {
                        if (error) {
                            console.error('Failed to send message', error);

                            return;
                        }

                        console.log('Sent message to room with status ', _.get(result, ['status']), ': ', message);
                    });
                }, 5000);
            }, function membersChangedCallback(members) { // This is triggered every time a member joins or leaves
                console.log('Members updated, count=[' + members.length + ']');
                removeOldMembers(members);
                addNewMembers(members);
            });
        }

        function removeOldMembers(members) {
            var membersThatLeft = membersStore.filter(function(member) {
                return !members.includes(member);
            });

            membersThatLeft.forEach(function(memberThatLeft) {
                var memberSubscription = memberSubscriptions[memberThatLeft.getSessionId()];

                if (memberSubscription) {
                    memberSubscription.forEach(function(subscription) {
                        removeMemberStream(subscription.memberStream, memberThatLeft.getSessionId());
                    });
                }

                delete memberSubscriptions[memberThatLeft.getSessionId()];
            });

            membersStore = membersStore.filter(function(member) {
                return !membersThatLeft.includes(member);
            });
        }

        function removeMemberStream(memberStream, memberSessionId) {
            var memberSubscriptionToRemove = memberSubscriptions[memberSessionId].find(function(memberSubscription) {
                return memberStream.getPCastStreamId() === memberSubscription.memberStream.getPCastStreamId();
            });

            memberSubscriptions[memberSessionId] = memberSubscriptions[memberSessionId].filter(function(memberSubscription) {
                return memberStream.getPCastStreamId() !== memberSubscription.memberStream.getPCastStreamId();
            });

            if (memberSubscriptionToRemove) {
                memberSubscriptionToRemove.mediaStream.stop();
                memberSubscriptionToRemove.videoElement.remove();

                return true;
            }

            return false;
        }

        function addNewMembers(members) {
            var membersThatJoined = members.filter(function(member) {
                return !membersStore.includes(member);
            });

            membersThatJoined.forEach(function(newMember) {
                var memberSessionId = newMember.getSessionId();

                memberSubscriptions[memberSessionId] = [];

                // Listen for changes to member streams. This will happen when the publisher fails and it recovers, or when adding or removing a screen share
                var memberStreamSubscription = newMember.getObservableStreams().subscribe(function(memberStreams) {
                    if (!memberSubscriptions[memberSessionId]) {
                        return memberStreamSubscription.dispose();
                    }

                    // Remove old streams
                    memberSubscriptions[memberSessionId].forEach(function(memberSubscription) {
                        var shouldRemoveMember = !memberStreams.find(function(stream) {
                            return stream.getPCastStreamId() === memberSubscription.memberStream.getPCastStreamId();
                        });

                        if (shouldRemoveMember) {
                            removeMemberStream(memberSubscription.memberStream, memberSessionId);
                        }
                    });

                    // Subscribe to new streams
                    memberStreams.filter(function(memberStream) {
                        return !memberSubscriptions[memberSessionId].find(function(memberSubscription) {
                            return memberStream.getPCastStreamId() === memberSubscription.memberStream.getPCastStreamId();
                        });
                    }).forEach(function(memberStream) {
                        subscribeToMemberStream(memberStream, memberSessionId);
                    });
                });

                newMember.getObservableStreams().getValue().forEach(function(memberStream) {
                    subscribeToMemberStream(memberStream, memberSessionId);
                });
            });

            membersStore = members;
        }

        function subscribeToMemberStream(memberStream, sessionId) {
            var videoElement = createVideo();
            var isSelf = sessionId === roomService.getSelf().getSessionId(); // Check if is yourself!
            var streamInfo = memberStream.getInfo(); // Access the custom stream info params that you passed when publishing
            var statusContainer = document.createElement('div');
            var audioStatus = document.createElement('div');
            var videoStatus = document.createElement('div');

            statusContainer.classList.add('status-container');
            statusContainer.appendChild(audioStatus);
            statusContainer.appendChild(videoStatus);

            if (app.getUrlParameter('multipleQualities')) {
                videoElement.classList.add(streamInfo.quality);
            }

            if (isSelf) {
                return; // Ignore self
            }

            var subscribeOptions = {
                videoElement: videoElement,
                monitor: {callback: onMonitorEvent}
            };
            var handleSubscribe = function(error, response) {
                if (!response || !response.mediaStream) {
                    return;
                }

                // Make sure we don't end up with 2 streams due to auto recovery
                var removed = removeMemberStream(memberStream, sessionId);

                memberSubscriptions[sessionId].push({
                    mediaStream: response.mediaStream,
                    videoElement: videoElement,
                    isSelf: isSelf,
                    memberStream: memberStream
                });

                function onPrivacyChange() {
                    var audiMuted = (memberStream.getObservableAudioState().getValue() !== 'TrackEnabled');
                    var videoMuted = (memberStream.getObservableVideoState().getValue() !== 'TrackEnabled');

                    if (audiMuted) {
                        audioStatus.innerHTML = 'Audio is Off';
                    } else {
                        audioStatus.innerHTML = 'Audio is On';
                    }

                    if (videoMuted) {
                        videoStatus.innerHTML = 'Video is Off';
                    } else {
                        videoStatus.innerHTML = 'Video is On';
                    }
                }

                onPrivacyChange();

                memberStream.getObservableVideoState().subscribe(onPrivacyChange);
                memberStream.getObservableAudioState().subscribe(onPrivacyChange);

                videoList.append(statusContainer);
                videoList.append(videoElement);

                setTimeout(function() {
                    if (videoElement.muted) {
                        unMuteAudio(videoElement);
                    }
                }, 10);

                if (removed) {
                    console.log('Replaced member subscription for session ID [' + sessionId + ']');
                }
            };

            roomExpress.subscribeToMemberStream(memberStream, subscribeOptions, handleSubscribe);
        }

        function unMuteAudio(videoElement) {
            videoElement.muted = false;
            videoElement.setAttribute('muted', false);
        }

        function setUserMessage(message) {
            var userMessageElement = document.getElementById('userMessage');

            userMessageElement.innerText = message;
        }

        // This will get called when the stream fails to recover from an issue
        function onMonitorEvent(error, response) {
            if (error) {
                return; // May want to display something indicating failure for member
            }

            if (response.status !== 'ok') {
                console.log('Member stream subscription failed [%s]', response.status); // May want to display something indicating failure for member
            }

            // You may have the option to automatically retry
            if (response.retry) {
                console.log('Attempting to redo member stream subscription after failure'); // May want to display something indicating failure for member

                response.retry();
            }
        }

        function createVideo() {
            var videoElement = document.createElement('video');

            videoElement.setAttribute('playsinline', ''); // For Safari and IOS
            videoElement.setAttribute('autoplay', ''); // For Safari and IOS + Mobile

            // To resolve unintended pauses
            videoElement.onpause = function() {
                setTimeout(function() {
                    videoElement.play();
                }, 10);
            };

            return videoElement;
        }

        function publishScreen() {
            hideElement(publishScreenShareButton);
            displayElement(stopScreenShareButton);

            var roomAlias = $('#alias').val();
            var videoElement = createVideo();
            var publishOptions = {
                capabilities: ['fhd', 'prefer-h264'], // Add other capabilities if you like. Prefer-h264 allows publishing/viewing on Safari/IOS 11
                room: {
                    alias: roomAlias,
                    name: roomAlias,
                    type: 'MultiPartyChat'
                }, // Set alias so that you can always uniquely identify room without accessing the return value
                screenName: screenName,
                streamType: 'Presentation', // Distinguish from normal publisher
                memberRole: memberRole,
                videoElement: videoElement,
                monitor: {callback: onMonitorEvent}
            };

            videoElement.setAttribute('muted'); // Don't want to hear yourself
            videoElement.muted = true;

            return roomExpress.publishScreenToRoom(publishOptions, function(error, response) {
                if (error) {
                    setUserMessage('Unable to publish to Room: ' + error.message);

                    throw error;
                }

                if (response.status !== 'ok' && response.status !== 'ended') {
                    setUserMessage('New Status: ' + response.status);

                    throw new Error(response.status);
                }

                if (response.status === 'ok') {
                    screenPublisher = {
                        publisher: response.publisher,
                        videoElement: videoElement
                    };

                    selfVideoList.append(videoElement);
                }
            });
        }

        function stopPublishScreen() {
            displayElement(publishScreenShareButton);
            hideElement(stopScreenShareButton);

            if (screenPublisher) {
                screenPublisher.publisher.stop();
                screenPublisher.videoElement.remove();

                screenPublisher = null;
            }
        }

        muteAudioButton.onclick = onMuteAudioClick;
        muteVideoButton.onclick = onMuteVideoClick;
        publishAndJoinRoomButton.onclick = publishVideoAndCameraAtTwoQualitiesAndJoinRoom;
        stopButton.onclick = leaveRoomAndStopPublisher;
        publishScreenShareButton.onclick = publishScreen;
        stopScreenShareButton.onclick = stopPublishScreen;

        app.setOnReset(function() {
            createRoomExpress();
        });

        createRoomExpress();
    };

    function onMuteAudioClick() {
        muteAudio = !muteAudio;
        muteAudioButton.textContent = muteAudio
            ? 'Unmute Audio'
            : 'Mute Audio';

        if (publisher) {
            if (muteAudio) {
                publisher.publisher.disableAudio();
            } else {
                publisher.publisher.enableAudio();
            }
        }
    }

    function onMuteVideoClick() {
        muteVideo = !muteVideo;
        muteVideoButton.textContent = muteVideo
            ? 'Unmute Video'
            : 'Mute Video';

        if (publisher) {
            if (muteVideo) {
                publisher.publisher.disableVideo();
            } else {
                publisher.publisher.enableVideo();
            }
        }
    }

    function leaveRoomAndStopPublisher() {
        if (publisher) {
            publisher.publisher.stop();
            publisher.videoElement.remove();

            publisher = null;
        }

        if (lowQualityPublisher) {
            lowQualityPublisher.publisher.stop();

            lowQualityPublisher = null;
        }

        if (screenPublisher) {
            screenPublisher.publisher.stop();
            screenPublisher.videoElement.remove();

            screenPublisher = null;
        }

        if (roomService) {
            roomService.leaveRoom(function(error, response) {
                roomService = null;

                if (error) {
                    throw error;
                }

                if (response.status !== 'ok') {
                    throw new Error(response.status);
                }
            });
        }

        if (sendMessageIntervalId) {
            clearInterval(sendMessageIntervalId);

            sendMessageIntervalId = null;
        }

        hideElement(stopButton);
        displayElement(publishAndJoinRoomButton);
    }

    function displayElement(element) {
        element.className = element.className.substring(0, element.className.indexOf(' hide'));
    }

    function hideElement(element) {
        if (element.className.indexOf('hide') === -1) {
            element.className += ' hide';
        }
    }

    $('#applicationId').change(function() {
        leaveRoomAndStopPublisher();
        init();
    });

    $('#secret').change(function() {
        leaveRoomAndStopPublisher();
        init();
    });

    $(function() {
        app.init();
        init();
    });
});