/*************************************************************************
*
* ADOBE CONFIDENTIAL
* ___________________
*
*  Copyright 2015 Adobe Systems Incorporated
*  All Rights Reserved.
*
* NOTICE:  All information contained herein is, and remains
* the property of Adobe Systems Incorporated and its suppliers,
* if any.  The intellectual and technical concepts contained
* herein are proprietary to Adobe Systems Incorporated and its
* suppliers and are protected by trade secret or copyright law.
* Dissemination of this information or reproduction of this material
* is strictly forbidden unless prior written permission is obtained
* from Adobe Systems Incorporated.
**************************************************************************/
/*jslint node: true, vars: true, plusplus: true, devel: true, nomen: true, indent: 4, maxerr: 50, unparam: true */
/*global define, console*/

/**
    JavaScript Library for IMSLib
**/

'use strict';

var ref = require('ref');
var xml2js = require('xml2js');
var path = require('path');

var IMSNativeLib = require('./IMSNativeLib');
var getIMSLibPath = require('./IMSLibPath');
var IMSError = require('./IMSError');


/**
    Constants
**/

// For now, we just create a single clientContext, since we likely don't need to expose this
// We can always update the API to expose this, if it turns out to be needed.
var CLIENT_CONTEXT = ref.alloc('void');

var IMS_TYPE = IMSNativeLib.IMS_TYPE;
var IMS_BUDDY_GROUP = IMSNativeLib.IMS_BUDDY_GROUP;


/**
    Utilities
**/

// Utility: wrapping callback
function wrapHttpCallback(callback) {
    var hasReturned = false;

    return function (status, data) {
        if (!hasReturned) {
            if (status !== IMSError.SUCCESS) {
                callback(new IMSError(status));
            } else {
                var result;
                try {
                    result = JSON.parse(data);
                } catch (ignore) {
                }
                callback(null, result);
            }
            hasReturned = true;
        }
    };
}

function wrapAsNative(callback) {
    var cb = IMSNativeLib.createHTTPCallbackFn(function (status, data) {
        // Note: releaseHTTPCallbackFn just removes the reference to the callback, allowing it to be
        // garbage collected. It doesn't call into IMSLib, so it's safe to do during the callback.
        IMSNativeLib.releaseHTTPCallbackFn(cb);
        callback(status, data);
    });
    return cb;
}

// Utility: checking status (throws an exception, or calls the callback with error code)
function checkStatus(status, callback) {
    if (status !== IMSError.SUCCESS) {
        if (callback) {
            callback(new IMSError(status));
        } else {
            throw new IMSError(status);
        }
    }
}

// Utility: parse XML into a Javascript object
function parseXML(xml) {
    // Note: this works because parseString calls its callback synchronously
    var obj;
    xml2js.parseString(xml, function (err, result) {
        obj = result;
    });
    return obj;
}

// Utility: format user data better
function formatUserData(userData) {
    Object.keys(userData).forEach(function (key) {
        if (key === '$') {
            Object.keys(userData[key]).forEach(function (attr) {
                userData[attr] = userData[key][attr];
            });
            delete userData[key];

        } else if (key === 'SAOList') {
            userData[key] = userData[key][0].SAOData || [];
            userData[key].forEach(formatUserData);

        } else if (key === 'MrktPerm') {
            var fields = userData[key][0].split(',');
            userData[key] = {};
            fields.forEach(function (field) {
                var keyValue = field.split(':');
                userData[key][keyValue[0]] = keyValue[1];
            });

        } else {
            userData[key] = userData[key][0];
        }
    });
}


/**
    APIs
**/

var IMSLib = {
    // Lazy initialized
    _IMS: null
};

Object.defineProperty(IMSLib, "IMS", {
    get: function () {
        if (!this._IMS) {
            // Load IMS lib using the default path if it wasn't already loaded.
            this.loadLib();
        }

        return this._IMS;
    }
});

IMSLib.loadLib = function (libPath) {
    libPath = libPath || getIMSLibPath();

    try {
        this._IMS = IMSNativeLib.loadIMSLib(libPath);
    } catch (error) {
        // Windows fails to load on some absolute paths so fall back
        // to a relative path before failing completely.
        if (!path.isAbsolute(libPath)) {
            throw error;
        }

        this._IMS = IMSNativeLib.loadIMSLib(path.relative(process.cwd(), libPath));
    }
};

