/*
 * Copyright 2013 Research In Motion Limited.
 *
 * 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 PluginResult, JNEXT, escape */

var pimContacts,
    ContactError = require("./ContactError"),
    ContactName = require("./ContactName"),
    ContactFindOptions = require("./ContactFindOptions");

function populateSearchFields(fields) {
    var i,
        l,
        key,
        searchFieldsObject = {},
        searchFields = [];

    for (i = 0, l = fields.length; i < l; i++) {
        if (fields[i] === "*") {
            searchFieldsObject[ContactFindOptions.SEARCH_FIELD_GIVEN_NAME] = true;
            searchFieldsObject[ContactFindOptions.SEARCH_FIELD_FAMILY_NAME] = true;
            searchFieldsObject[ContactFindOptions.SEARCH_FIELD_PHONE] = true;
            searchFieldsObject[ContactFindOptions.SEARCH_FIELD_EMAIL] = true;
            searchFieldsObject[ContactFindOptions.SEARCH_FIELD_ORGANIZATION_NAME] = true;
        } else if (fields[i] === "displayName" || fields[i] === "name") {
            searchFieldsObject[ContactFindOptions.SEARCH_FIELD_GIVEN_NAME] = true;
            searchFieldsObject[ContactFindOptions.SEARCH_FIELD_FAMILY_NAME] = true;
        } else if (fields[i] === "nickname") {
            // not supported by Cascades
        } else if (fields[i] === "phoneNumbers") {
            searchFieldsObject[ContactFindOptions.SEARCH_FIELD_PHONE] = true;
        } else if (fields[i] === "emails") {
            searchFieldsObject[ContactFindOptions.SEARCH_FIELD_EMAIL] = true;
        } else if (fields[i] === "addresses") {
            // not supported by Cascades
        } else if (fields[i] === "ims") {
            // not supported by Cascades
        } else if (fields[i] === "organizations") {
            searchFieldsObject[ContactFindOptions.SEARCH_FIELD_ORGANIZATION_NAME] = true;
        } else if (fields[i] === "birthday") {
            // not supported by Cascades
        } else if (fields[i] === "note") {
            // not supported by Cascades
        } else if (fields[i] === "photos") {
            // not supported by Cascades
        } else if (fields[i] === "categories") {
            // not supported by Cascades
        } else if (fields[i] === "urls") {
            // not supported by Cascades
        }
    }

    for (key in searchFieldsObject) {
        if (searchFieldsObject.hasOwnProperty(key)) {
            searchFields.push(window.parseInt(key));
        }
    }

    return searchFields;
}

function convertBirthday(birthday) {
    //Convert date string from native to milliseconds since epoch for cordova-js
    var birthdayInfo;
    if (birthday) {
        birthdayInfo = birthday.split("-");
        return new Date(birthdayInfo[0], birthdayInfo[1] - 1, birthdayInfo[2]).getTime();
    } else {
        return null;
    }
}

function processJnextSaveData(result, JnextData) {
    var data = JnextData;

    if (data._success === true) {
        data.birthday = convertBirthday(data.birthday);
        result.callbackOk(data, false);
    } else {
        result.callbackError(data.code, false);
    }
}

function processJnextRemoveData(result, JnextData) {
    var data = JnextData;

    if (data._success === true) {
        result.callbackOk(data);
    } else {
        result.callbackError(ContactError.UNKNOWN_ERROR, false);
    }
}

function processJnextFindData(eventId, eventHandler, JnextData) {
    var data = JnextData,
        i,
        l,
        more = false,
        resultsObject = {};

    if (data.contacts) {
        for (i = 0, l = data.contacts.length; i < l; i++) {
            data.contacts[i].birthday = convertBirthday(data.contacts[i].birthday);
            data.contacts[i].name = new ContactName(data.contacts[i].name);
        }
    } else {
        data.contacts = []; // if JnextData.contacts return null, return an empty array
    }

    if (data._success === true) {
        eventHandler.error = false;
    }

    if (eventHandler.multiple) {
        // Concatenate results; do not add the same contacts
        for (i = 0, l = eventHandler.searchResult.length; i < l; i++) {
            resultsObject[eventHandler.searchResult[i].id] = true;
        }

        for (i = 0, l = data.contacts.length; i < l; i++) {
            if (resultsObject[data.contacts[i].id]) {
                // Already existing
            } else {
                eventHandler.searchResult.push(data.contacts[i]);
            }
        }

        // check if more search is required
        eventHandler.searchFieldIndex++;
        if (eventHandler.searchFieldIndex < eventHandler.searchFields.length) {
            more = true;
        }
    } else {
        eventHandler.searchResult = data.contacts;
    }

    if (more) {
        pimContacts.getInstance().invokeJnextSearch(eventId);
    } else {
        if (eventHandler.error) {
            eventHandler.result.callbackError(data.code, false);
        } else {
            eventHandler.result.callbackOk(eventHandler.searchResult, false);
        }
    }
}

