/*
 *
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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.
 *
*/

/*global Windows:true */

var MediaFile = require('cordova-plugin-media-capture.MediaFile');
var CaptureError = require('cordova-plugin-media-capture.CaptureError');
var CaptureAudioOptions = require('cordova-plugin-media-capture.CaptureAudioOptions');
var CaptureImageOptions = require('cordova-plugin-media-capture.CaptureImageOptions');
var CaptureVideoOptions = require('cordova-plugin-media-capture.CaptureVideoOptions');
var MediaFileData = require('cordova-plugin-media-capture.MediaFileData');

/*
 * Class that combines all logic for capturing picture and video on WP8.1
 */
function MediaCaptureProxy() {

    var previewContainer,
        capturePreview = null,
        captureCancelButton = null,
        captureSettings = null,
        captureStarted = false,
        capturedPictureFile,
        capturedVideoFile,
        capture = null;

    var CaptureNS = Windows.Media.Capture;

    /**
     * Helper function that toggles visibility of DOM elements with provided ids
     * @param {String} variable number of elements' ids which visibility needs to be toggled 
     */
    function toggleElements() {
        // convert arguments to array
        var args = Array.prototype.slice.call(arguments);
        args.forEach(function(buttonId) {
            var buttonEl = document.getElementById(buttonId);
            if (buttonEl) {
                var curDisplayStyle = buttonEl.style.display;
                buttonEl.style.display = curDisplayStyle === 'none' ? 'block' : 'none';
            }
        });
    }

    /**
     * Creates basic camera UI with preview 'video' element and 'Cancel' button
     * Capture starts, when you clicking on preview.
     */
    function createCameraUI() {

        var buttonStyle = "margin: 7px; border: 2.5px solid white; width: 45%; height: 35px; color: white; background-color: black;";

        previewContainer = document.createElement('div');
        previewContainer.style.cssText = "background-position: 50% 50%; background-repeat: no-repeat; background-size: contain; background-color: black; left: 0px; top: 0px; width: 100%; height: 100%; position: fixed; z-index: 9999";
        previewContainer.innerHTML =
            '<video id="capturePreview" style="width: 100%; height: 100%"></video>' +
            '<div id="previewButtons" style="width: 100%; bottom: 0px; display: flex; position: absolute; justify-content: space-around; background-color: black;">' +
                '<button id="takePicture" style="' + buttonStyle + '">Capture</button>' +
                '<button id="cancelCapture" style="' + buttonStyle + '">Cancel</button>' +
                '<button id="selectPicture" style="display: none; ' + buttonStyle + '">Accept</button>' +
                '<button id="retakePicture" style="display: none; ' + buttonStyle + '">Retake</button>' +
            '</div>';

        document.body.appendChild(previewContainer);

        // Create fullscreen preview
        capturePreview = document.getElementById('capturePreview');

        // Create cancel button
        captureCancelButton = document.getElementById('cancelCapture');

        capture = new CaptureNS.MediaCapture();

        captureSettings = new CaptureNS.MediaCaptureInitializationSettings();
        captureSettings.streamingCaptureMode = CaptureNS.StreamingCaptureMode.audioAndVideo;
    }

    /**
     * Starts camera preview and binds provided callbacks to controls
     * @param  {function} takeCallback   Callback for Take button
     * @param  {function} errorCallback  Callback for Cancel button + default error callback
     * @param  {function} selectCallback Callback for Select button
     * @param  {function} retakeCallback Callback for Retake button
     */
    function startCameraPreview(takeCallback, errorCallback, selectCallback, retakeCallback) {
        // try to select appropriate device for capture
        // rear camera is preferred option
        var expectedPanel = Windows.Devices.Enumeration.Panel.back;
        Windows.Devices.Enumeration.DeviceInformation.findAllAsync(Windows.Devices.Enumeration.DeviceClass.videoCapture).done(function (devices) {
            if (devices.length > 0) {
                devices.forEach(function (currDev) {
                    if (currDev.enclosureLocation && currDev.enclosureLocation.panel && currDev.enclosureLocation.panel == expectedPanel) {
                        captureSettings.videoDeviceId = currDev.id;
                    }
                });

                capture.initializeAsync(captureSettings).done(function () {
                    // This is necessary since WP8.1 MediaCapture outputs video stream rotated 90 degrees CCW
                    // TODO: This can be not consistent across devices, need additional testing on various devices
                    // msdn.microsoft.com/en-us/library/windows/apps/hh452807.aspx
                    capture.setPreviewRotation(Windows.Media.Capture.VideoRotation.clockwise90Degrees);
                    capturePreview.msZoom = true;

                    capturePreview.src = URL.createObjectURL(capture);
                    capturePreview.play();

                    previewContainer.style.display = 'block';

                    // Bind events to controls
                    capturePreview.onclick = takeCallback;
                    document.getElementById('takePicture').onclick = takeCallback;
                    document.getElementById('cancelCapture').onclick = function () {
                        errorCallback(CaptureError.CAPTURE_NO_MEDIA_FILES);
                    };
                    document.getElementById('selectPicture').onclick = selectCallback;
                    document.getElementById('retakePicture').onclick = retakeCallback;
                }, function (err) {
                    destroyCameraPreview();
                    errorCallback(CaptureError.CAPTURE_INTERNAL_ERR, err);
                });
            } else {
                // no appropriate devices found
                destroyCameraPreview();
                errorCallback(CaptureError.CAPTURE_INTERNAL_ERR);
            }
        });
    }

    /**
     * Destroys camera preview, removes all elements created
     */
    function destroyCameraPreview() {
        capturePreview.pause();
        capturePreview.src = null;
        previewContainer && document.body.removeChild(previewContainer);
        if (capture) {
            capture.stopRecordAsync();
            capture = null;
        }
    }

    return {
        /**
         * Initiate video capture using MediaCapture class
         * @param  {function} successCallback Called, when user clicked on preview, with captured file object
         * @param  {function} errorCallback   Called on any error
         */
        captureVideo: function (successCallback, errorCallback) {
            try {
                createCameraUI();
                startCameraPreview(function () {
                    // This callback called twice: whem video capture started and when it ended
                    // so we need to check capture status
                    if (!captureStarted) {
                        // remove cancel button and rename 'Take' button to 'Stop'
                        toggleElements('cancelCapture');
                        document.getElementById('takePicture').text = 'Stop';

                        var encodingProperties = Windows.Media.MediaProperties.MediaEncodingProfile.createMp4(Windows.Media.MediaProperties.VideoEncodingQuality.auto),
                            generateUniqueCollisionOption = Windows.Storage.CreationCollisionOption.generateUniqueName,
                            localFolder = Windows.Storage.ApplicationData.current.localFolder;

                        localFolder.createFileAsync("cameraCaptureVideo.mp4", generateUniqueCollisionOption).done(function(capturedFile) {
                            capture.startRecordToStorageFileAsync(encodingProperties, capturedFile).done(function() {
                                capturedVideoFile = capturedFile;
                                captureStarted = true;
                            }, function(err) {
                                destroyCameraPreview();
                                errorCallback(CaptureError.CAPTURE_INTERNAL_ERR, err);
                            });
                        }, function(err) {
                            destroyCameraPreview();
                            errorCallback(CaptureError.CAPTURE_INTERNAL_ERR, err);
                        });
                    } else {
                        capture.stopRecordAsync().done(function () {
                            destroyCameraPreview();
                            successCallback(capturedVideoFile);
                        });
                    }
                }, errorCallback);
            } catch (ex) {
                destroyCameraPreview();
                errorCallback(CaptureError.CAPTURE_INTERNAL_ERR, ex);
            }
        },

        /**
         * Initiate image capture using MediaCapture class
         * @param  {function} successCallback Called, when user clicked on preview, with captured file object
         * @param  {function} errorCallback   Called on any error
         */
        capturePhoto: function (successCallback, errorCallback) {
            try {
                createCameraUI();
                startCameraPreview(
                    // Callback for Take button - captures intermediate image file.
                    function () {
                        var encodingProperties = Windows.Media.MediaProperties.ImageEncodingProperties.createJpeg(),
                            overwriteCollisionOption = Windows.Storage.CreationCollisionOption.replaceExisting,
                            tempFolder = Windows.Storage.ApplicationData.current.temporaryFolder;

                        tempFolder.createFileAsync("cameraCaptureImage.jpg", overwriteCollisionOption).done(function (capturedFile) {
                            capture.capturePhotoToStorageFileAsync(encodingProperties, capturedFile).done(function () {
                                // store intermediate result in object's global variable
                                capturedPictureFile = capturedFile;
                                // show pre-captured image and toggle visibility of all buttons
                                previewContainer.style.backgroundImage = 'url("' + 'ms-appdata:///temp/' + capturedFile.name + '")';
                                toggleElements('capturePreview', 'takePicture', 'cancelCapture', 'selectPicture', 'retakePicture');
                            }, function (err) {
                                destroyCameraPreview();
                                errorCallback(CaptureError.CAPTURE_INTERNAL_ERR, err);
                            });
                        }, function (err) {
                            destroyCameraPreview();
                            errorCallback(CaptureError.CAPTURE_INTERNAL_ERR, err);
                        });
                    },
                    // error + cancel callback
                    function (err) {
                        destroyCameraPreview();
                        errorCallback(err);
                    },
                    // Callback for Select button - copies intermediate file into persistent application's storage
                    function () {
                        var generateUniqueCollisionOption = Windows.Storage.CreationCollisionOption.generateUniqueName,
                            localFolder = Windows.Storage.ApplicationData.current.localFolder;

                        capturedPictureFile.copyAsync(localFolder, capturedPictureFile.name, generateUniqueCollisionOption).done(function (copiedFile) {
                            destroyCameraPreview();
                            successCallback(copiedFile);
                        }, function(err) {
                            destroyCameraPreview();
                            errorCallback(err);
                        });
                    },
                    // Callback for retake button - just toggles visibility of necessary elements
                    function () {
                        toggleElements('capturePreview', 'takePicture', 'cancelCapture', 'selectPicture', 'retakePicture');
                    }
                );
            } catch (ex) {
                destroyCameraPreview();
                errorCallback(CaptureError.CAPTURE_INTERNAL_ERR, ex);
            }
        }
    };
}

