/*
 * Copyright (c) 2015 Samsung Electronics Co., Ltd. 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.
 */

/*global define, document, console, tau */

/**
 * Edit exif view module.
 * @namespace JPEGExifEditor/views/editExif
 * @memberof JPEGExifEditor/views
 * @requires {@link core/tizen}
 * @requires {@link core/template}
 * @requires {@link core/event}
 * @requires {@link JPEGExifEditor/models/exif}
 * @requires {@link JPEGExifEditor/helpers/notification}
 * @requires {@link JPEGExifEditor/helpers/validateDouble}
 */
define({
    name: 'views/editExif',
    requires: [
        'core/tizen',
        'core/template',
        'core/event',
        'models/exif',
        'helpers/notification',
        'helpers/validateDouble'
    ],
    def: function chooseImageModule(req) {
        'use strict';

        /**
         * Core tizen module.
         * @type {Module}
         */
        var tizen = req.core.tizen,

            /**
             * Core template module.
             * @type {Module}
             */
            template = req.core.template,

            /**
             * Core event module.
             * @type {Module}
             */
            event = req.core.event,

            /**
             * Exif module.
             * @type {Module}
             */
            exif = req.models.exif,

            /**
             * Popup helper module.
             * @type {Module}
             */
            notification = req.helpers.notification,

            /**
             * Popup helper module.
             * @type {Module}
             */
            validateDouble = req.helpers.validateDouble,

            /**
             * Reference to the width input.
             * @type {HTMLElement}
             */
            widthInput = null,

            /**
             * Reference to the height input.
             * @type {HTMLElement}
             */
            heightInput = null,

            /**
             * Reference to the manufacturer input.
             * @type {HTMLElement}
             */
            manufacturerInput = null,

            /**
             * Reference to the model input.
             * @type {HTMLElement}
             */
            modelInput = null,

            /**
             * Reference to the creation date input.
             * @type {HTMLElement}
             */
            dateInput = null,

            /**
             * Reference to the orientation select.
             * @type {HTMLElement}
             */
            orientationSelect = null,

            /**
             * Reference to the f-number input.
             * @type {HTMLElement}
             */
            fNumberInput = null,

            /**
             * Reference to the ISO speed list.
             * @type {HTMLElement}
             */
            isoSpeedList = null,

            /**
             * Reference to the exposure time input.
             * @type {HTMLElement}
             */
            exposureTimeInput = null,

            /**
             * Reference to the exposure program select.
             * @type {HTMLElement}
             */
            exposureProgramSelect = null,

            /**
             * Reference to the focal length input.
             * @type {HTMLElement}
             */
            focalLengthInput = null,

            /**
             * Reference to the white balance select.
             * @type {HTMLElement}
             */
            whiteBalanceSelect = null,

            /**
             * Reference to the latitude input.
             * @type {HTMLElement}
             */
            latitudeInput = null,

            /**
             * Reference to the longitude input.
             * @type {HTMLElement}
             */
            longitudeInput = null,

            /**
             * Reference to the altitude input.
             * @type {HTMLElement}
             */
            altitudeInput = null,

            /**
             * Reference to the GPS processing method input.
             * @type {HTMLElement}
             */
            gpsProcessingMethodInput = null,

            /**
             * Reference to the GPS datetime input input.
             * @type {HTMLElement}
             */
            gpsDateTimeInput = null,

            /**
             * Reference to the user comment text area.
             * @type {HTMLElement}
             */
            userCommentTextArea = null,

            /**
             * Reference to the 'Save' button.
             * @type {HTMLElement}
             */
            saveButton = null,

            /**
             * Reference to the 'Add ISO speed' button.
             * @type {HTMLElement}
             */
            addIsoSpeedButton = null,

            /**
             * Reference to the Edit page header.
             * @type {HTMLElement}
             */
            titleHeader = null,

            /**
             * Reference to the list of invalid fields in the popup.
             * @type {HTMLElement}
             */
            invalidFieldsList = null,

            /**
             * Reference to the invalid fields popup.
             * @type {HTMLElement}
             */
            invalidFieldsPopup = null,

            /**
             * Exif information object.
             * @type {ExifInformation}
             */
            exifInfo = null,

            /**
             * Reference to the page element.
             * @type {HTMLElement}
             */
            page = null,

            /**
             * Array of objects containing relation between an input element and
             * an exif field.
             * @type {array.<{input: HTMLElement, field: string}>}
             */
            inputsAndExifFieldsMapping = null;

        /**
         * Sets the specified value to the specified Select Menu widget.
         * @param {object} selectMenuWidget
         * @param {string} value
         */
        function setSelectMenuValue(selectMenuWidget, value) {
            var options = selectMenuWidget.querySelectorAll('option'),
                length = options.length,
                i = 0;

            for (i = 0; i < length; i += 1) {
                if (options[i].value === value) {
                    selectMenuWidget.selectedIndex = i;

                    return;
                }
            }
            selectMenuWidget.selectedIndex = 0;
        }

        /**
         * Returns a HTML content of an item of the ISO speed list.
         * @param {string} initialValue Initial value of the text input placed
         * in the returned list item.
         * @returns {string}
         */
        function createIsoSpeedItemHTML(initialValue) {
            return template.get('isoSpeed', {value: initialValue});
        }

        /**
         * Returns a HTML content of the ISO speed list.
         * @returns {string}
         */
        function getIsoSpeedListContent() {
            var isoSpeedArray = exifInfo.isoSpeedRatings || [],
                length = isoSpeedArray.length,
                i = 0,
                result = [];

            for (i = 0; i < length; i += 1) {
                result.push(createIsoSpeedItemHTML(isoSpeedArray[i]));
            }

            return result.join('');
        }

        /**
         * Handles click events on the Delete buttons placed in items of the
         * ISO speed list.
         * @param {Event} event
         */
        function onDeleteIsoSpeedClick(event) {
            var liElement = event.currentTarget.parentNode;

            liElement.parentNode.removeChild(liElement);
        }

        /**
         * Registers view event listeners for controls from the ISO speed list.
         */
        function bindIsoSpeedListEvents() {
            var clearButtons =
                    isoSpeedList.querySelectorAll('[data-role="button"]'),
                length = clearButtons.length,
                i = 0;

            for (i = 0; i < length; i += 1) {
                clearButtons[i].addEventListener('click',
                    onDeleteIsoSpeedClick);
            }
        }

        /**
         * Fills text inputs with data from exif info.
         */
        function fillTextInputs() {
            var mapping = null,
                length = inputsAndExifFieldsMapping.length,
                i = 0;

            for (i = 0; i < length; i += 1) {
                mapping = inputsAndExifFieldsMapping[i];
                mapping.input.value = exifInfo[mapping.field] || '';
            }
        }

        /**
         * Fills inputs related to the GPS location with data taken from the
         * exifInfo object.
         */
        function fillGpsLocationInputs() {
            if (!exifInfo.gpsLocation) {
                latitudeInput.value = '';
                longitudeInput.value = '';
            } else {
                latitudeInput.value = exifInfo.gpsLocation.latitude;
                longitudeInput.value = exifInfo.gpsLocation.longitude;
            }
        }

        /**
         * Fills the ISO speed list with data obtained from the read exif info
         * object.
         */
        function loadIsoSpeed() {
            isoSpeedList.innerHTML = getIsoSpeedListContent();
            tau.engine.createWidgets(isoSpeedList);
            tau.widget.Listview(isoSpeedList).refresh();
            bindIsoSpeedListEvents();
        }

        /**
         * Creates the string representation of the specified Date object in
         * the following format: yyyy-MM-dd.
         * @param {Date} date
         * @returns {string}
         */
        function formatDate(date) {
            if (!date) {
                return '';
            }
            return date.toISOString().substring(0, 10);
        }

        /**
         * Returns a string representation of the specified number. If the
         * number is a digit, returned value is padded with a leading zero.
         * @param {number} number
         * @returns {string}
         */
        function zeroPad(number) {
            if (number < 10) {
                return '0' + number;
            }
            return number.toString();
        }

        /**
         * Creates the string representation of the specified TZDate object in
         * the following format: yyyy-MM-dd.
         * @param {TZDate} date
         * @returns {string}
         */
        function formatTZDate(date) {
            if (!date) {
                return '';
            }
            return date.getFullYear() + '-' +
                zeroPad(date.getMonth() + 1) + '-' +
                zeroPad(date.getDate());
        }

        /**
         * Returns the title of the screen.
         * @returns {string}
         */
        function getTitle() {
            var title = exifInfo.uri;
            if (title.indexOf('/') !== -1) {
                return title.substring(title.lastIndexOf('/') + 1);
            }
            return title;
        }

        /**
         * Fills input fields with the specified Exif info.
         * @param {ExifInformation} info
         */
        function loadExif(info) {
            exifInfo = info;
            fillTextInputs();
            fillGpsLocationInputs();
            dateInput.value = formatDate(exifInfo.originalTime);
            gpsDateTimeInput.value = formatTZDate(exifInfo.gpsTime);
            setSelectMenuValue(orientationSelect, exifInfo.orientation);
            setSelectMenuValue(exposureProgramSelect, exifInfo.exposureProgram);
            setSelectMenuValue(whiteBalanceSelect, exifInfo.whiteBalance);
            loadIsoSpeed();
            titleHeader.innerText = getTitle();
        }

        /**
         * Handles select image events.
         * Fills input fields with Exif info extracted from the selected image.
         * @param {Event} event
         */
        function onImageSelected(event) {
            var imageUri = event.detail;

            exif.readExifInfo(imageUri, loadExif, function onError(e) {
                if (e.type === 'NotFoundError') {
                    loadExif(exif.createExifInfo(imageUri));
                } else {
                    console.error(e.message);
                }
            });
        }

        /**
         * Creates HTMLElement from the specified HTML string.
         * @param {string} html
         * @returns {HTMLElement}
         */
        function createElementFromHTML(html) {
            var container = document.createElement('div');
            container.innerHTML = html;
            return container.firstChild;
        }

        /**
         * Handles click events on the 'Add iso speed' button.
         */
        function onAddIsoSpeedClick() {
            var newItem = createElementFromHTML(createIsoSpeedItemHTML(0));
            isoSpeedList.appendChild(newItem);
            newItem.querySelector('.ui-btn')
                .addEventListener('click', onDeleteIsoSpeedClick);
            tau.engine.createWidgets(isoSpeedList);
            tau.widget.Listview(isoSpeedList).refresh();
            bindIsoSpeedListEvents();
        }

        /**
         * Handles the 'pageshow' event on the page.
         */
        function onPageShow() {
            validateDouble.clear();
            page.querySelector('.ui-scrollview-clip').scrollTop = 0;
        }

        /**
         * Updates the exifInfo fields with values taken from text inputs
         * related to these fields.
         * Relation between a text input and a exifInfo field is taken from the
         * inputsAndExifFieldsMapping array.
         */
        function setValuesFromInputs() {
            var mapping = null,
                length = inputsAndExifFieldsMapping.length,
                i = 0;

            for (i = 0; i < length; i += 1) {
                mapping = inputsAndExifFieldsMapping[i];
                exifInfo[mapping.field] =
                    mapping.input.value.trim().length > 0 ?
                        mapping.input.value : null;
            }
        }

        /**
         * Sets the GPS location values taken from the suitable inputs to the
         * exifInfo.gpsLocation field.
         */
        function setGpsLocation() {
            var latitude = parseFloat(latitudeInput.value),
                longitude = parseFloat(longitudeInput.value);

            if (isNaN(latitude) && isNaN(longitude)) {
                exifInfo.gpsLocation = null;
            } else {
                exifInfo.gpsLocation =
                    new tizen.SimpleCoordinates(latitude, longitude);
            }
        }

        /**
         * Sets the value from the specified date field to the specified field
         * of the exifInfo object.
         * @param {HTMLElement} dateInput Date input.
         * @param {string} fieldName Field name.
         * @param {boolean} [isTZDate] indicates whether the field is of type
         * TZDate.
         */
        function setValueFromDateInput(dateInput, fieldName, isTZDate) {
            var time = Date.parse(dateInput.value),
                date = !isNaN(time) ? new Date(time) : null;

            if (!date) {
                exifInfo[fieldName] = null;
            } else if (isTZDate) {
                exifInfo[fieldName] = new tizen.TZDate(date);
            } else {
                exifInfo[fieldName] = date;
            }
        }

        /**
         * Sets the value from the specified SelectMenu element to the specified
         * field of the exifInfo object.
         * @param {HTMLElement} selectMenu
         * @param {string} fieldName
         */
        function setValueFromSelectMenu(selectMenu, fieldName) {
            var selectedOption = selectMenu.options[selectMenu.selectedIndex];

            exifInfo[fieldName] = selectedOption ? selectedOption.value : '';
        }

        /**
         * Sets ISO speed ratings from page inputs to the
         * exifInfo.isoSpeedRatings array.
         */
        function updateIsoSpeed() {
            var inputs = isoSpeedList.querySelectorAll('input'),
                length = inputs.length,
                i = 0,
                result = [];

            for (i = 0; i < length; i += 1) {
                result.push(parseInt(inputs[i].value, 10));
            }

            exifInfo.isoSpeedRatings = result;
        }

        /**
         * Updates the exifInfo object.
         * Sets exif data from all page inputs to the exifInfo object.
         */
        function updateExifInfo() {
            setValuesFromInputs();
            setGpsLocation();
            setValueFromDateInput(dateInput, 'originalTime');
            setValueFromDateInput(gpsDateTimeInput, 'gpsTime', true);
            setValueFromSelectMenu(orientationSelect, 'orientation');
            setValueFromSelectMenu(exposureProgramSelect, 'exposureProgram');
            setValueFromSelectMenu(whiteBalanceSelect, 'whiteBalance');
            updateIsoSpeed();
        }

        /**
         * Prepares the validation error popup.
         * Fills the list of invalid fields with the specified field labels.
         * @param {string[]} fieldLabels
         */
        function prepareValidationErrorPopup(fieldLabels) {
            var length = fieldLabels.length, i = 0,
                htmlContent = '';

            for (i = 0; i < length; i += 1) {
                htmlContent += '<li>' + fieldLabels[i] + '</li>';
            }

            invalidFieldsList.innerHTML = htmlContent;
        }

        /**
         * Opens the validation error popup.
         */
        function openValidationErrorPopup() {
            tau.widget.Popup(invalidFieldsPopup).open();
        }

        /**
         * Returns label texts for inputs with the specified ids.
         * @param {string[]} inputIds
         * @returns {string[]}
         */
        function getFieldLabels(inputIds) {
            var i = 0,
                length = inputIds.length,
                label = null,
                result = [];

            for (i = 0; i < length; i += 1) {
                label = document.querySelector('[for="' + inputIds[i] + '"]');
                if (label) {
                    result.push(label.innerText);
                } else {
                    result.push(inputIds[i]);
                }
            }

            return result;
        }

        /**
         * Handles click events on the 'Save' button.
         */
        function onSaveClick() {
            var invalidFields = validateDouble.validate(),
                fieldLabels = [];
            if (invalidFields.length) {
                fieldLabels = getFieldLabels(invalidFields);
                prepareValidationErrorPopup(fieldLabels);
                openValidationErrorPopup();
            } else {
                updateExifInfo();
                exif.saveExifInfo(exifInfo, onSaveSucceed, onSaveFail);
            }
        }

        /**
         * Handles save succeeded events.
         */
        function onSaveSucceed() {
            notification.showSuccess();
        }

        /**
         * Handles save failed events.
         * @param {Error} error
         */
        function onSaveFail(error) {
            notification.showError(error.message);
        }

        /**
         * Registers view event listeners.
         */
        function bindEvents() {
            event.listen({
                'views.selectImage.selected': onImageSelected
            });
            addIsoSpeedButton.addEventListener('click', onAddIsoSpeedClick);
            page.addEventListener('pageshow', onPageShow);
            saveButton.addEventListener('click', onSaveClick);
        }

        /**
         * Initializes the module.
         * @memberof JPEGExifEditor/views/editExif
         */
        function init() {
            page = document.getElementById('edit');
            widthInput = document.getElementById('width');
            heightInput = document.getElementById('height');
            manufacturerInput = document.getElementById('manufacturer');
            modelInput = document.getElementById('model');
            dateInput = document.getElementById('date');
            orientationSelect = document.getElementById('orientation');
            fNumberInput = document.getElementById('fNumber');
            isoSpeedList = document.getElementById('isoSpeed');
            exposureTimeInput = document.getElementById('exposureTime');
            exposureProgramSelect = document.getElementById('exposureProgram');
            focalLengthInput = document.getElementById('focalLength');
            whiteBalanceSelect = document.getElementById('whiteBalance');
            latitudeInput = document.getElementById('latitude');
            longitudeInput = document.getElementById('longitude');
            altitudeInput = document.getElementById('altitude');
            gpsProcessingMethodInput =
                document.getElementById('gpsProcessingMethod');
            gpsDateTimeInput = document.getElementById('gpsDateTime');
            userCommentTextArea = document.getElementById('userComment');
            saveButton = document.getElementById('save');
            addIsoSpeedButton = document.getElementById('addIsoSpeedElement');
            titleHeader = document.querySelector('#edit .ui-header h1');
            invalidFieldsList = document.getElementById('invalidFieldsList');
            invalidFieldsPopup = document.getElementById('invalidFields');

            inputsAndExifFieldsMapping = [
                {input: widthInput, field: 'width'},
                {input: heightInput, field: 'height'},
                {input: manufacturerInput, field: 'deviceMaker'},
                {input: modelInput, field: 'deviceModel'},
                {input: fNumberInput, field: 'fNumber'},
                {input: exposureTimeInput, field: 'exposureTime'},
                {input: focalLengthInput, field: 'focalLength'},
                {input: altitudeInput, field: 'gpsAltitude'},
                {input: userCommentTextArea, field: 'userComment'},
                {input: gpsProcessingMethodInput, field: 'gpsProcessingMethod'}
            ];

            bindEvents();
        }

        return {
            init: init
        };
    }
});
