// bitmask.js
// Copyright (C) 2016 LEAP
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

/**
 * bitmask object
 *
 * Contains all the bitmask API mapped by sections
 * - user. User management like login, creation, ...
 * - mail. Email service control.
 * - keys. Keyring operations.
 * - events. For registering to events.
 *
 * Every function returns a Promise that will be triggered once the request is
 * finished or will fail if there was any error. Errors are always user readable
 * strings.
 */

try {
    // Use Promises in non-ES6 compliant engines.
    eval('import "babel-polyfill";')
}
catch (err) {}

var bitmask = function(){
    var event_handlers = {};
    var api_url = '/API/';
    var api_token = null;
    var last_uid = null;
    var last_uuid = null;

    if (window.location.protocol === "file:") {
        api_url = 'http://localhost:7070/API/';
    }
    if (window.location.hash) {
        api_token = window.location.hash.replace('#', '')
    }

    function call(command) {
        var url = api_url  + command.slice(0, 3).join('/');
        var data = JSON.stringify(command.slice(3));

        return new Promise(function(resolve, reject) {
            var req = new XMLHttpRequest();

            req.open('POST', url);
            if (api_token) {
                req.setRequestHeader("X-Bitmask-Auth", api_token)
            }

            req.onload = function() {
                if (req.status == 200) {
                    parseResponse(req.response, resolve, reject);
                }
                else {
                    reject(Error(req.statusText));
                }
            };

            req.onerror = function() {
                reject(Error("Network Error"));
            };

            req.send(data);
        });
    };

    function parseResponse(raw_response, resolve, reject) {
        var response = JSON.parse(raw_response);
        if (response.error === null) {
            resolve(response.result);
        } else {
            reject(response.error);
        }
    };

    function event_polling() {
        if (api_token) {
            call(['events', 'poll']).then(function(response) {
                if (response !== null) {
                    var event = response[0];
                    var content = response[1];
                    if (event in event_handlers) {
                        Object.values(event_handlers[event]).forEach(function(handler) {
                            handler(event, content);
                        })
                    }
                }
                event_polling();
            }, function(error) {
                setTimeout(event_polling, 5000);
            });
        }
    };
    event_polling();

    function private_str(priv) {
        if (priv) {
            return 'private'
        }
        return 'public'
    };

    return {
        api_token: function() {return api_token},

        core: {
            /**
             * Get bitmaskd version
             *
             * @return {Promise<json>} {'version_core': str}
             */
            version: function() {
                return call(['core', 'version']);
            },

            /**
             * Stop bitmaskd
             */
            stop: function() {
                return call(['core', 'stop']);
            }
        },

        bonafide: {
            provider: {
                create: function(domain) {
                    return call(['bonafide', 'provider', 'create', domain]);
                },

                read: function(domain, service) {
                    if (typeof service !== 'string') {
                        service  = "";
                    }
                    return call(['bonafide', 'provider', 'read', domain, service]);
                },

                delete: function(domain) {
                    return call(['bonafide', 'provider', 'delete', domain]);
                },

                list: function(seeded) {
                    if (typeof seeded !== 'boolean') {
                        seeded = false;
                    }
                    return call(['bonafide', 'provider', 'list', seeded]);
                }
            },

            /**
             * uids are of the form user@provider.net
             */
            user: {
                /**
                 * Register a new user
                 *
                 * @param {string} uid The uid to be created
                 * @param {string} password The user password
                 * @param {boolean} autoconf If the provider should be autoconfigured if it's not already known
                 *                           If it's not provided it will default to false
                 */
                create: function(uid, password, invite, autoconf) {
                    if (typeof autoconf !== 'boolean') {
                        autoconf = false;
                    }
                    return call(['bonafide', 'user', 'create', uid, password, invite, autoconf]);
                },

                /**
                 * Login
                 *
                 * @param {string} uid The uid to log in
                 * @param {string} password The user password
                 * @param {boolean} autoconf If the provider should be autoconfigured if it's not already known
                 *                           If it's not provided it will default to false
                 */
                auth: function(uid, password, autoconf) {
                    if (typeof autoconf !== 'boolean') {
                        autoconf = false;
                    }
                    return call(['bonafide', 'user', 'authenticate', uid, password, autoconf]).then(function(response) {
                        last_uuid = response.uuid
                        last_uid = uid
                        return response;
                    });
                },

                /**
                 * Logout
                 *
                 * @param {string} uid The uid to log out.
                 */
                logout: function(uid) {
                    return call(['bonafide', 'user', 'logout', uid]);
                },

                /**
                 * List users
                 *
                 * @return {Promise<json>} [{'userid': str, 'authenticated': boolean}]
                 */
                list: function() {
                    return call(['bonafide', 'user', 'list']);
                },

                /**
                 * Change password
                 *
                 * @param {string} uid The uid to log in
                 * @param {string} current_password The current user password
                 * @param {string} new_password The new user password
                 */
                update: function(uid, current_password, new_password) {
                    return call(['bonafide', 'user', 'update', uid, current_password, new_password]);
                }
            }
        },

        /**
         * For now the VPN setup is not really streamlined
         *
         * src/leap/bitmask/vpn/README.rst for more info
         */
        vpn: {
            enable: function() {
                return call(['vpn', 'enable'])
            },

            disable: function() {
                return call(['vpn', 'disable'])
            },

            status: function() {
                return call(['vpn', 'status'])
            },

            start: function(provider) {
                return call(['vpn', 'start', provider])
            },

            stop: function() {
                return call(['vpn', 'stop'])
            },

            /**
             * Check if the VPN is ready to start and has the cert downloaded
             *
             * @return {Promise<{'vpn_ready': bool,
             *                   'installed': bool}>}
             */
            check: function(provider) {
                if (typeof provider !== 'string') {
                    provider = "";
                }
                return call(['vpn', 'check', provider]);
            },

            /**
             * Download VPN cert
             *
             * @param {string} userid the userid to be used
             */
            get_cert: function(userid) {
                return call(['vpn', 'get_cert', userid])
            },

            /**
             * Install helpers in the system
             */
            install: function() {
                return call(['vpn', 'install'])
            },

            /**
             * Uninstall helpers in the system
             */
            uninstall: function() {
                return call(['vpn', 'uninstall'])
            }
        },

        mail: {
            enable: function() {
                return call(['mail', 'enable'])
            },

            disable: function() {
                return call(['mail', 'disable'])
            },

            /**
             * Check the status of the email service
             *
             * @param {string} uid The uid to get status about
             *
             * @return {Promise<string>} User readable status
             */
            status: function(uid) {
                return call(['mail', 'status', uid]);
            },

            /**
             * Get the token of the active user.
             *
             * This token is used as password to authenticate in the IMAP and SMTP services.
             *
             * @return {Promise<string>} The token
             */
            get_token: function() {
                return call(['mail', 'get_token']);
            }
        },

        /**
         * A KeyObject have the following attributes:
         *   - address {string} the email address for wich this key is active
         *   - fingerprint {string} the fingerprint of the key
         *   - length {number} the size of the key bits
         *   - private {bool} if the key is private
         *   - uids {[string]} the uids in the key
         *   - key_data {string} the key content
         *   - validation {string} the validation level which this key was found
         *   - expiry_date {string} date when the key expires
         *   - refreshed_at {string} date of the last refresh of the key
         *   - audited_at {string} date of the last audit (unused for now)
         *   - sign_used {bool} if has being used to checking signatures
         *   - enc_used {bool} if has being used to encrypt
         */
        keys: {
            /**
             * List all the keys in the keyring
             *
             * @param {string} uid The uid of the keyring.
             * @param {boolean} priv Should list private keys?
             *                       If it's not provided the public ones will be listed.
             *
             * @return {Promise<[KeyObject]>} List of keys in the keyring
             */
            list: function(uid, priv) {
                return call(['keys', 'list', uid, private_str(priv)]);
            },

            /**
             * Export key
             *
             * @param {string} uid The uid of the keyring.
             * @param {string} address The email address of the key
             * @param {boolean} priv Should get the private key?
             *                       If it's not provided the public one will be fetched.
             *
             * @return {Promise<KeyObject>} The key
             */
            exprt: function(uid, address, priv) {
                return call(['keys', 'export', uid, address, private_str(priv)]);
            },

            /**
             * Insert key
             *
             * @param {string} uid The uid of the keyring.
             * @param {string} address The email address of the key
             * @param {string} rawkey The key material
             * @param {string} validation The validation level of the key
             *                            If it's not provided 'Fingerprint' level will be used.
             *
             * @return {Promise<KeyObject>} The key
             */
            insert: function(uid, address, rawkey, validation) {
                if (typeof validation !== 'string') {
                    validation = 'Fingerprint';
                }
                return call(['keys', 'insert', uid, address, validation, rawkey]);
            },

            /**
             * Delete a key
             *
             * @param {string} uid The uid of the keyring.
             * @param {string} address The email address of the key
             * @param {boolean} priv Should get the private key?
             *                       If it's not provided the public one will be deleted.
             *
             * @return {Promise<KeyObject>} The key
             */
            del: function(uid, address, priv) {
                return call(['keys', 'delete', uid, address, private_str(priv)]);
            }
        },

        events: {
            /**
             * Register func for an event
             *
             * @param {string} event The event to register
             * @param {string} name The unique name for the callback
             * @param {function} func The function that will be called on each event.
             *                        It has to be like: function(event, content) {}
             *                        Where content will be a list of strings.
             */
            register: function(event, name, func) {
                event_handlers[event] = event_handlers[event] || {}
                if (event_handlers[event][name]) {
                    return null;
                } else {
                    event_handlers[event][name] = func;
                    return call(['events', 'register', event])
                }
            },

            /**
             * Unregister from an event
             *
             * @param {string} event The event to unregister
             * @param {string} name The unique name of the callback to remove
             */
            unregister: function(event, name) {
                event_handlers[event] = event_handlers[event] || {}
                delete event_handlers[event][name]
                if (Object.keys(event_handlers[event]).length == 0) {
                  return call(['events', 'unregister', event]);
                } else {
                  return null;
                }
            }
        }
    };
}();

try {
    module.exports = bitmask
} catch(err) {}