module.exports = {

    captureAudio:function(successCallback, errorCallback, args) {
        var options = args[0];

        var audioOptions = new CaptureAudioOptions();
        if (typeof(options.duration) == 'undefined') {
            audioOptions.duration = 3600; // Arbitrary amount, need to change later
        } else if (options.duration > 0) {
            audioOptions.duration = options.duration;
        } else {
            errorCallback(new CaptureError(CaptureError.CAPTURE_INVALID_ARGUMENT));
            return;
        }

        // Some shortcuts for long namespaces
        var CaptureNS = Windows.Media.Capture,
            MediaPropsNS = Windows.Media.MediaProperties,
            localAppData = Windows.Storage.ApplicationData.current.localFolder,
            generateUniqueName = Windows.Storage.NameCollisionOption.generateUniqueName;

        var mediaCapture = new CaptureNS.MediaCapture(),
            mediaCaptureSettings = new CaptureNS.MediaCaptureInitializationSettings(),
            mp3EncodingProfile = new MediaPropsNS.MediaEncodingProfile.createMp3(MediaPropsNS.AudioEncodingQuality.auto),
            m4aEncodingProfile = new MediaPropsNS.MediaEncodingProfile.createM4a(MediaPropsNS.AudioEncodingQuality.auto);

        mediaCaptureSettings.streamingCaptureMode = CaptureNS.StreamingCaptureMode.audio;

        var capturedFile,
            stopRecordTimeout;

        var stopRecord = function () {
            mediaCapture.stopRecordAsync().then(function() {
                capturedFile.getBasicPropertiesAsync().then(function (basicProperties) {
                    var result = new MediaFile(capturedFile.name, 'ms-appdata:///local/' + capturedFile.name, capturedFile.contentType, basicProperties.dateModified, basicProperties.size);
                    result.fullPath = capturedFile.path;
                    successCallback([result]);
                }, function() {
                    errorCallback(new CaptureError(CaptureError.CAPTURE_NO_MEDIA_FILES));
                });
            }, function() { errorCallback(new CaptureError(CaptureError.CAPTURE_NO_MEDIA_FILES)); });
        };

        mediaCapture.initializeAsync(mediaCaptureSettings).done(function () {
            localAppData.createFileAsync("captureAudio.mp3", generateUniqueName).then(function (storageFile) {
                capturedFile = storageFile;
                mediaCapture.startRecordToStorageFileAsync(mp3EncodingProfile, capturedFile).then(function () {
                    stopRecordTimeout = setTimeout(stopRecord, audioOptions.duration * 1000);
                }, function (err) {
                    // -1072868846 is the error code for "No suitable transform was found to encode or decode the content."
                    // so we try to use another (m4a) format
                    if (err.number === -1072868846) {
                        // first we clear existing timeout to prevent success callback to be called with invalid arguments
                        // second we start same actions to try to record m4a audio
                        clearTimeout(stopRecordTimeout);
                        localAppData.createFileAsync("captureAudio.m4a", generateUniqueName).then(function (storageFile) {
                            capturedFile = storageFile;
                            mediaCapture.startRecordToStorageFileAsync(m4aEncodingProfile, capturedFile).then(function () {
                                stopRecordTimeout = setTimeout(stopRecord, audioOptions.duration * 1000);
                            }, function() {
                                // if we here, we're totally failed to record either mp3 or m4a
                                errorCallback(new CaptureError(CaptureError.CAPTURE_INTERNAL_ERR));
                                return;
                            });
                        });
                    } else {
                        errorCallback(new CaptureError(CaptureError.CAPTURE_INTERNAL_ERR));
                        return;
                    }
                });
            }, function () { errorCallback(new CaptureError(CaptureError.CAPTURE_NO_MEDIA_FILES)); });
        });
    },

    captureImage:function (successCallback, errorCallback, args) {
        var options = args[0];

        var CaptureNS = Windows.Media.Capture;
        // Check if necessary API available
        if (!CaptureNS.CameraCaptureUI) {
            // We are running on WP8.1 which lacks CameraCaptureUI class
            // so we need to use MediaCapture class instead and implement custom UI for camera

            function fail(code, data) {
                var err = new CaptureError(code);
                err.message = data;
                errorCallback(err);
            }

            var proxy = new MediaCaptureProxy();

            proxy.capturePhoto(function (photoFile) {
                photoFile.getBasicPropertiesAsync().done(function (basicProperties) {
                    var result = new MediaFile(photoFile.name, 'ms-appdata:///local/' + photoFile.name, photoFile.contentType, basicProperties.dateModified, basicProperties.size);
                    result.fullPath = photoFile.path;
                    successCallback([result]);
                }, function (err) {
                    fail(CaptureError.CAPTURE_INTERNAL_ERR, err);
                });
            }, function (err) {
                fail(err);
            });

        } else {
            var imageOptions = new CaptureImageOptions();
            var cameraCaptureUI = new Windows.Media.Capture.CameraCaptureUI();
            cameraCaptureUI.photoSettings.allowCropping = true;
            cameraCaptureUI.photoSettings.maxResolution = Windows.Media.Capture.CameraCaptureUIMaxPhotoResolution.highestAvailable;
            cameraCaptureUI.photoSettings.format = Windows.Media.Capture.CameraCaptureUIPhotoFormat.jpeg;
            cameraCaptureUI.captureFileAsync(Windows.Media.Capture.CameraCaptureUIMode.photo).done(function (file) {
                file.moveAsync(Windows.Storage.ApplicationData.current.localFolder, "cameraCaptureImage.jpg", Windows.Storage.NameCollisionOption.generateUniqueName).then(function () {
                    file.getBasicPropertiesAsync().then(function (basicProperties) {
                        var result = new MediaFile(file.name, 'ms-appdata:///local/' + file.name, file.contentType, basicProperties.dateModified, basicProperties.size);
                        result.fullPath = file.path;
                        successCallback([result]);
                    }, function () {
                        errorCallback(new CaptureError(CaptureError.CAPTURE_NO_MEDIA_FILES));
                    });
                }, function () {
                    errorCallback(new CaptureError(CaptureError.CAPTURE_NO_MEDIA_FILES));
                });
            }, function () {
                errorCallback(new CaptureError(CaptureError.CAPTURE_NO_MEDIA_FILES));
            });
        }
    },

    captureVideo:function (successCallback, errorCallback, args) {
        var options = args[0];

        var CaptureNS = Windows.Media.Capture;
        // Check if necessary API available
        if (!CaptureNS.CameraCaptureUI) {
            // We are running on WP8.1 which lacks CameraCaptureUI class
            // so we need to use MediaCapture class instead and implement custom UI for camera

            function fail(code, data) {
                var err = new CaptureError(code);
                err.message = data;
                errorCallback(err);
            }

            var proxy = new MediaCaptureProxy();

            proxy.captureVideo(function (videoFile) {
                videoFile.getBasicPropertiesAsync().done(function (basicProperties) {
                    var result = new MediaFile(videoFile.name, 'ms-appdata:///local/' + videoFile.name, videoFile.contentType, basicProperties.dateModified, basicProperties.size);
                    result.fullPath = videoFile.path;
                    successCallback([result]);
                }, function (err) {
                    fail(CaptureError.CAPTURE_INTERNAL_ERR, err);
                });
            }, fail);

        } else {

            var videoOptions = new CaptureVideoOptions();
            if (options.duration && options.duration > 0) {
                videoOptions.duration = options.duration;
            }
            if (options.limit > 1) {
                videoOptions.limit = options.limit;
            }
            var cameraCaptureUI = new Windows.Media.Capture.CameraCaptureUI();
            cameraCaptureUI.videoSettings.allowTrimming = true;
            cameraCaptureUI.videoSettings.format = Windows.Media.Capture.CameraCaptureUIVideoFormat.mp4;
            cameraCaptureUI.videoSettings.maxDurationInSeconds = videoOptions.duration;
            cameraCaptureUI.captureFileAsync(Windows.Media.Capture.CameraCaptureUIMode.video).then(function(file) {
                file.moveAsync(Windows.Storage.ApplicationData.current.localFolder, "cameraCaptureVideo.mp4", Windows.Storage.NameCollisionOption.generateUniqueName).then(function () {
                    file.getBasicPropertiesAsync().then(function(basicProperties) {
                        var result = new MediaFile(file.name, 'ms-appdata:///local/' + file.name, file.contentType, basicProperties.dateModified, basicProperties.size);
                        result.fullPath = file.path;
                        successCallback([result]);
                    }, function() {
                        errorCallback(new CaptureError(CaptureError.CAPTURE_NO_MEDIA_FILES));
                    });
                }, function() {
                    errorCallback(new CaptureError(CaptureError.CAPTURE_NO_MEDIA_FILES));
                });
            }, function() { errorCallback(new CaptureError(CaptureError.CAPTURE_NO_MEDIA_FILES)); });
        }
    },

    getFormatData: function (successCallback, errorCallback, args) {
        Windows.Storage.StorageFile.getFileFromPathAsync(args[0]).then(
            function (storageFile) {
                var mediaTypeFlag = String(storageFile.contentType).split("/")[0].toLowerCase();
                if (mediaTypeFlag === "audio") {
                    storageFile.properties.getMusicPropertiesAsync().then(function (audioProperties) {
                        successCallback(new MediaFileData(null, audioProperties.bitrate, 0, 0, audioProperties.duration / 1000));
                    }, function () {
                        errorCallback(new CaptureError(CaptureError.CAPTURE_INVALID_ARGUMENT));
                    });
                }
                else if (mediaTypeFlag === "video") {
                    storageFile.properties.getVideoPropertiesAsync().then(function (videoProperties) {
                        successCallback(new MediaFileData(null, videoProperties.bitrate, videoProperties.height, videoProperties.width, videoProperties.duration / 1000));
                    }, function () {
                        errorCallback(new CaptureError(CaptureError.CAPTURE_INVALID_ARGUMENT));
                    });
                }
                else if (mediaTypeFlag === "image") {
                    storageFile.properties.getImagePropertiesAsync().then(function (imageProperties) {
                        successCallback(new MediaFileData(null, 0, imageProperties.height, imageProperties.width, 0));
                    }, function () {
                        errorCallback(new CaptureError(CaptureError.CAPTURE_INVALID_ARGUMENT));
                    });
                }
                else { errorCallback(new CaptureError(CaptureError.CAPTURE_INVALID_ARGUMENT)); }
            }, function () {
                errorCallback(new CaptureError(CaptureError.CAPTURE_INVALID_ARGUMENT));
            }
        );
    }
};

require("cordova/exec/proxy").add("Capture",module.exports);
