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

// Cordova contact definition: 
// http://cordova.apache.org/docs/en/2.5.0/cordova_contacts_contacts.md.html#Contact
// FxOS contact definition:
// https://developer.mozilla.org/en-US/docs/Web/API/mozContact


var Contact = require('./Contact');
var ContactField = require('./ContactField');
var ContactName = require('./ContactName');

// XXX: a hack to check if id is "empty". Cordova inserts a
// string "this string is supposed to be a unique identifier that will 
// never show up on a device" if id is empty
function _hasId(id) {
    if (!id || id.indexOf(' ') >= 0) {
        return false;
    }
    return true;
}

// Extend mozContact prototype to provide update from Cordova
function updateFromCordova(contact, fromContact) {

    function exportContactFieldArray(contactFieldArray, key) {
        if (!key) {
            key = 'value';
        }
        var arr = [];
        for (var i=0; i < contactFieldArray.length; i++) {
            arr.push(contactFieldArray[i][key]);
        }
        return arr;
    }

    function exportAddress(addresses) {
        // TODO: check moz address format
        var arr = [];
        
        for (var i=0; i < addresses.length; i++) {
            var addr = {};
            for (var key in addresses[i]) {
                if (key == 'formatted' || key == 'id') {
                    continue;
                } else if (key == 'type') {
                    addr[key] = [addresses[i][key]];
                } else if (key == 'country') {
                    addr.countryName = addresses[i][key];
                } else {
                    addr[key] = addresses[i][key];
                }
            }
            arr.push(addr);
        }
        return arr;
    }

    function exportContactField(data) {
        var contactFields = [];
        for (var i=0; i < data.length; i++) {
            var item = data[i];
            if (item.value) {
                var itemData = {value: item.value};
                if (item.type) {
                    itemData.type = [item.type];
                }
                if (item.pref) {
                    itemData.pref = item.pref;
                }
                contactFields.push(itemData);
            }
        }
        return contactFields;
    }
    // adding simple fields [contactField, eventualMozContactField]
    var nameFields = [['givenName'], ['familyName'],  
                      ['honorificPrefix'], ['honorificSuffix'],
                      ['middleName', 'additionalName']];
    var baseArrayFields = [['displayName', 'name'], ['nickname']];
    var baseStringFields = [];
    var j = 0, field; while(field = nameFields[j++]) { // jshint ignore:line
      if (fromContact.name[field[0]]) {
        contact[field[1] || field[0]] = fromContact.name[field[0]].split(' ');
      }
    }
    j = 0; while(field = baseArrayFields[j++]) { // jshint ignore:line
      if (fromContact[field[0]]) {
        contact[field[1] || field[0]] = fromContact[field[0]].split(' ');
      }
    }
    j = 0; while(field = baseStringFields[j++]) { // jshint ignore:line
      if (fromContact[field[0]]) {
        contact[field[1] || field[0]] = fromContact[field[0]];
      }
    }
    if (fromContact.birthday) {
      contact.bday = new Date(fromContact.birthday);
    }
    if (fromContact.emails) {
        var emails = exportContactField(fromContact.emails);
        contact.email = emails;
    }
    if (fromContact.categories) {
        contact.category = exportContactFieldArray(fromContact.categories);
    }
    if (fromContact.addresses) {
        contact.adr = exportAddress(fromContact.addresses);
    }
    if (fromContact.phoneNumbers) {
        contact.tel = exportContactField(fromContact.phoneNumbers);
    }
    if (fromContact.organizations) {
        // XXX: organizations are saved in 2 arrays - org and jobTitle
        //      depending on the usecase it might generate issues
        //      where wrong title will be added to an organization
        contact.org = exportContactFieldArray(fromContact.organizations, 'name');
        contact.jobTitle = exportContactFieldArray(fromContact.organizations, 'title');
    }
    if (fromContact.note) {
        contact.note = [fromContact.note];
    }
}


