define([
    '/bower_components/chainpad-crypto/crypto.js',
    '/bower_components/chainpad-netflux/chainpad-netflux.js',
    '/common/common-util.js',
    '/common/common-hash.js',
    '/common/common-realtime.js',
    '/common/outer/network-config.js',
    '/bower_components/chainpad/chainpad.dist.js',
], function (Crypto, CPNetflux, Util, Hash, Realtime, NetConfig) {
    var finish = function (S, err, doc) {
        if (S.done) { return; }
        S.cb(err, doc);
        S.done = true;

        if (!S.hasNetwork) {
            var disconnect = Util.find(S, ['network', 'disconnect']);
            if (typeof(disconnect) === 'function') { disconnect(); }
        }
        if (S.realtime && S.realtime.stop) {
            try {
                S.realtime.stop();
            } catch (e) { console.error(e); }
        }
        var abort = Util.find(S, ['session', 'realtime', 'abort']);
        if (typeof(abort) === 'function') {
            S.session.realtime.sync();
            abort();
        }
    };

    var makeConfig = function (hash, opt) {
        var secret;
        if (typeof(hash) === 'string') {
        // We can't use cryptget with a file or a user so we can use 'pad' as hash type
            secret = Hash.getSecrets('pad', hash, opt.password);
        } else if (typeof(hash) === 'object') {
            // we may want to just supply options directly
            // and this is the easiest place to do it
            secret = hash;
        }
        if (!secret.keys) { secret.keys = secret.key; } // support old hashses
        var config = {
            websocketURL: NetConfig.getWebsocketURL(opt.origin),
            channel: secret.channel,
            validateKey: secret.keys.validateKey || undefined,
            crypto: Crypto.createEncryptor(secret.keys),
            logLevel: 0,
            initialState: opt.initialState
        };
        return config;
    };

    var isObject = function (o) {
        return typeof(o) === 'object';
    };

    var overwrite = function (a, b) {
        if (!(isObject(a) && isObject(b))) { return; }
        Object.keys(b).forEach(function (k) { a[k] = b[k]; });
    };

    var get = function (hash, cb, opt, progress) {
        if (typeof(cb) !== 'function') {
            throw new Error('Cryptget expects a callback');
        }
        opt = opt || {};
        progress = progress || function () {};

        var config = makeConfig(hash, opt);
        var Session = { cb: cb, hasNetwork: Boolean(opt.network) };

        config.onReady = function (info) {
            var rt = Session.session = info.realtime;
            Session.network = info.network;
            progress(1);
            finish(Session, void 0, rt.getUserDoc());
        };

        config.onError = function (info) {
            finish(Session, info.error);
        };
        config.onChannelError = function (info) {
            finish(Session, info.error);
        };

        // We use the new onMessage handler to compute the progress:
        // we should receive 2 checkpoints max, so 100 messages max
        // We're going to consider that 1 message = 1%, and we'll send 100%
        // at the end
        var i = 0;
        config.onMessage = function () {
            i++;
            progress(Math.min(0.99, i/100));
        };

        overwrite(config, opt);

        Session.realtime = CPNetflux.start(config);
    };

    var put = function (hash, doc, cb, opt) {
        if (typeof(cb) !== 'function') {
            throw new Error('Cryptput expects a callback');
        }
        opt = opt || {};

        var config = makeConfig(hash, opt);
        var Session = { cb: cb, hasNetwork: Boolean(opt.network) };

        config.onReady = function (info) {
            var realtime = Session.session = info.realtime;
            Session.network = info.network;

            realtime.contentUpdate(doc);

            var to = setTimeout(function () {
                cb(new Error("Timeout"));
            }, 15000);

            Realtime.whenRealtimeSyncs(realtime, function () {
                clearTimeout(to);
                var doc = realtime.getAuthDoc();
                realtime.abort();
                finish(Session, void 0, doc);
            });
        };
        overwrite(config, opt);

        Session.session = CPNetflux.start(config);
    };

    return {
        get: get,
        put: put,
    };
});