// the odnepa!
function Odnepa(){
    if ( Odnepa.prototype._singletonInstance ) {
        return Odnepa.prototype._singletonInstance;
    }
    Object.defineProperty(this, "__Apendo", {
      enumerable: false,
      configurable: false,
      writable: false,
      value: window.Apendo
    });
    this.unregisterAll();
    Odnepa.prototype._singletonInstance = this;
}

Odnepa.prototype.register = function(eventName, fn){
    var self = this;
    window.refs = window.refs || {};
    var ref = window.refs[eventName];
    if (ref){
        try {
            self.__Apendo[eventName].disconnect(ref);
        } catch(ex){}
    }
    window.refs[eventName] = fn;
    var ev = self.__Apendo[eventName];
    if (ev){
        ev.connect(window.refs[eventName]);
        return true;
    }
    return false;
}

Odnepa.prototype.unregisterAll = function(){
    var self = this;
    window.refs = window.refs || {};
    for (var e in window.refs){
        self.unregister(e);
    }
}

Odnepa.prototype.unregister = function (eventName){
    var self = this;

    if (Array.isArray(eventName)){
        for (var i = 0; i < eventName.length; i++){
            self.unregister(eventName[i]);
        }
        return true;
    }

    window.refs = window.refs || {};
    var ref = window.refs[eventName];
    if (ref){
        try {
            self.__Apendo[eventName].disconnect(ref);
        } catch(ex){
            return false;
        }
    }
    return true;
}

// disconnect handlers
Odnepa.prototype.disconnect = function(fn, slot){
    setImmediate(function(){
        try{
            fn.disconnect(slot);
        }catch(ex){
            console.log (ex);
        }
    });
}

// select files, by invoking file picker
Odnepa.prototype.selectFile = function(options, cb){
    var self = this;
    var opt = options || {};
    if (typeof options == "function") {
        cb = options;
        opt = {};
    }
    if (!cb){
        cb = self.debug;
    }
    opt = opt || {};
    var handler = function(ctx){
        cb(null, ctx);
        self.disconnect(self.__Apendo.filesSelected, arguments.callee);
    }
    self.__Apendo.filesSelected.connect(handler);
    self.__Apendo.filesSelect(opt);
}

// prepare session
Odnepa.prototype.prepare = function(options, cb){
    console.log('preparing...');
    var self = this;
    if (!cb){
        cb = self.debug;
    }
    if (!options){
        return cb (new Error("context is required"))
    }
    if (!options.objId || !options.owner){
        return cb (new Error("context owner and objId are required"))
    }
    var handler = function(ctx){
        cb(null, ctx);
        self.disconnect(self.__Apendo.prepared, arguments.callee);
    }
    var additionalKeys = [];
    for(var k in options){
        if (['objId', 'owner'].indexOf(k) < 0){
            additionalKeys.push(k);
        }
    }
    options.additionalKeys = additionalKeys;
    // self.__Apendo.prepared.connect(handler);
    self.__Apendo.prepared.connect(function(ctx) {
        if (!ctx.success) {
            cb(new Error("failed to create a session context"));
        } else {
            cb(null, ctx);
        }
        self.disconnect(self.__Apendo.prepared, arguments.callee)
    });
    self.__Apendo.prepare(options);
}

// to save envelopes data
Odnepa.prototype.save = function(options, cb){
    var self = this;
    if (!cb){
        cb = self.debug;
    }
    if (!options){
        return cb (new Error("context is required"))
    }
    if (!options.objId || !options.owner){
        return cb (new Error("context owner and objId are required"))
    }
    if (!options.envelopes || !Array.isArray(options.envelopes) || options.envelopes.length == 0){
        return cb (new Error("context envelopes are required, it should be a non-empty array"))
    }

    var bundles = [];
    var bundleKeys = [];

    for (var i = 0; i < options.envelopes.length; i++){
        var envelope = options.envelopes[i];
        var keys = Object.keys(envelope);
        if (keys.length != 1){
            return cb (new Error("an envelope in context envelopes should contains exactly one value"));
        }

        var name = keys[0];
        var val = envelope[name];
        var isObject = typeof val == "object";

        /*if (Array.isArray(val)){
            return cb(new Error("saving an array is not supported"));
        }*/

        var bundle = {
            name : name,
            isObject : isObject,
            owner : options.owner,
            objId : options.objId,
            val : isObject ? JSON.stringify(val) : val,

        }
        bundleKeys.push(name);
        bundles.push(bundle);
    }

    options.bundles = bundles;
    options.envelopes = bundleKeys;

    var additionalKeys = [];
    for(var k in options){
        if (['objId', 'owner', 'bundles'].indexOf(k) < 0){
            additionalKeys.push(k);
        }
    }
    options.additionalKeys = additionalKeys;

    var handler = function(ctx){
        if (!ctx.success){
            cb(new Error(ctx.errorMessage));
        } else {
            cb(null, ctx);
        }
        self.disconnect(self.__Apendo.saved, arguments.callee);
    }
    self.__Apendo.saved.connect(handler);
    self.__Apendo.save(options);
}