IMSLib.createRef = function (isGM) {
    return this.IMS.IMS_createRef(CLIENT_CONTEXT, isGM ? 1 : 0);
};

IMSLib.createRefEx = function (imsEndpoint) {
    return this.IMS.IMS_createRefEx(CLIENT_CONTEXT, imsEndpoint);
};

IMSLib.createRefForProductVersion = function (LEID) {
    return this.IMS.IMS_createRefForProductVersion(CLIENT_CONTEXT, LEID);
};

IMSLib.releaseRef = function (imsRef) {
    var status = this.IMS.IMS_releaseRef(imsRef.ref());
    checkStatus(status);
};

IMSLib.fetchAccessToken = function (imsRef, clientId, clientSecret, userAccountGuid, serviceAccountGuid, scope, callback) {
    var _callback = wrapHttpCallback(callback);
    var status = this.IMS.IMS_fetchAccessToken(imsRef, clientId, clientSecret, userAccountGuid, serviceAccountGuid, scope, wrapAsNative(_callback));
    checkStatus(status, _callback);
};

IMSLib.fetchAccessToken2 = function (imsRef, xmlData, callback) {
    var _callback = wrapHttpCallback(callback);
    var status = this.IMS.IMS_fetchAccessToken2(imsRef, xmlData, wrapAsNative(_callback));
    checkStatus(status, _callback);
};

IMSLib.fetchIDPResponseForKey = function (imsRef, data, callback) {
    var _callback = wrapHttpCallback(callback);
    var status = this.IMS.IMS_fetchIDPResponseForKey(imsRef, data, wrapAsNative(_callback));
    checkStatus(status, _callback);
};

IMSLib.revokeDeviceToken = function (imsRef, userAccountGuid, clientId, clientSecret, callback) {
    var _callback = wrapHttpCallback(callback);
    var status = this.IMS.IMS_revokeDeviceToken(imsRef, userAccountGuid, clientId, clientSecret, wrapAsNative(_callback));
    checkStatus(status, _callback);
};

IMSLib.fetchAccounts = function (imsRef, clientId) {
    var outAccounts = ref.alloc('string');
    var status = this.IMS.IMS_fetchAccounts(imsRef, clientId, outAccounts);
    checkStatus(status);

    var accounts = parseXML(outAccounts.deref());
    status = this.IMS.IMS_releaseData(imsRef, IMS_TYPE.ACCOUNTS, outAccounts);
    checkStatus(status);

    accounts = accounts && accounts.UserAccounts && accounts.UserAccounts.UserData;
    if (accounts) {
        accounts.forEach(formatUserData);
    }
    return accounts;
};

// NOTE: we don't expose the IMS_releaseData API, since we handle calling it at the right time

IMSLib.fetchDefaultUserInfoForClientId = function (imsRef, clientId) {
    var outUserData = ref.alloc('string');
    var status = this.IMS.IMS_fetchDefaultUserInfoForClientId(imsRef, clientId, outUserData);
    checkStatus(status);

    var userData = parseXML(outUserData.deref());
    status = this.IMS.IMS_releaseData(imsRef, IMS_TYPE.DEFAULT_USER_DATA, outUserData);
    checkStatus(status);

    userData = userData && userData.UserData;
    if (userData) {
        formatUserData(userData);
    }
    return userData;
};

IMSLib.fetchUserProfileData = function (imsRef, userAccountGuid) {
    var outUserData = ref.alloc('string');
    var status = this.IMS.IMS_fetchUserProfileData(imsRef, userAccountGuid, outUserData);
    checkStatus(status);

    var userData = parseXML(outUserData.deref());
    status = this.IMS.IMS_releaseData(imsRef, IMS_TYPE.DEFAULT_USER_DATA, outUserData);
    checkStatus(status);

    userData = userData && userData.UserData;
    if (userData) {
        formatUserData(userData);
    }
    return userData;
};