module.exports = {
    search: function (successCb, failCb, args, env) {
        var cordovaFindOptions = {},
            result = new PluginResult(args, env),
            key;

        for (key in args) {
            if (args.hasOwnProperty(key)) {
                cordovaFindOptions[key] = JSON.parse(decodeURIComponent(args[key]));
            }
        }

        pimContacts.getInstance().find(cordovaFindOptions, result, processJnextFindData);
        result.noResult(true);
    },
    save: function (successCb, failCb, args, env) {
        var attributes = {},
            result = new PluginResult(args, env),
            nativeEmails = [];

        attributes = JSON.parse(decodeURIComponent(args[0]));

        //convert birthday format for our native .so file
        if (attributes.birthday) {
            attributes.birthday = new Date(attributes.birthday).toDateString();
        }

        if (attributes.emails) {
            attributes.emails.forEach(function (email) {
                if (email.value) {
                    if (email.type) {
                        nativeEmails.push({ "type" : email.type, "value" : email.value });
                    } else {
                        nativeEmails.push({ "type" : "home", "value" : email.value });
                    }
                }
            });
            attributes.emails = nativeEmails;
        }

        if (attributes.id !== null) {
            attributes.id = window.parseInt(attributes.id);
        }

        attributes._eventId = result.callbackId;
        pimContacts.getInstance().save(attributes, result, processJnextSaveData);
        result.noResult(true);
    },
    remove: function (successCb, failCb, args, env) {
        var result = new PluginResult(args, env),
            attributes = {
                "contactId": window.parseInt(JSON.parse(decodeURIComponent(args[0]))),
                "_eventId": result.callbackId
            };

        if (!window.isNaN(attributes.contactId)) {
            pimContacts.getInstance().remove(attributes, result, processJnextRemoveData);
            result.noResult(true);
        } else {
            result.error(ContactError.UNKNOWN_ERROR);
            result.noResult(false);
        }
    }
};

///////////////////////////////////////////////////////////////////
// JavaScript wrapper for JNEXT plugin
///////////////////////////////////////////////////////////////////

JNEXT.PimContacts = function ()
{
    var self = this,
        hasInstance = false;

    self.find = function (cordovaFindOptions, pluginResult, handler) {
        //register find eventHandler for when JNEXT onEvent fires
        self.eventHandlers[cordovaFindOptions.callbackId] = {
            "result" : pluginResult,
            "action" : "find",
            "multiple" : cordovaFindOptions[1].filter ? true : false,
            "fields" : cordovaFindOptions[0],
            "searchFilter" : cordovaFindOptions[1].filter,
            "searchFields" : cordovaFindOptions[1].filter ? populateSearchFields(cordovaFindOptions[0]) : null,
            "searchFieldIndex" : 0,
            "searchResult" : [],
            "handler" : handler,
            "error" : true
        };

        self.invokeJnextSearch(cordovaFindOptions.callbackId);
        return "";
    };

    self.invokeJnextSearch = function(eventId) {
        var jnextArgs = {},
            findHandler = self.eventHandlers[eventId];

        jnextArgs._eventId = eventId;
        jnextArgs.fields = findHandler.fields;
        jnextArgs.options = {};
        jnextArgs.options.filter = [];

        if (findHandler.multiple) {
            jnextArgs.options.filter.push({
                "fieldName" : findHandler.searchFields[findHandler.searchFieldIndex],
                "fieldValue" : findHandler.searchFilter
            });
            //findHandler.searchFieldIndex++;
        }

        JNEXT.invoke(self.m_id, "find " + JSON.stringify(jnextArgs));
    };

    self.getContact = function (args) {
        return JSON.parse(JNEXT.invoke(self.m_id, "getContact " + JSON.stringify(args)));
    };

    self.save = function (args, pluginResult, handler) {
        //register save eventHandler for when JNEXT onEvent fires
        self.eventHandlers[args._eventId] = {
            "result" : pluginResult,
            "action" : "save",
            "handler" : handler
        };
        JNEXT.invoke(self.m_id, "save " + JSON.stringify(args));
        return "";
    };

    self.remove = function (args, pluginResult, handler) {
        //register remove eventHandler for when JNEXT onEvent fires
        self.eventHandlers[args._eventId] = {
            "result" : pluginResult,
            "action" : "remove",
            "handler" : handler
        };
        JNEXT.invoke(self.m_id, "remove " + JSON.stringify(args));
        return "";
    };

    self.getId = function () {
        return self.m_id;
    };

    self.getContactAccounts = function () {
        var value = JNEXT.invoke(self.m_id, "getContactAccounts");
        return JSON.parse(value);
    };

    self.init = function () {
        if (!JNEXT.require("libpimcontacts")) {
            return false;
        }

        self.m_id = JNEXT.createObject("libpimcontacts.PimContacts");

        if (self.m_id === "") {
            return false;
        }

        JNEXT.registerEvents(self);
    };

    // Handle data coming back from JNEXT native layer. Each async function registers a handler and a PluginResult object.
    // When JNEXT fires onEvent we parse the result string  back into JSON and trigger the appropriate handler (eventHandlers map
    // uses callbackId as key), along with the actual data coming back from the native layer. Each function may have its own way of
    // processing native data so we do not do any processing here.

    self.onEvent = function (strData) {
        var arData = strData.split(" "),
            strEventDesc = arData[0],
            eventHandler,
            args = {};

        if (strEventDesc === "result") {
            args.result = escape(strData.split(" ").slice(2).join(" "));
            eventHandler = self.eventHandlers[arData[1]];
            if (eventHandler.action === "save" || eventHandler.action === "remove") {
                eventHandler.handler(eventHandler.result, JSON.parse(decodeURIComponent(args.result)));
            } else if (eventHandler.action === "find") {
                eventHandler.handler(arData[1], eventHandler, JSON.parse(decodeURIComponent(args.result)));
            }
        }
    };

    self.m_id = "";
    self.eventHandlers = {};

    self.getInstance = function () {
        if (!hasInstance) {
            self.init();
            hasInstance = true;
        }
        return self;
    };
};

pimContacts = new JNEXT.PimContacts();