// to load envelopes data
Odnepa.prototype.load = function(options, cb){
    var self = this;
    if (!cb){
        cb = self.debug;
    }
    if (!options){
        return cb (new Error("context is required"))
    }
    if (!options.objId || !options.owner){
        return cb (new Error("context owner and objId are required"))
    }
    if (!options.envelopes || !Array.isArray(options.envelopes)){
        return cb (new Error("context envelopes are required, it should be contained in an arrays"))
    }

    for (var i = 0; i < options.envelopes.length; i++){
        var envelope = options.envelopes[i];
        if (!envelope || typeof envelope != "string"){
            return cb(new Error("context envelopes should be an array of non-empty string"));
        }
    }

    var additionalKeys = [];
    for(var k in options){
        if (['objId', 'owner', 'envelopes'].indexOf(k) < 0){
            additionalKeys.push(k);
        }
    }
    options.additionalKeys = additionalKeys;

    var handler = function(ctx){
        if (!ctx.success){
            cb(new Error(ctx.errorMessage));
        } else {
            cb(null, ctx);
        }
        self.disconnect(self.__Apendo.loaded, arguments.callee);
    }
    self.__Apendo.loaded.connect(handler);
    self.__Apendo.load(options);
}

// to check: saved, packed, downloaded, uploaded, sorted
Odnepa.prototype.check = function(options, cb){
    var self = this;
    if (!cb){
        cb = self.debug;
    }
    if (!options){
        return cb (new Error("context is required"))
    }
    if (!options.objId || !options.owner){
        return cb (new Error("context owner and objId are required"))
    }
    if (!options.type){
        return cb (new Error("context type is required"))
    }
    if (!options.envelopes || !Array.isArray(options.envelopes)){
        return cb (new Error("context envelopes are required, it should be contained in an array"))
    }
    for (var i = 0; i < options.envelopes.length; i++){
        var envelope = options.envelopes[i];
        if (!envelope || typeof envelope != "string"){
            return cb(new Error("context envelopes should be an array of non-empty string"));
        }
    }
    var additionalKeys = [];
    for(var k in options){
        if (["objId", "owner", "type", "envelopes"].indexOf(k) < 0){
            additionalKeys.push(k);
        }
    }

    if (options.type == "pack"){
        return self._checkPack(options, cb);
    }

    if (options.type == "save"){
        return self._checkSave(options, cb);
    }

    return cb (new Error("invalid type"));
}

Odnepa.prototype._checkPack = function(options, cb){
    var self = this;
    var handler = function(ctx){
        if (!ctx.success){
            cb(new Error(ctx.errorMessage));
        } else {
            cb(null, ctx);
        }
        self.disconnect(self.__Apendo.packChecked, arguments.callee);
    }
    self.__Apendo.packChecked.connect(handler);
    self.__Apendo.packCheck(options);
}

Odnepa.prototype._checkSave = function(options, cb){
    var self = this;
    var handler = function(ctx){
        if (!ctx.success){
            cb(new Error(ctx.errorMessage));
        } else {
            cb(null, ctx);
        }
        self.disconnect(self.__Apendo.saveChecked, arguments.callee);
    }
    self.__Apendo.saveChecked.connect(handler);
    self.__Apendo.saveCheck(options);
}