IMSLib.setProxyCredentials = function (proxyUsername, proxyPassword) {
    var status = this.IMS.IMS_setProxyCredentials(proxyUsername, proxyPassword);
    checkStatus(status);
};

IMSLib.setProxyCredentialsInIMSLibSession = function (imsRef, proxyUsername, proxyPassword, saveOnDisk) {
    var status = this.IMS.IMS_setProxyCredentialsInIMSLibSession(imsRef, proxyUsername, proxyPassword, saveOnDisk ? 1 : 0);
    checkStatus(status);
};

IMSLib.getProxyCredentialsFromIMSLibSession = function (imsRef) {
    var outProxyData = ref.alloc('string');
    var status = this.IMS.IMS_getProxyCredentialsFromIMSLibSession(imsRef, outProxyData);
    checkStatus(status);

    var proxyData = parseXML(outProxyData.deref());
    status = this.IMS.IMS_releaseData(imsRef, IMS_TYPE.PROXY_DATA, outProxyData);
    checkStatus(status);

    return proxyData;
};

IMSLib.fetchContinueToken = function (imsRef, bearerToken, targetClientId, redirectUri, scope, responseType, locale, callback) {
    var _callback = wrapHttpCallback(callback);
    var status = this.IMS.IMS_fetchContinueToken(imsRef, bearerToken, targetClientId, redirectUri, scope, responseType, locale, wrapAsNative(_callback));
    checkStatus(status, _callback);
};

IMSLib.EEPLogin = function (imsRef, data, callback) {
    var _callback = wrapHttpCallback(callback);
    var status = this.IMS.IMS_EEPLogin(imsRef, data, wrapAsNative(_callback));
    checkStatus(status, _callback);
};

IMSLib.setUserAsDefaultForClientId = function (imsRef, userAccountGuid, clientId) {
    var status = this.IMS.IMS_setUserAsDefaultForClientId(imsRef, userAccountGuid, clientId);
    checkStatus(status);
};

IMSLib.getDeviceID = function (imsRef) {
    var outDeviceID = ref.alloc('string');
    var status = this.IMS.IMS_getDeviceID(imsRef, outDeviceID);
    checkStatus(status);

    var deviceID = outDeviceID.deref(); // Note: this isn't XML
    status = this.IMS.IMS_releaseData(imsRef, IMS_TYPE.DEVICE_ID, outDeviceID);
    checkStatus(status);

    return deviceID;
};

IMSLib.logOutUserForClient = function (imsRef, userAccountGuid, clientId, callback) {
    var _callback = wrapHttpCallback(callback);
    var status = this.IMS.IMS_logOutUserForClient(imsRef, userAccountGuid, clientId, wrapAsNative(_callback));
    checkStatus(status, _callback);
};

IMSLib.registerClientToGroup = function (imsRef, clientId, groupId) {
    var status = this.IMS.IMS_registerClientToGroup(imsRef, clientId, IMS_BUDDY_GROUP[groupId]);
    checkStatus(status);
};

IMSLib.deregisterClientFromGroup = function (imsRef, clientId, groupId) {
    var status = this.IMS.IMS_deregisterClientFromGroup(imsRef, clientId, IMS_BUDDY_GROUP[groupId]);
    checkStatus(status);
};

IMSLib.fetchFeatureFlags = function (imsRef, clientId, accessToken, releaseFlag, callback) {
    var _callback = wrapHttpCallback(callback);
    var status = this.IMS.IMS_fetchFeatureFlags(imsRef, clientId, accessToken, releaseFlag, wrapAsNative(_callback));
    checkStatus(status);
};

IMSLib.fetchReleaseFlag = function (imsRef, clientId, accessToken, callback) {
    var _callback = wrapHttpCallback(callback);
    var status = this.IMS.IMS_fetchReleaseFlag(imsRef, clientId, accessToken, wrapAsNative(_callback));
    checkStatus(status);
};

IMSLib.setRetryAfterResponseHandlingData = function (imsRef, maxRetryDurationInMillisecs, maxRetryCount) {
    var status = this.IMS.IMS_setRetryAfterResponseHandlingData(imsRef, maxRetryDurationInMillisecs, maxRetryCount);
    checkStatus(status);
};


module.exports = IMSLib;
