/*jshint node: true, jasmine: true */

/*
 *
 * 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.
 *
*/

// these tests are meant to be executed by Cordova Paramedic test runner
// you can find it here: https://github.com/apache/cordova-paramedic/
// it is not necessary to do a full CI setup to run these tests
// just run "node cordova-paramedic/main.js --platform ios --plugin cordova-plugin-camera"

'use strict';

var wdHelper = global.WD_HELPER;
var screenshotHelper = global.SCREENSHOT_HELPER;
var isDevice = global.DEVICE;
var cameraConstants = require('../../www/CameraConstants');
var cameraHelper = require('../helpers/cameraHelper');

var MINUTE = 60 * 1000;
var DEFAULT_WEBVIEW_CONTEXT = 'WEBVIEW_1';
var PROMISE_PREFIX = 'appium_camera_promise_';
var CONTEXT_NATIVE_APP = 'NATIVE_APP';

describe('Camera tests iOS.', function () {
    var driver;
    var webviewContext = DEFAULT_WEBVIEW_CONTEXT;
    // promise count to use in promise ID
    var promiseCount = 0;
    // going to set this to false if session is created successfully
    var failedToStart = true;
    // points out which UI automation to use
    var isXCUI = false;
    // spec counter to restart the session
    var specsRun = 0;

    function getNextPromiseId() {
        promiseCount += 1;
        return getCurrentPromiseId();
    }

    function getCurrentPromiseId() {
        return PROMISE_PREFIX + promiseCount;
    }

    function gracefullyFail(error) {
        fail(error);
        return driver
            .quit()
            .then(function () {
                return getDriver();
            });
    }

    // generates test specs by combining all the specified options
    // you can add more options to test more scenarios
    function generateOptions() {
        var sourceTypes = cameraConstants.PictureSourceType;
        var destinationTypes = cameraConstants.DestinationType;
        var encodingTypes = cameraConstants.EncodingType;
        var allowEditOptions = [ true, false ];
        var correctOrientationOptions = [ true, false ];

        return cameraHelper.generateSpecs(sourceTypes, destinationTypes, encodingTypes, allowEditOptions, correctOrientationOptions);
    }

    function usePicture() {
        return driver
            .elementByXPath('//*[@label="Use"]')
            .click()
            .fail(function () {
                if (isXCUI) {
                    return driver
                        .waitForElementByAccessibilityId('Choose', MINUTE / 3)
                        .click();
                }
                // For some reason "Choose" element is not clickable by standard Appium methods on iOS <= 9
                return wdHelper.tapElementByXPath('//UIAButton[@label="Choose"]', driver);
            });
    }

    function clickPhoto() {
        if (isXCUI) {
            // iOS >=10
            return driver
                .context(CONTEXT_NATIVE_APP)
                .elementsByXPath('//XCUIElementTypeCell')
                .then(function(photos) {
                    if (photos.length == 0) {
                        return driver
                            .sleep(0) // driver.source is not a function o.O
                            .source()
                            .then(function (src) {
                                console.log(src);
                                gracefullyFail('Couldn\'t find an image to click');
                            });
                    }
                    // intentionally clicking the second photo here
                    // the first one is not clickable for some reason
                    return photos[1].click();
                });
        }
        // iOS <10
        return driver
            .elementByXPath('//UIACollectionCell')
            .click();
    }

    function getPicture(options, cancelCamera, skipUiInteractions) {
        var promiseId = getNextPromiseId();
        if (!options) {
            options = {};
        }

        return driver
            .context(webviewContext)
            .execute(cameraHelper.getPicture, [options, promiseId])
            .context(CONTEXT_NATIVE_APP)
            .then(function () {
                if (skipUiInteractions) {
                    return;
                }
                if (options.hasOwnProperty('sourceType') && options.sourceType === cameraConstants.PictureSourceType.PHOTOLIBRARY) {
                    return driver
                        .waitForElementByAccessibilityId('Camera Roll', MINUTE / 2)
                        .click()
                        .then(function () {
                            return clickPhoto();
                        })
                        .then(function () {
                            if (!options.allowEdit) {
                                return driver;
                            }
                            return usePicture();
                        });
                }
                if (options.hasOwnProperty('sourceType') && options.sourceType === cameraConstants.PictureSourceType.SAVEDPHOTOALBUM) {
                    return clickPhoto()
                        .then(function () {
                            if (!options.allowEdit) {
                                return driver;
                            }
                            return usePicture();
                        });
                }
                if (cancelCamera) {
                    return driver
                        .waitForElementByAccessibilityId('Cancel', MINUTE / 2)
                        .click();
                }
                return driver
                    .waitForElementByAccessibilityId('Take Picture', MINUTE / 2)
                    .click()
                    .waitForElementByAccessibilityId('Use Photo', MINUTE / 2)
                    .click();
            })
            .fail(fail);
    }

    // checks if the picture was successfully taken
    // if shouldLoad is falsy, ensures that the error callback was called
    function checkPicture(shouldLoad, options) {
        if (!options) {
            options = {};
        }
        return driver
            .context(webviewContext)
            .setAsyncScriptTimeout(MINUTE / 2)
            .executeAsync(cameraHelper.checkPicture, [getCurrentPromiseId(), options])
            .then(function (result) {
                if (shouldLoad) {
                    if (result !== 'OK') {
                        fail(result);
                    }
                } else if (result.indexOf('ERROR') === -1) {
                    throw 'Unexpected success callback with result: ' + result;
                }
            });
    }

    // takes a picture with the specified options
    // and then verifies it
    function runSpec(options, done, pending) {
        if (options.sourceType === cameraConstants.PictureSourceType.CAMERA && !isDevice) {
            pending('Camera is not available on iOS simulator');
        }
        checkSession(done);
        specsRun += 1;
        return driver
            .then(function () {
                return getPicture(options);
            })
            .then(function () {
                return checkPicture(true, options);
            })
            .fail(gracefullyFail);
    }

    function getDriver() {
        failedToStart = true;
        driver = wdHelper.getDriver('iOS');
        return wdHelper.getWebviewContext(driver)
            .then(function(context) {
                webviewContext = context;
                return driver.context(webviewContext);
            })
            .then(function () {
                return wdHelper.waitForDeviceReady(driver);
            })
            .then(function () {
                return wdHelper.injectLibraries(driver);
            })
            .sessionCapabilities()
            .then(function (caps) {
                var platformVersion = parseFloat(caps.platformVersion);
                isXCUI = platformVersion >= 10.0;
            })
            .then(function () {
                var options = {
                    quality: 50,
                    allowEdit: false,
                    sourceType: cameraConstants.PictureSourceType.SAVEDPHOTOALBUM,
                    saveToPhotoAlbum: false,
                    targetWidth: 210,
                    targetHeight: 210
                };
                return driver
                    .then(function () { return getPicture(options, false, true); })
                    .context(CONTEXT_NATIVE_APP)
                    .acceptAlert()
                    .then(function alertDismissed() {
                        // TODO: once we move to only XCUITest-based (which is force on you in either iOS 10+ or Xcode 8+)
                        // UI tests, we will have to:
                        // a) remove use of autoAcceptAlerts appium capability since it no longer functions in XCUITest
                        // b) can remove this entire then() clause, as we do not need to explicitly handle the acceptAlert
                        //    failure callback, since we will be guaranteed to hit the permission dialog on startup.
                    }, function noAlert() {
                        // in case the contacts permission alert never showed up: no problem, don't freak out.
                        // This can happen if:
                        // a) The application-under-test already had photos permissions granted to it
                        // b) Appium's autoAcceptAlerts capability is provided (and functioning)
                    })
                    .elementByAccessibilityId('Cancel', 10000)
                    .click();
            })
            .then(function () {
                failedToStart = false;
            });
    }

    function checkSession(done) {
        if (failedToStart) {
            fail('Failed to start a session');
            done();
        }
    }

    it('camera.ui.util configure driver and start a session', function (done) {
        getDriver()
            .fail(fail)
            .done(done);
    }, 15 * MINUTE);

    describe('Specs.', function () {
        afterEach(function (done) {
            if (specsRun >= 15) {
                specsRun = 0;
                // we need to restart the session regularly because for some reason
                // when running against iOS 10 simulator on SauceLabs, 
                // Appium cannot handle more than ~20 specs at one session
                // the error would be as follows:
                // "Could not proxy command to remote server. Original error: Error: connect ECONNREFUSED 127.0.0.1:8100"
                checkSession(done);
                return driver
                    .quit()
                    .then(function () {
                        return getDriver();
                    })
                    .done(done);
            } else {
                done();
            }
        }, 15 * MINUTE);

        // getPicture() with mediaType: VIDEO, sourceType: PHOTOLIBRARY
        it('camera.ui.spec.1 Selecting only videos', function (done) {
            checkSession(done);
            specsRun += 1;
            var options = { sourceType: cameraConstants.PictureSourceType.PHOTOLIBRARY,
                            mediaType: cameraConstants.MediaType.VIDEO };
            driver
                // skip ui unteractions
                .then(function () { return getPicture(options, false, true); })
                .waitForElementByXPath('//*[contains(@label,"Videos")]', MINUTE / 2)
                .elementByAccessibilityId('Cancel')
                .click()
                .fail(gracefullyFail)
                .done(done);
        }, 7 * MINUTE);

        // getPicture(), then dismiss
        // wait for the error callback to be called
        it('camera.ui.spec.2 Dismissing the camera', function (done) {
            checkSession(done);
            if (!isDevice) {
                pending('Camera is not available on iOS simulator');
            }
            specsRun += 1;
            var options = { sourceType: cameraConstants.PictureSourceType.CAMERA,
                            saveToPhotoAlbum: false };
            driver
                .then(function () {
                    return getPicture(options, true);
                })
                .then(function () {
                    return checkPicture(false);
                })
                .fail(gracefullyFail)
                .done(done);
        }, 7 * MINUTE);

        it('camera.ui.spec.3 Verifying target image size, sourceType=CAMERA', function (done) {
            var options = {
                quality: 50,
                allowEdit: false,
                sourceType: cameraConstants.PictureSourceType.CAMERA,
                saveToPhotoAlbum: false,
                targetWidth: 210,
                targetHeight: 210
            };

            runSpec(options, done, pending).done(done);
        }, 7 * MINUTE);

        it('camera.ui.spec.4 Verifying target image size, sourceType=SAVEDPHOTOALBUM', function (done) {
            var options = {
                quality: 50,
                allowEdit: false,
                sourceType: cameraConstants.PictureSourceType.SAVEDPHOTOALBUM,
                saveToPhotoAlbum: false,
                targetWidth: 210,
                targetHeight: 210
            };

            runSpec(options, done, pending).done(done);
        }, 7 * MINUTE);

        it('camera.ui.spec.5 Verifying target image size, sourceType=PHOTOLIBRARY', function (done) {
            var options = {
                quality: 50,
                allowEdit: false,
                sourceType: cameraConstants.PictureSourceType.PHOTOLIBRARY,
                saveToPhotoAlbum: false,
                targetWidth: 210,
                targetHeight: 210
            };

            runSpec(options, done, pending).done(done);
        }, 7 * MINUTE);

        it('camera.ui.spec.6 Verifying target image size, sourceType=CAMERA, destinationType=FILE_URL', function (done) {
            // remove this line if you don't mind the tests leaving a photo saved on device
            pending('Cannot prevent iOS from saving the picture to photo library');

            var options = {
                quality: 50,
                allowEdit: false,
                sourceType: cameraConstants.PictureSourceType.CAMERA,
                destinationType: cameraConstants.DestinationType.FILE_URL,
                saveToPhotoAlbum: false,
                targetWidth: 210,
                targetHeight: 210
            };

            runSpec(options, done, pending).done(done);
        }, 7 * MINUTE);

        it('camera.ui.spec.7 Verifying target image size, sourceType=SAVEDPHOTOALBUM, destinationType=FILE_URL', function (done) {
            var options = {
                quality: 50,
                allowEdit: false,
                sourceType: cameraConstants.PictureSourceType.SAVEDPHOTOALBUM,
                destinationType: cameraConstants.DestinationType.FILE_URL,
                saveToPhotoAlbum: false,
                targetWidth: 210,
                targetHeight: 210
            };

            runSpec(options, done, pending).done(done);
        }, 7 * MINUTE);

        it('camera.ui.spec.8 Verifying target image size, sourceType=PHOTOLIBRARY, destinationType=FILE_URL', function (done) {
            var options = {
                quality: 50,
                allowEdit: false,
                sourceType: cameraConstants.PictureSourceType.PHOTOLIBRARY,
                destinationType: cameraConstants.DestinationType.FILE_URL,
                saveToPhotoAlbum: false,
                targetWidth: 210,
                targetHeight: 210
            };

            runSpec(options, done, pending).done(done);
        }, 7 * MINUTE);

        it('camera.ui.spec.9 Verifying target image size, sourceType=CAMERA, destinationType=FILE_URL, quality=100', function (done) {
            // remove this line if you don't mind the tests leaving a photo saved on device
            pending('Cannot prevent iOS from saving the picture to photo library');

            var options = {
                quality: 100,
                allowEdit: false,
                sourceType: cameraConstants.PictureSourceType.CAMERA,
                destinationType: cameraConstants.DestinationType.FILE_URL,
                saveToPhotoAlbum: false,
                targetWidth: 305,
                targetHeight: 305
            };
            runSpec(options, done, pending).done(done);
        }, 7 * MINUTE);

        it('camera.ui.spec.10 Verifying target image size, sourceType=SAVEDPHOTOALBUM, destinationType=FILE_URL, quality=100', function (done) {
            var options = {
                quality: 100,
                allowEdit: false,
                sourceType: cameraConstants.PictureSourceType.SAVEDPHOTOALBUM,
                destinationType: cameraConstants.DestinationType.FILE_URL,
                saveToPhotoAlbum: false,
                targetWidth: 305,
                targetHeight: 305
            };

            runSpec(options, done, pending).done(done);
        }, 7 * MINUTE);

        it('camera.ui.spec.11 Verifying target image size, sourceType=PHOTOLIBRARY, destinationType=FILE_URL, quality=100', function (done) {
            var options = {
                quality: 100,
                allowEdit: false,
                sourceType: cameraConstants.PictureSourceType.PHOTOLIBRARY,
                destinationType: cameraConstants.DestinationType.FILE_URL,
                saveToPhotoAlbum: false,
                targetWidth: 305,
                targetHeight: 305
            };

            runSpec(options, done, pending).done(done);
        }, 7 * MINUTE);

        // combine various options for getPicture()
        generateOptions().forEach(function (spec) {
            it('camera.ui.spec.12.' + spec.id + ' Combining options. ' + spec.description, function (done) {
                // remove this check if you don't mind the tests leaving a photo saved on device
                if (spec.options.sourceType === cameraConstants.PictureSourceType.CAMERA &&
                    spec.options.destinationType === cameraConstants.DestinationType.NATIVE_URI) {
                    pending('Skipping: cannot prevent iOS from saving the picture to photo library and cannot delete it. ' +
                        'For more info, see iOS quirks here: https://github.com/apache/cordova-plugin-camera#ios-quirks-1');
                }

                runSpec(spec.options, done, pending).done(done);
            }, 7 * MINUTE);
        });

    });

    it('camera.ui.util Destroy the session', function (done) {
        checkSession(done);
        driver
            .quit()
            .done(done);
    }, 5 * MINUTE);
});