// Extend Cordova Contact prototype to provide update from FFOS contact
Contact.prototype.updateFromMozilla = function(moz) {
    function exportContactField(data) {
        var contactFields = [];
        for (var i=0; i < data.length; i++) {
            var item = data[i];
            var itemData = new ContactField(item.type, item.value, item.pref);
            contactFields.push(itemData);
        }
        return contactFields;
    }

    function makeContactFieldFromArray(data) {
        var contactFields = [];
        for (var i=0; i < data.length; i++) {
            var itemData = new ContactField(null, data[i]);
            contactFields.push(itemData);
        }
        return contactFields;
    }

    function exportAddresses(addresses) {
        // TODO: check moz address format
        var arr = [];
        
        for (var i=0; i < addresses.length; i++) {
            var addr = {};
            for (var key in addresses[i]) {
                if (key == 'countryName') {
                    addr.country = addresses[i][key];
                } else if (key == 'type') {
                    addr[key] = addresses[i][key].join(' ');
                } else {
                    addr[key] = addresses[i][key];
                }
            }
            arr.push(addr);
        }
        return arr;
    }

    function createOrganizations(orgs, jobs) {
        orgs = (orgs) ? orgs : [];
        jobs = (jobs) ? jobs : [];
        var max_length = Math.max(orgs.length, jobs.length);
        var organizations = [];
        for (var i=0; i < max_length; i++) {
            organizations.push(new ContactOrganization(
                  null, null, orgs[i] || null, null, jobs[i] || null));
        }
        return organizations;
    }

    function createFormatted(name) {
        var fields = ['honorificPrefix', 'givenName', 'middleName', 
                      'familyName', 'honorificSuffix'];
        var f = '';
        for (var i = 0; i < fields.length; i++) {
            if (name[fields[i]]) {
                if (f) {
                    f += ' ';
                }
                f += name[fields[i]];
            }
        }
        return f;
    }


    if (moz.id) {
        this.id = moz.id;
    }
    var nameFields = [['givenName'], ['familyName'], 
                       ['honorificPrefix'], ['honorificSuffix'],
                       ['additionalName', 'middleName']];
    var baseArrayFields = [['name', 'displayName'], 'nickname', ['note']];
    var baseStringFields = [];
    var name = new ContactName();
    var j = 0, field; while(field = nameFields[j++]) { // jshint ignore:line
        if (moz[field[0]]) {
            name[field[1] || field[0]] = moz[field[0]].join(' ');
        }
    }
    this.name = name;
    j = 0; while(field = baseArrayFields[j++]) { // jshint ignore:line
        if (moz[field[0]]) {
            this[field[1] || field[0]] = moz[field[0]].join(' ');
        }
    }
    j = 0; while(field = baseStringFields[j++]) { // jshint ignore:line
        if (moz[field[0]]) {
            this[field[1] || field[0]] = moz[field[0]];
        }
    }
    // emails
    if (moz.email) {
        this.emails = exportContactField(moz.email);
    }
    // categories
    if (moz.category) {
        this.categories = makeContactFieldFromArray(moz.category);
    }

    // addresses
    if (moz.adr) {
        this.addresses = exportAddresses(moz.adr);
    }

    // phoneNumbers
    if (moz.tel) {
        this.phoneNumbers = exportContactField(moz.tel);
    }
    // birthday
    if (moz.bday) {
      this.birthday = Date.parse(moz.bday);
    }
    // organizations
    if (moz.org || moz.jobTitle) {
        // XXX: organizations array is created from org and jobTitle
        this.organizations = createOrganizations(moz.org, moz.jobTitle);
    }
    // construct a read-only formatted value
    this.name.formatted = createFormatted(this.name);

    /*  Find out how to translate these parameters
        // photo: Blob
        // url: Array with metadata (?)
        // impp: exportIM(contact.ims), TODO: find the moz impp definition
        // anniversary
        // sex
        // genderIdentity
        // key
    */
};


function createMozillaFromCordova(successCB, errorCB, contact) {
    var moz;
    // get contact if exists
    if (_hasId(contact.id)) {
      var search = navigator.mozContacts.find({
        filterBy: ['id'], filterValue: contact.id, filterOp: 'equals'});
      search.onsuccess = function() {
        moz = search.result[0];
        updateFromCordova(moz, contact);
        successCB(moz);
      };
      search.onerror = errorCB;
      return;
    }

    // create empty contact
    moz = new mozContact();
    // if ('init' in moz) {
      // 1.2 and below compatibility
      // moz.init();
    // }
    updateFromCordova(moz, contact);
    successCB(moz);
}


function createCordovaFromMozilla(moz) {
    var contact = new Contact();
    contact.updateFromMozilla(moz);
    return contact;
}


