ppmessageModule.factory("yvMain", [
    "$q",
    "$timeout",
    "$rootScope",
    "yvDB",
    "yvLog",
    "yvSys",
    "yvAPI",
    "yvNav",
    "yvNoti",
    "yvMime",
    "yvSend",
    "yvUser",
    "yvLink",
    "yvFile",
    "yvType",
    "yvBase",
    "yvLogout",
    "yvMessage",
    "yvConstants",
function ($q, $timeout, $rootScope, yvDB, yvLog, yvSys, yvAPI, yvNav, yvNoti, yvMime, yvSend, yvUser, yvLink, yvFile, yvType, yvBase, yvLogout,
          yvMessage, yvConstants) {

    var scope = $rootScope.$new();

    scope.$on("event:add_message", function(event, message) {
        _add_message(message, true, null);
    });

    scope.$on("event:receive_ack_message", function(event, message) {
        _receive_ack_message(message);
    });
    
    scope.$on("event:update_message_all", function(event, message, params) {
        _update_message_all(message, params);
    });

    scope.$on("event:update_message_status", function(event, message, status) {
        _update_message_status(message, status);
    });

    scope.$on("event:online", function(event, params) {
        _handle_online_message(params);
    });
   
    function _handle_online_message(message) {
        yvLog.log("receive online message", message);
        var object = yvBase.get("object", message.user_uuid);
        if (!object) return;
        $timeout(function () {
            object.extra_data = message.extra_data;
            switch (message.browser) {
            case yvConstants.ONLINE_STATUS.ONLINE:
                object.browser_online = true;
                break;
            case yvConstants.ONLINE_STATUS.OFFLINE:
                object.browser_online = false;
            default:
                // keep unchanged
            }
            switch (message.mobile) {
            case yvConstants.ONLINE_STATUS.ONLINE:
                object.mobile_online = true;
                break;
            case yvConstants.ONLINE_STATUS.OFFLINE:
                object.mobile_online = false;
            default:
                // keep unchanged
            }
        });
    }

    function _init_yvdb(callback) {
        if (yvSys.has_db()) {
            return yvDB.init_yvdb(callback);
        }
        _set_server();
        callback && callback(null);
    }


    function _init_userdb(uuid, callback) {
        if (yvSys.has_db()) {
            return yvDB.init_userdb(uuid, callback);
        }
        callback && callback();
    }


    function _add_login_user(data, callback) {
        var user = yvUser.update_user_from_login(data);
        
        yvLink.get_real_icon(user.icon, function (icon) {
            user.icon = icon;
            yvBase.mset("object", user.uuid, {
                name: user.name,
                icon: user.icon,
                email: user.email,
                fullname: user.fullname,
                signature: user.signature,
                updatetime: user.updatetime
            });
            
            if (!yvSys.has_db()) {
                callback && callback();
                return;
            }

            yvDB.add_login_user(user, data.app, function () {
                yvDB.init_userdb(user.uuid, function () {
                    yvDB.add_object(user);
                    callback && callback();
                });
            });
        });
    }


    function _set_server() {
        if (yvSys.has_db()) {
            return yvDB.query_server();
        }
        var server = {
            id: 0,
            name: "web",
            host: window.location.hostname,
            port: window.location.port,
            protocol: window.location.protocol + "//"
        };
        yvAPI.set_server(server);
    }


    function _unread_zero(conversation) {
        conversation.unread = 0;
        if (yvSys.has_db()) {
            yvDB.unread_zero(conversation.uuid);
        }
    }


    function _delete_message(conversation, message) {
        conversation.delete_message(message);
        if (yvSys.has_db()) {
            yvDB.delete_message(message);
        }
    }


    function _set_audio_read(message) {
        message.status = yvConstants.RECV_STATUS.RECV_PLAY;

        if (yvSys.has_db()) {
            yvDB.set_audio_read(message.id);
        }
    }


    function _delete_conversation(conversation) {
        yvBase.remove("conversation", conversation.uuid);

        if (yvSys.has_db()) {
            yvDB.delete_conversation(conversation);
        }
    }

    function _delete_all_conversations(callback) {
        yvBase.get_scope("conversation").page = 0;
        yvBase.get_scope("conversation").dict = {};

        if (yvSys.has_db()) {
            yvDB.delete_all_conversations(callback);
        } else {
            callback && callback();
        }
    }

    function _delete_all_contacts(callback) {
        yvBase.get_scope("contact").dict = {};

        if (yvSys.has_db()) {
            yvDB.delete_all_contacts(callback);
        } else {
            callback && callback();
        }
    }

    // key can be 'icon', 'fullname', 'signature'
    function _update_current_user(key, value) {
        if (["icon", "fullname", "signature"].indexOf(key) === -1) {
            return;
        }

        yvUser.set(key, value);
        yvBase.set("object", yvUser.get("uuid"), key, value);

        if (yvSys.has_db()) {
            yvDB.update_current_user(key, value);
        }
    }


    function _update_noti_settings(key, value) {
        if (["show_badge", "mute_notification", "silence_notification"].indexOf(key) === -1) {
            return;
        }

        yvUser.set(key, value);
        
        if (yvSys.has_db()) {
            yvDB.update_noti_settings(key, value);
        } 
    }

    function _update_android_notification_type(value) {
        if (!yvSys.in_android_app()) {
            return;
        }
        if ([yvConstants.NOTIFICATION_TYPE.GCM, yvConstants.NOTIFICATION_TYPE.MQTT].indexOf(value) === -1) {
            return;
        }

        yvUser.set("android_notification_type", value);
        
        if (yvSys.has_db()) {
            yvDB.update_android_notification_type(value);
        } 
    }
    
    function _add_object(object, callback) {
        if (!object) {
            return;
        }

        var local_object = yvBase.get("object", object.uuid);
        if (local_object && local_object.updatetime >= parseInt(object.updatetime)) {
            callback && callback(local_object);
            return;
        }

        var ppobject = yvBase.create("object", object.uuid);
        yvBase.add("object", ppobject);
        yvBase.update("object", ppobject, object, function (obj) {
            callback && callback(obj);
        });
    }


    function _check_object(uuid) {
        var object = yvBase.get("object", uuid);
        console.log("check object: %o, by uuid: %s.", object, uuid);
        object || yvBase.set("object", uuid, "need_update", true);
    }


    function _add_contact(contact) {
        yvBase.mset("contact", contact.uuid, {
            name: contact.user_name,
            email: contact.user_email,
            fullname: contact.user_fullname,
            signature: contact.user_signature,
            is_portal_user: !!contact.is_portal_user,
            is_service_user: !!contact.is_service_user
        });

        _add_object(contact, function (object) {
            yvBase.set("contact", contact.uuid, "icon", object.icon);
        });

        if (yvSys.has_db()) {
            yvDB.add_contact(contact);
        }
    }


    function _is_conversation_online (conversation) {
        switch (conversation.type) {
        case yvConstants.CONVERSATION_TYPE.S2S:
            return true;
        case yvConstants.CONVERSATION_TYPE.P2S:
            var portal_user = yvBase.get("object", conversation.user_uuid);
            if (!portal_user) return false;
            if (portal_user.mobile_online || portal_user.browser_online) return true;
        default:
            return false;
        }
    }


    function _add_conversation(conv, callback) {
        var conversation = yvBase.create("conversation", conv.uuid);
        yvBase.add("conversation", conversation);
        yvBase.update("conversation", conversation, conv, callback);
    }


    // Called when chat with contact. open real conversation if conversation_name matchs a local conversation.
    function _open_conversation(params, callback) {
        var conv_list = yvBase.get_list("conversation"), conv = null;

        for (var i in conv_list) {
            conv = conv_list[i];
            if (conv.type === params.conversation_type && conv.name === params.conversation_name) {
                yvBase.active("conversation", conv);
                callback && callback(conv);
                return;
            }
        }

        yvAPI.create_conversation(params, function (data) {
            _add_conversation(data, function (conversation) {
                yvBase.active("conversation", conversation);
                callback && callback(conversation);
            });
        });
    }


    function _add_message_to_conversation(conversation, message, callback) {
        var active = yvBase.active("conversation");
        if (!active || (active.uuid != conversation.uuid && message.direction === yvConstants.MESSAGE_DIR.DIR_IN)) {
            conversation.unread += 1;
        }
        if (conversation.latest_message && conversation.latest_message.timestamp > message.timestamp) {
            message.timestamp = conversation.latest_message.timestamp + 1;
        }
        conversation.add_message(message);
        callback && callback();

        if (yvSys.has_db()) {
            yvDB.insert_message(message);
            yvDB.update_conversation_unread(conversation);
        }
    }

    function _update_conversation(message, callback) {
        if (!message) {
            callback && callback();
            return;
        }

        var conversation = yvBase.get("conversation", message.conversation_uuid);
        if (conversation) {
            _add_message_to_conversation(conversation, message, callback);
            return;
        }

        conversation = yvBase.create("conversation", message.conversation_uuid);
        conversation.need_update = true;
        conversation.show = false;
        yvBase.add("conversation", conversation);
        _add_message_to_conversation(conversation, message, callback);
    }


    function _download_audio(message, callback) {
        var ext = null, audio = null;
        var body = angular.fromJson(message.body);

        if (yvSys.in_ios_app()) {
            ext = "m4a";
        } else if (yvSys.in_android_app()) {
            ext = "amr";
        }

        audio = body[ext];
        yvAPI.download_file(audio.fid, null, function () {
            message.file = audio.fid;
            message.mime = "audio/" + ext;
            message.duration = audio.dura;
            cb(message);
        });
    }


    function _download_message_file(message, cb) {
        var _ext = null, _url = null, _body = null;

        if (yvType.is_text(message)) {
            return cb(message);
        }

        if (yvType.is_gps_location(message)) {
            _body = angular.fromJson(message.body);
            _body.zoom = Math.round(_body.zoom);
            _url = yvAPI.get_amap_url(_body);
            if (!yvSys.in_mobile_app()) {
                message.file = _url;
                return cb(message);
            }

            yvAPI.download_file(_url, null, function (file) {
                message.file = file.name;
                message.mime = "image/jpeg";
                cb(message);
            });
            return;
        }

        if (yvType.is_txt(message)) {
            _body = angular.fromJson(message.body);
            message.mime = "text/plain";

            if (!yvSys.in_mobile_app()) {
                message.file = _body.fid;
                return cb(message);
            }

            yvAPI.download_file(_body.fid, null, function (file) {
                message.file = _body.fid;
                cb(message);
            });
            return;
        }

        if (yvType.is_audio(message)) {
            if (!yvSys.in_mobile_app()) {
                _body = angular.fromJson(message.body);
                message.duration = _body.mp3.dura;
                message.file = _body.mp3.fid;
                message.mime = "audio/mp3";
                return cb(message);
            }
            return _download_audio(message, cb);
        }

        if (yvType.is_image(message)) {
            _body = angular.fromJson(message.body);
            message.mime = _body.mime;

            if (!yvSys.in_mobile_app()) {
                message.thumbnail = _body.thum;
                message.file = _body.orig;
                return cb(message);
            }

            yvAPI.download_file(_body.thum, null, function () {
                message.thumbnail = _body.thum;
                cb(message);
            });
            return;
        }

        if (yvType.is_file(message)) {
            _body = angular.fromJson(message.body);
            message.mime = _body.mime;
            message.name = _body.name;
            message.size = _body.size;
            message.file = _body.fid;
            return cb(message);
        }

        console.error("download message file can't handle message --> %o", message);
    }


    function _receive_ack_message(message) {
        if (!message || message.code == 0) {
            return;
        }
        yvLog.error("receive ack message error", message.reason, message);
        var conversation = yvBase.get("conversation", message.extra.conversation_uuid);
        if (conversation) { 
            var target = conversation.get_message_by_tid(message.extra.uuid);
            target && _update_message_status(target, yvConstants.SEND_STATUS.SEND_ERROR);
        }
    }

    
    function _ack_message(_message, _success, _error) {
        var list = [_message.push_uuid];
        _ack_message_list(list, _success, _error);
    }


    function _ack_message_list(_uuid_list, _success, _error) {
        var _ack_param = {"list": _uuid_list};
        yvAPI.ack_message(_ack_param, _success, _error, _error);
    }


    function _check_logout(_message, _cb) {
        var _device_uuid = yvUser.get("device_uuid");
        if (_message.body === _device_uuid) {
            yvLogout.local_logout();
        }
        _cb && _cb();
    }


    function _handle_message(message) {
        _download_message_file(message, function (msg) {
            _update_conversation(msg);
        });
    }


    //incoming message
    function _add_message(raw_message, with_ack) {
        var message = yvMessage.get_standard_message(raw_message);

        if (yvType.is_logout(message)) {
            with_ack && _check_logout(message);
            return;
        }

        if (!message.conversation_uuid) {
            with_ack && _ack_message(message);
            return;
        }

        _handle_message(message);
        with_ack && _ack_message(message);
        message.from_user ? _add_object(message.from_user) : _check_object(message.from_uuid);
    }


    function _load_conversation_messages(conversation, callback) {
        if (!yvSys.has_db()) {
            callback && callback(0);
            return;
        }

        yvDB.query_messages(conversation.uuid, function (res) {
            var item = null, message = null;
            for (var i = 0, len = res.rows.length; i < len; i++) {
                item = res.rows.item(i);
                message = yvMessage.create(item.task_uuid);
                message.id = item.id;
                angular.forEach(item, function (value, key) {
                    message[key] = value;
                });
                conversation.messages.push(message);
            }
            conversation._refresh();
            callback && callback(len);
        });
    }


    function _update_message_all(message, params) {
        $timeout(function () {
            message.status = params.status;
            message.timestamp = params.timestamp;
            message.task_uuid = params.task_uuid;
        });

        yvSys.has_db() && yvDB.update_message_status(message);
    }


    function _update_message_status(message, status) {
        $timeout(function () {
            message.status = status;
        });
        yvSys.has_db() && yvDB.update_message_status(message);
    }


    function _update_message_file(message, file) {
        // Note: do not use $timeout
        message.file = file;
        yvSys.has_db() && yvDB.update_message_file(message);
    }


    // Upload file for TXT, IMAGE, AUDIO, GPS_LOCATION message in native iOS and Android.
    // Message with a valid message_mime will be uploaded, or it will be directly sended.
    // All message in PC and mobile chrome, file message in Android, will be uploaded by other way.
    function _upload_message_file(_message, _cb) {
        if ((yvSys.in_android_app() && yvType.is_file(_message)) || !yvSys.in_mobile_app()) {
            _cb && _cb(_message);
            return;
        }

        // For new type of message, keep message_mime invalid if it should not be uploaded
        if (yvType.is_text(_message) || !_message.mime) {
            _update_message_status(_message, yvConstants.SEND_STATUS.SEND_SENDING);
            _cb && _cb(_message);
            return;
        }

        _update_message_status(_message, yvConstants.SEND_STATUS.SEND_UPLOADING);
        console.log("upload message file", _message.mime, _message.subtype, _message);

        // TXT, AUDIO, IMAGE, GPS_LOCATION
        yvAPI.upload_file(_message.file, _message.mime, function (res) {
            if (res === null) {
                _update_message_status(_message, yvConstants.SEND_STATUS.SEND_ERROR);
                console.error("upload message file error", _message);
                return;
            }

            _message.fid = JSON.parse(res).fuuid;
            _update_message_status(_message, yvConstants.SEND_STATUS.SEND_SENDING);
            _cb && _cb(_message);
        });
    }


    // save message to local storage before send it to server, including 2 steps:
    // 1. insert and display message in this conversation
    // 2. update latest message of this converstion
    function _prepare_send(conversation, raw_message, callback) {
        var message = yvMessage.create_sending_message(conversation, raw_message);
        conversation.add_message(message);
        callback && callback(message);

        if (yvSys.has_db()) {
            yvDB.insert_message(message);
        }
    }


    function _send_message(conversation, raw_message, callback) {
        _prepare_send(conversation, raw_message, function (message) {
            var _upload_message = typeof callback === "function" ? callback : _upload_message_file;
            _upload_message(message, function (msg) {
                yvSend.send_message(msg, false);
            });
        });
    }


    function _forward_message_new(_forward, _cb) {
        var _m = yvMessage.create_forward_message(_forward);

        _prepare_send(_m, function (_message) {
            yvSend.send_message(_message, true);
        });
    }


    function _update_contacts_from_server() {
        var defer = $q.defer();

        function __error() {
            defer.reject("error in update contacts from server");
        }

        yvAPI.get_service_user_list(function (data) {
            console.log("updating contacts from server..., total count: %d.", data.list.length);
            _delete_all_contacts(function () {
                angular.forEach(data.list, function (user) {
                    _add_contact(user);
                });
                defer.resolve("finish update contacts from server");
            });
        }, __error, __error);

        return defer.promise;
    }


    function _reorder_conversations(conv1, conv2) {
        if (conv1.latest_message && !conv2.latest_message) {
            return -1;
        }
        if (!conv1.latest_message && conv2.latest_message) {
            return 1;
        }
        if (!conv1.latest_message && !conv2.latest_message) {
            var upt1 = yvMessage.string_to_ts(conv1.updatetime);
            var upt2 = yvMessage.string_to_ts(conv2.updatetime);
            return upt2 - upt1;
        }
        var t1 = yvMessage.string_to_ts(conv1.latest_message.createtime);
        var t2 = yvMessage.string_to_ts(conv2.latest_message.createtime);
        return t2 - t1;
    }


    function _add_conversation_from_api_reserve(list) {
        var user_set = {};
        angular.forEach(list, function (conv) {
            if (yvBase.get("conversation", conv.uuid) || !conv.latest_message) {
                return; // reserve is old or no latest message, ignore it
            }
            _add_conversation(conv, null);
            user_set[conv.user_uuid] = conv.from_user;
        });
        angular.forEach(user_set, function (user) {
            _add_object(user);
        });
    }


    function _update_conversations_from_server() {
        var defer = $q.defer();
        var args = { "page_offset": 0, "page_size": yvSys.page_size() };

        function __error() {
            defer.reject("error in update conversations from server");
        }

        yvAPI.get_conversation_page(args, function (data) {
            console.log("updating conversations from server..., total count: %d.", data.list.length, data.list);
            _delete_all_conversations(function () {
                _add_conversation_from_api_reserve(data.list);
                defer.resolve("finish update conversations from server");
            });
        }, __error, __error);

        return defer.promise;
    }

    function _load_objects_from_database() {
        var _defer = $q.defer();

        yvDB.load_objects(function (tx, res) {
            var item = null;

            for (var i = 0, len = res.rows.length; i < len; i++) {
                item = res.rows.item(i);
                yvBase.mset("object", item.uuid, item);
            }
            _defer.resolve("finish load objects");
        }, function (tx, error) {
            _defer.reject("error in load objects");
        });

        return _defer.promise;
    }


    function _load_conversations_from_database() {
        var _defer = $q.defer();

        yvDB.load_conversations(function (tx, res) {
            var item = null;
            for (var i = 0, len = res.rows.length; i < len; i++) {
                item = res.rows.item(i);
                yvDB.get_latest_message(item, function (message, conv) {
                    conv.latest_message = message;
                    yvBase.mset("conversation", conv.uuid, conv);
                });
            }
            _defer.resolve("finish load conversations");
        }, function (tx, error) {
            _defer.reject("error in load conversations");
        });

        return _defer.promise;
    }


    function _load_contacts_from_database() {
        var _defer = $q.defer();

        yvDB.load_contacts(function (tx, res) {
            var item = null;

            for (var i = 0, len = res.rows.length; i < len; i++) {
                item = res.rows.item(i);
                item.is_portal_user = !!item.is_portal_user;
                item.is_service_user = !!item.is_service_user;
                yvBase.mset("contact", item.uuid, item);
            }
            _defer.resolve("finish load contacts");
        }, function (tx, error) {
            _defer.reject("error in load contacts");
        });

        return _defer.promise;
    }


    function _update_all_from_server(success, error) {
        var p0 = _update_contacts_from_server();
        var p1 = _update_conversations_from_server();

        yvBase.reset();

        $q.all([p0, p1]).then(function (value) {
            success && success();
        }, function (reason) {
            yvLog.log("update all from server error:", reason);
            error && error();
        });
    }


    function _load_all_from_database(cb) {
        var p0 = _load_objects_from_database();
        var p1 = _load_contacts_from_database();
        var p2 = _load_conversations_from_database();

        yvBase.reset();
        
        $q.all([p0, p1, p2]).finally(function () {
            cb && cb();
        });
    }


    function _reload(callback) {
        _update_all_from_server(function () {
            callback && callback();
        }, function () {
            _offline_reload(callback);
        });
    }

    function _offline_reload(callback) {
        if (yvSys.has_db()) {
            _load_all_from_database(function () {
                callback && callback();
            });
        }
    }
    

    return {
        init_yvdb: function (callback) {
            _init_yvdb(callback);
        },

        init_userdb: function (uuid, callback) {
            _init_userdb(uuid, callback);
        },

        add_login_user: function (userdata, callback) {
            _add_login_user(userdata, callback);
        },

        set_server: function () {
            _set_server();
        },

        unread_zero: function (conversation) {
            _unread_zero(conversation);
        },

        delete_message: function (conversation, message) {
            _delete_message(conversation, message);
        },

        set_audio_read: function (message) {
            _set_audio_read(message);
        },

        delete_conversation: function (conversation) {
            _delete_conversation(conversation);
        },

        update_conversations_from_server: function () {
            return _update_conversations_from_server();
        },

        reload: function (offline, callback) {
            if (!!offline) {
                _offline_reload(callback);
            } else {
                _reload(callback);
            }
        },
        
        open_conversation: function (params, callback) {
            _open_conversation(params, callback);
        },

        is_conversation_online: function (conversation) {
            return _is_conversation_online(conversation);
        },
        
        add_conversation_from_api_reserve: function (list, callback) {
            _add_conversation_from_api_reserve(list, callback);
        },

        add_message: function (message, with_ack, callback) {
            _add_message(message, with_ack, callback);
        },

        prepare_send: function (conversation, raw_message, callback) {
            _prepare_send(conversation, raw_message, callback);
        },
        
        send_message: function (conversation, raw_message, callback) {
            _send_message(conversation, raw_message, callback);
        },

        load_conversation_messages: function (conversation, callback) {
            _load_conversation_messages(conversation, callback);
        },

        update_message_status: function (message, status) {
            _update_message_status(message, status);
        },

        update_message_file: function (message, file) {
            _update_message_file(message, file);
        },

        update_message_all: function (message, params) {
            _update_message_all(message, params);
        },

        update_current_user: function (key, value) {
            _update_current_user(key, value);
        },

        update_noti_settings: function (key, value) {
            _update_noti_settings(key, value);
        },

        update_android_notification_type: function (value) {
            _update_android_notification_type(value);
        },
    };

}]);