Odnepa.prototype.packCategory = function(options, cb){
    var self = this;
    if (!cb) {
        cb = self.debug;
    }
    if (!options){
        return cb (new Error("context is required"))
    }
    if (!options.objId || !options.owner){
        return cb (new Error("context owner and objId are required"))
    }
    if (!options.key || typeof options.key != "string"){
        return cb (new Error("public key is required"))
    }
    var handler = function(ctx){
        if (!ctx.success){
            cb(new Error(ctx.errorMessage));
        } else {
            cb(null, ctx);
        }
        self.disconnect(self.__Apendo.categoryPacked, arguments.callee);
    }
    self.__Apendo.categoryPacked.connect(handler);
    self.__Apendo.packCategory(options);
}

// to pack envelopes
Odnepa.prototype.pack = function(options, cb){
    var self = this;
    if (!cb){
        cb = self.debug;
    }
    if (!options){
        return cb (new Error("context is required"))
    }
    if (!options.objId || !options.owner){
        return cb (new Error("context owner and objId are required"))
    }
    if (!options.key || typeof options.key != "string"){
        return cb (new Error("public key is required"))
    }
    if (!options.envelopes || !Array.isArray(options.envelopes)){
        return cb (new Error("context envelopes are required, it should be contained in an array"))
    }

    for (var i = 0; i < options.envelopes.length; i++){
        var envelope = options.envelopes[i];
        if (!envelope || typeof envelope != "string"){
            return cb(new Error("context envelopes should be an array of non-empty string"));
        }
    }

    var additionalKeys = [];
    for(var k in options){
        if (["objId", "owner", "key", "envelopes"].indexOf(k) < 0){
            additionalKeys.push(k);
        }
    }
    options.additionalKeys = additionalKeys;

    // multiple envelopes, with the same public key
    var handler = function(ctx){
        if (!ctx.success){
            cb(new Error(ctx.errorMessage));
        } else {
            cb(null, ctx);
        }
        self.disconnect(self.__Apendo.packed, arguments.callee);
    }
    self.__Apendo.packed.connect(handler);
    self.__Apendo.pack(options);
}

// to unpack packed envelopes
Odnepa.prototype.unpack = function(options, cb){
    var self = this;
    if (!cb){
        cb = self.debug;
    }
    if (!options){
        return cb (new Error("context is required"))
    }
    if (!options.objId || !options.owner){
        return cb (new Error("context owner and objId are required"))
    }
    if (!options.key || typeof options.key != "string"){
        return cb (new Error("private key is required"))
    }
    if (!options.envelope || typeof options.envelope != "string"){
        return cb (new Error("context envelope is required"))
    }
    if (!options.packageOwners || !Array.isArray(options.packageOwners)){
        return cb (new Error("context package owners are required, it should be contained in an array"))
    }
    for (var i = 0; i < options.packageOwners.length; i++){
        var packageOwner = options.packageOwners[i];
        if (!packageOwner || typeof packageOwner != "string"){
            return cb(new Error("context package owners should be an array of non-empty string"));
        }
    }
    var additionalKeys = [];
    for(var k in options){
        if (["objId", "owner", "key", "envelope", "packageOwners"].indexOf(k) < 0){
            additionalKeys.push(k);
        }
    }
    options.additionalKeys = additionalKeys;

    // multiple envelopes, with the same public key
    var handler = function(ctx){
        if (!ctx.success){
            cb(new Error(ctx.errorMessage));
        } else {
            cb(null, ctx);
        }
        self.disconnect(self.__Apendo.unpacked, arguments.callee);
    }
    self.__Apendo.unpacked.connect(handler);
    self.__Apendo.unpack(options);
}