// However API requires the ability to save multiple contacts, it is 
// used to save only one element array
function saveContacts(successCB, errorCB, contacts) {
    // a closure which is holding the right moz contact
    function makeSaveSuccessCB(moz) {
        return function(result) {
            // create contact from FXOS contact (might be different than
            // the original one due to differences in API)
            var contact = createCordovaFromMozilla(moz);
            // call callback
            successCB(contact);
        };
    }
    var i=0;
    var contact;
    /*jshint -W083 */
    while(contact = contacts[i++]) { // jshint ignore:line
        createMozillaFromCordova(function(moz) {
            var request = navigator.mozContacts.save(moz);
            // success and/or fail will be called every time a contact is saved
            request.onsuccess = makeSaveSuccessCB(moz);
            request.onerror = errorCB;
        }, function() {}, contact);
    }
    /*jshint +W083 */
}


// API provides a list of ids to be removed
function remove(successCB, errorCB, ids) {
    var i;
    /*jshint -W083 */
    for (i = 0; i < ids.length; i++){
        // throw an error if no id provided
        if (!_hasId(ids[i])) {
            console.error('FFOS: Attempt to remove unsaved contact');
            errorCB(0);
            return;
        }
        // check if provided id actually exists 
        var search = navigator.mozContacts.find({
            filterBy: ['id'], filterValue: ids[i], filterOp: 'equals'});
        search.onsuccess = function() {
            if (search.result.length === 0) {
                console.error('FFOS: Attempt to remove a non existing contact');
                errorCB(0);
                return;
            }
            var moz = search.result[0];
            var request = navigator.mozContacts.remove(moz);
            request.onsuccess = successCB;
            request.onerror = errorCB;
        };
        search.onerror = errorCB;
    }
    /*jshint +W083 */
}


var mozContactSearchFields = [['name', 'displayName'], ['givenName'], 
    ['familyName'], ['email'], ['tel'], ['jobTitle'], ['note'], 
    ['tel', 'phoneNumbers'], ['email', 'emails']]; 
// Searching by nickname and additionalName is forbidden in  1.3 and below
// Searching by name is forbidden in 1.2 and below

// finds if a key is allowed and returns FFOS name if different
function getMozSearchField(key) {
    if (mozContactSearchFields.indexOf([key]) >= 0) {
        return key;
    }
    for (var i=0; i < mozContactSearchFields.length; i++) {
        if (mozContactSearchFields[i].length > 1) {
            if (mozContactSearchFields[i][1] === key) {
                return mozContactSearchFields[i][0];
            }
        }
    }
    return false;
}


function _getAll(successCB, errorCB, params) {
    // [contactField, eventualMozContactField]
    var getall = navigator.mozContacts.getAll({});
    var contacts = [];

    getall.onsuccess = function() {
        if (getall.result) {
            contacts.push(createCordovaFromMozilla(getall.result));
            getall.continue();
        } else {
            successCB(contacts);
        }
    };
    getall.onerror = errorCB;
}


function search(successCB, errorCB, params) {
    var options = params[1] || {}; 
    if (!options.filter) {
        return _getAll(successCB, errorCB, params);
    }
    var filterBy = [];
    // filter and translate fields
    for (var i=0; i < params[0].length; i++) {
        var searchField = params[0][i];
        var mozField = getMozSearchField(searchField);
        if (searchField === 'name') {
            // Cordova uses name for search by all name fields.
            filterBy.push('givenName');
            filterBy.push('familyName');
            continue;
        } 
        if (searchField === 'displayName' && 'init' in new mozContact()) {
            // ``init`` in ``mozContact`` indicates FFOS version 1.2 or below
            // Searching by name (in moz) is then forbidden
            console.log('FFOS ContactProxy: Unable to search by displayName on FFOS 1.2');
            continue;
        } 
        if (mozField) {
            filterBy.push(mozField);
        } else {
            console.log('FXOS ContactProxy: inallowed field passed to search filtered out: ' + searchField);
        }
    }

    var mozOptions = {filterBy: filterBy, filterOp: 'startsWith'};
    if (!options.multiple) {
        mozOptions.filterLimit = 1;
    }
    mozOptions.filterValue = options.filter;
    var request = navigator.mozContacts.find(mozOptions);
    request.onsuccess = function() {
        var contacts = [];
        var mozContacts = request.result;
        for (var i=0; i < mozContacts.length; i++) {
            contacts.push(createCordovaFromMozilla(mozContacts[i]));
        }
        successCB(contacts);
    };
    request.onerror = errorCB;
}


module.exports = {
    save: saveContacts,
    remove: remove,
    search: search
};    
    
require("cordova/exec/proxy").add("Contacts", module.exports);