// to upload packed envelopes to server
Odnepa.prototype.upload = function(options, cb){
    var self = this;
    if (!cb){
        cb = self.debug;
    }
    if (!options){
        return cb (new Error("context is required"))
    }
    if (!options.objId || !options.owner){
        return cb (new Error("context owner and objId are required"))
    }
    if (!options.packages || !Array.isArray(options.packages)){
        return cb (new Error("context packages are required"))
    }
    if (!options.url || typeof options.url != "string"){
        return cb (new Error("context url is required"))
    }

    for (var i = 0; i < options.packages.length; i++){
        var package = options.packages[i];
        if (typeof package != "object"){
            return cb (new Error("context package should be an object"))
        }
        if (!package["envelope"] || typeof package["envelope"] != "string"){
            return cb (new Error("context package envelope is required"))
        }

        var fields = [];
        if (package["fields"]){
            for (var f in package["fields"]){
                fields.push({ key : f, value : package["fields"][f]});
            }
        }

        package.fields = fields;
        options.packages[i] = package;
    }

    var additionalKeys = [];
    for(var k in options){
        if (["objId", "owner", "key", "envelopes", "fields"].indexOf(k) < 0){
            additionalKeys.push(k);
        }
    }
    options.additionalKeys = additionalKeys;

    // multiple envelopes, with the same public key
    var handler = function(ctx){
        console.log('upload.ctx', ctx);
        if (!ctx.success){
            cb(new Error(ctx.errorMessage));
        } else {
            cb(null, ctx);
        }
        self.disconnect(self.__Apendo.uploaded, arguments.callee);
    }

    var multipart = options.hasOwnProperty("isMultipart") || options.hasOwnProperty("isMultiPart");
    if (!multipart){
        options.isMultipart = true;
    }
    self.__Apendo.uploaded.connect(handler);
    self.__Apendo.upload(options);
}

// to download packed envelopes from server
Odnepa.prototype.download = function(options, cb){
    var self = this;
    if (!cb){
        cb = self.debug;
    }
    if (!options){
        return cb (new Error("context is required"))
    }
    if (!options.objId || !options.owner){
        return cb (new Error("context owner and objId are required"))
    }
    if (!options.envelope || typeof options.envelope != "string"){
        return cb (new Error("context envelope is required"))
    }
    if (!options.packages || !Array.isArray(options.packages)){
        return cb (new Error("context packages are required"))
    }

    for (var i = 0; i < options.packages.length; i++){
        var package = options.packages[i];
        if (!package || typeof package != "object"){
            // { owner : "", url : ""}
            return cb(new Error("context envelopes should be an array of non-empty string"));
        }
    }
    var additionalKeys = [];
    for(var k in options){
        if (["objId", "owner", "key", "packages"].indexOf(k) < 0){
            additionalKeys.push(k);
        }
    }

    options.additionalKeys = additionalKeys;
    var handler = function(ctx){
        if (!ctx.success){
            cb(new Error(ctx.errorMessage));
        } else {
            cb(null, ctx);
        }
        self.disconnect(self.__Apendo.downloaded, arguments.callee);
    }
    self.__Apendo.downloaded.connect(handler);
    self.__Apendo.download(options);
}

// to sort unpacked envelopes by sending request to server
Odnepa.prototype.sort = function(options, cb){
    var self = this;
    if (!cb){
        cb = self.debug;
    }
    if (!options){
        return cb (new Error("context is required"))
    }
    if (!options.objId || !options.owner){
        return cb (new Error("context owner and objId are required"))
    }
    if (!options.envelope || typeof options.envelope != "string"){
        return cb (new Error("context envelope is required"))
    }
    if (!options.url || typeof options.url != "string"){
        return cb (new Error("context url is required"))
    }
    if (!options.accessToken || typeof options.accessToken != "string"){
        return cb (new Error("context access token is required"))
    }
    var additionalKeys = [];
    for(var k in options){
        if (["objId", "owner", "url", "accessToken"].indexOf(k) < 0){
            additionalKeys.push(k);
        }
    }

    options.additionalKeys = additionalKeys;
    var handler = function(ctx){
        if (!ctx.success){
            cb(new Error(ctx.errorMessage));
        } else {
            cb(null, ctx);
        }
        self.disconnect(self.__Apendo.sorted, arguments.callee);
    }
    self.__Apendo.sorted.connect(handler);
    self.__Apendo.sort(options);
}

Odnepa.prototype.open = function(href){
    this.__Apendo.fileOpenExternally({ href : href});
}

// user login
Odnepa.prototype.login = function(){}

// user logout
Odnepa.prototype.logout = function(){}

// get version
Odnepa.prototype.version = function(){
    console.log ("0.1.0");
}

// debug function
Odnepa.prototype.debug = function (err, ctx){
    console.log ("err", err, "ctx", ctx);
}

var LT17 = new Odnepa();
window.onunload = function(){
    LT17.unregisterAll();
}

// moment
moment.lang("id");
LT17.moment = moment;

