var SERVER_HOST = "192.168.137.123";
// var SERVER_HOST = "192.168.1.101";

/*
 actions and states
 */
//mobile actions
var ACTION_CHANGETVNAME = 0;
var ACTION_PROGRAMINFO = 1;
var ACTION_SOURCELIST = 3;
var ACTION_CHANGESOURCE = 4;
var ACTION_CHANGEVOLUME = 5;
var ACTION_CHANGECHANNEL = 6;
var ACTION_APPVERSION = 7;
var ACTION_APPLIST = 8;
var ACTION_UNINSTALLAPP = 9;
var ACTION_LAUNCHAPP = 10;
var ACTION_TIMESEEK = 11;
var ACTION_RESETIQQI = 12;

//TV state
var TVSTATE_LIVETV = 100;
var TVSTATE_MEDIADLNA = 101;
var TVSTATE_MEDIADMP = 102;
var TVSTATE_TSHIFT = 103;
var TVSTATE_SOURCE = 104;
var TVSTATE_APP = 105;

//TV actions
var TV_ACTION_VKBINVOKE = 150;
var TV_ACTION_BROWSERLAUNCH = 151;
var TV_ACTION_CHANNELVER_CHANGE = 152;
var TV_ACTION_APPLISTVER_CHANGE = 153;
var TV_ACTION_VOLUME_CHANGE = 154;

//error code
// var REMOTEAPP_CLI_ERRCODE = {
//     REMOTEAPP_CLI_SUCCESS: 0,
//     REMOTEAPP_CLI_FAILED: -1
// }

//clientCallback :handle messages
// function ClientCallback(uuid,actions,data){
//     this.uuid = uuid;
//     this.actions = actions;
//     this.data = data;
// }


/*
 Topics for tv publish
 */

//root topic
var TOPIC_UI_SERVICE = '/remoteapp/tv/ui_service/#';

//broadcast state topics
var BROADCAST_STATE_ROOT = '/remoteapp/mobile/broadcast/ui_service/state/';
//livetv
var TOPIC_TVSTATE_LIVETV = BROADCAST_STATE_ROOT + 'livetv';
//dlna
var TOPIC_TVSTATE_MEDIADLNA = BROADCAST_STATE_ROOT + 'mediadlna';
//dmp
var TOPIC_TVSTATE_MEDIADMP = BROADCAST_STATE_ROOT + 'mediadmp';
//TShift
var TOPIC_TVSTATE_TSHIFT = BROADCAST_STATE_ROOT + 'tshift';
//app
var TOPIC_TVSTATE_APP = BROADCAST_STATE_ROOT + 'app';

//broadcast action topics
var BROADCAST_ACTION_ROOT = '/remoteapp/mobile/broadcast/ui_service/actions/';
//IME
var TOPIC_TV_ACTION_VKBINVOKE = BROADCAST_ACTION_ROOT + 'vkbinvoke';
//browser
var TOPIC_TV_ACTION_BROWSERLAUNCH = BROADCAST_ACTION_ROOT + 'browserlaunch';

//change channel
var TOPIC_CHANGECHANNEL
//reply
var REPLY_TOPIC_ROOT = '/remoteapp/mobile/';


//for validate mediaDLNA\mediaDmp\TShift
var Message_DDT = {
    name: "string",
    totaltime: "number",
    curtime: "number",
    starttime: "number",
    playstate: "string",
    mediatype: "string"
}

var Message_LiveTV = {
    type: "string",
    id: "string",
    eventid: "string",
    progname: "string",
    starttime: "number",
    endtime: "number",
    detail: "string"
}

var Message_APP = {
    name: "string",
    url: "string"
}

var Message_InfoItem = {
    url: "string",
    isunInstalled: "string",
    name: "string",
    icon: "string"
}

/**
 * Example:
 * <code><pre>
 // part 1: create an instance
 var remoteApp = new Remoteapp.Instance(appName);
 // part 2: set callbacks
 remoteapp._onConnectToServiceSuccess = function;
 remoteapp._onConnectToServiceFailed = function;
 remoteapp._onStartListenSuccess = function;
 remoteapp._onStartListenFailed = function;
 remoteapp._onStopListenSuccess = function;
 remoteapp._onStopListenFailed = function;
 remoteapp._onServiceConnectionLost = function;
 remoteapp._onSendMessageComplete = function;
 remoteapp._onMessageArrived = function;
 // part 3: invoke methods
 remoteapp.connectToService(appname, options);
 remoteapp.disconnectFromService(appname);
 remoteapp.startListen(appname, options);
 remoteapp.stopListen(appname, options);
 remoteapp.sendMessage_AppState(appname, appstate);
 remoteapp.sendMessage_Preview(appname, preview);
 * </pre></code>
 */
Remoteapp = (function () {


    /**
     *  global variables
     *  the uuid arry indicated all connecting mobiles
     *  @private
     */
    var UUIDs = [];

    /**
     *  Unique connection status type identifiers, with associated integer values.
     *  @private
     */
    var CONNECTION_STATUS = {
        CONNECTED: 1,
        DISCONNECTED: 2
    };

    /**
     *  Unique message type identifiers, with associated integer values.
     *  @private
     */
    var MESSAGE_TYPE = {
        ON_MESSAGE_ARRIVED: 1,
        DISCONNECTFROMSERVICE: 2,
        STARTLISTEN: 3,
        STOPLISTENER: 4,
        SENDMESSAGEAPPSTATE: 5,
        SENDMESSAGEPREVIEW: 6
    };

    /**
     *  Collection of validate connection options used for checking coming parameters
     *  @private
     */
    var Options_connect = {
        timeout: "number",
        userName: "string",
        password: "string",
        willMessage: "string",
        keepAliveInterval: "number",
        cleanSession: "boolean",
        useSSL: "boolean",
        invocationContext: "object",
        onSuccess: "function",
        onFailure: "function",
        hosts: "object",
        ports: "object",
        mqttVersion: "number"
    };

    /**
     *  Collection of validate subscribe options used for checking coming parameters
     *  @private
     */
    var Options_subscribe = {
        qos: "number",
        invocationContext: "object",
        onSuccess: "function",
        onFailure: "function",
        timeout: "number"
    };

    /**
     *  Collection of validate unsubscribe options used for checking coming parameters
     *  @private
     */
    var Options_unsubscribe = {
        invocationContext: "object",
        onSuccess: "function",
        onFailure: "function",
        timeout: "number"
    };

    /**
     * Assemble an options object with different parameters.
     * Used to make a needed object which passed into the paho API.
     * @param {String} appName - User's name.
     * @param {Function} cb_success - successful callback function.
     * @param {Function} cb_failed - failed callback function.
     * @param {Object} options - necessary keys and types that provided by user.
     * @private
     */
    var Options = function (appName, cb_success, cb_failed, options) {
        //TODO: add parameters checking method
        //this.appname = appName;
        this.onSuccess = cb_success;
        this.onFailure = cb_failed;

        for (var p in options) {
            var p_name = p;
            this[p_name] = options[p];
        }
    };

    // Messages interface is a data format used for communicating
    // between app and mqtt server with the JsObject and cJson type.
    // And JS object type is the data flow format between App and Remoteapp.js,
    // while cJson is between Remoteapp.js and PAHO.

    /**
     * App state type in the Messages.
     * Used to passing information to Mobile when current focus changed and mouse over events.
     * @private
     */
    var Messages_AppState = {
        source: "string",
        categories: "object",
        actions: "object",
        delegateid: "string",
        delegates: "object",
        statetype: "string",
        timestamp: "number",
        statehash: "string",
        listhash: "string",
        listsize: "number",
        listcurrent: "number",
        infodetail: "string",
        infotitle: "string",
        infodescription: "string",
        infotime: "string",
        bookmarktitle: "string",
        bookmarkintent: "string",
        bookmarkstatus: "string",
        playerstate: "string",
        playerprogress: "number",
        playerduration: "number",
        playerspeed: "number"
    };

    /**
     * Preview type in the Messages.
     * Used to passing response information to Mobile for the received action messages.
     * @private
     */
    var Messages_Preview = {
        source: "string",
        uuid: "string",
        timestamp: "number",
        uri: "string",
        primarytext: "string",
        secondarytext: "string",
        tertiarytext: "string",
        starrating: "number",
        rawdata: "object"
    };

    /**
     * Action type in the Messages.
     * Used to passing information to TV when mobile had some actions.
     * @private
     */
    var Messages_Action = {
        source: "string",
        uuid: "string",
        actionid: "number",
        command: "string",
        delegateid: "string",
        datainput: "string",
        datakeycode: "number",
        searchinput: "string",
        bitmask: "number",
        appname: "string",
        bookmarkintent: "string",
        thumbnailid: "string",
        itemid: "string",
        rownum: "number"
    };

    /**
     * List type in the Messages.
     * Used to passing information as a list format.
     * @private
     */
    var Messages_List = {
        items: "object"
    };

    /**
     * Item data of the List type in the Messages.
     * Used to passing information as a list format.
     * @private
     */
    var Messages_Item = {
        id: "string",
        type: "number",
        thumbnailid: "string",
        primarytext: "string",
        secondarytext: "string",
        tertiarytext: "string",
        queue: "boolean",
        iscurrent: "boolean"
    };

    /**
     * Validate an object's parameter names to ensure they
     * match a list of expected variables name for this option
     * type. Used to ensure option object passed into the API don't
     * contain erroneous parameters.
     * @param {Object} obj - User options object
     * @param {Object} keys - valid keys and types that may exist in obj.
     * @throws {Error} Invalid option parameter found.
     * @private
     */
    var validate = function (obj, keys) {
        for (var key in obj) {
            if (obj.hasOwnProperty(key)) {
                if (keys.hasOwnProperty(key)) {
                    if (typeof obj[key] !== keys[key])
                        throw new Error(format(ERROR.INVALID_TYPE, [typeof obj[key], key]));
                } else {
                    var errorStr = "Unknown property, " + key + ". Valid properties are:";
                    for (var key in keys)
                        if (keys.hasOwnProperty(key))
                            errorStr = errorStr + " " + key;
                    throw new Error(errorStr);
                }
            }
        }
    };

    /**
     * An Instance object for each App which needs remoteapp service. The Instance
     * is a app-based object that every app has its own instance, including callbacks.
     * It manages different message and callbacks function between app and mqtt server.
     * @param {String} appName - User's name of each app.
     * @private
     */
    var Instance = function (appName) {

        /**
         *  global variables in a instance
         *  @private
         */
        var that = this;
        that.uuid = null;
        that.owner = appName;
        that.connectionStatus = CONNECTION_STATUS.DISCONNECTED;

        DBG_INFO("create a remoteapp instance.");
        /**
         *  HandlerMessage used to handle all messages communicating between app and mqtt server.
         *  @private
         */
        var HandleMessage = function (type, data) {
            switch (type) {
                case MESSAGE_TYPE.ON_MESSAGE_ARRIVED:
                    // get uuid from payload
                    var topic = data['destinationName'] || {};
                    var fields = topic.split('/');
                    for (var p in fields) {
                        DBG_INFO(p + " = " + fields[p]);
                    }

                    // deal with arrived message according to different topics
                    if ((fields[2] === 'app') && (fields[3] === that.owner)) {
                        // Action topic: /remoteapp/app/${appname}/${delegate}/actions/${actionname}
                        // action message
                        // TODO: give action to app for preview
                        //var app = fields[3] || {};
                        // TEMP: preview data
                        DBG_INFO("data['payloadString'] = " + data['payloadString']);
                        var preview = JSON.parse(data['payloadString']);
                        for (var o in preview) {
                            DBG_INFO(o + " = " + preview[o]);
                        }

                        // invoke the app's callback here
                        that._onMessageArrived(data);
                    } else if (fields[2] === 'mobile') {
                        // Mobile topic: "/remoteapp/mobile/${uuid}"
                        // acquire the new uuid value and add it into array for global
                        that.uuid = fields[3] || {};
                        var duplicated = false;
                        for (var i in UUIDs) {
                            //DBG_INFO( " UUIDs[" + i +"] = " + UUIDs[i]);
                            if (UUIDs[i] === that.uuid) {
                                duplicated = true;
                            }
                        }
                        if (!duplicated) {
                            // add the new uuid into array
                            UUIDs[UUIDs.length] = that.uuid;
                        }
                    } else {
                        // Error: bad format of data
                        DBG_INFO("Error: bad format of the arrived payloadMessage");
                    }
                    break;

                case MESSAGE_TYPE.DISCONNECTFROMSERVICE:
                    // TODO: implemented function
                    break;

                case MESSAGE_TYPE.SENDMESSAGEAPPSTATE:
                    // TODO: implemented function
                    break;

                case MESSAGE_TYPE.SENDMESSAGEPREVIEW:
                    // TODO: implemented function
                    break;

                default:
                    DBG_INFO("Error: Handle received an unexpected message, type = " + type);
            }
        };

        // create a new instance based on appName for the current App
        DBG_INFO("new Paho.MQTT.Client.");
        var instance = new Paho.MQTT.Client(SERVER_HOST, 10001, appName);

        // invoked methods for each app
        this.connectToService = function (appname, options) {
            //TODO validate appname?

            DBG_INFO("connectToService() is called");
            // check current connection status
            if (that.connectionStatus === CONNECTION_STATUS.CONNECTED) {
                //throw new Error("WebSocket connection had been connected.");
                DBG_INFO("Error: WebSocket connection had been connected.");
            }

            options = options || {};
            validate(options, Options_connect);

            var cb_success = this.p_onConnectToServiceSuccess;
            var cb_failed = this.p_onConnectToServiceFailed;

            // get connection options for the current instance
            var opt = new Options(appName, cb_success, cb_failed, options);
            DBG_INFO("instance.connect() called.");
            instance.connect(opt);
        };
        this.disconnectFromService = function (appname) {
            DBG_INFO("disconnectFromService() is called");
            // check current connection status
            if (that.connectionStatus === CONNECTION_STATUS.DISCONNECTED) {
                //throw new Error("WebSocket connection had been disconnected.");
                DBG_INFO("Error: WebSocket connection had been disconnected.");
            } else {
                that.connectionStatus = CONNECTION_STATUS.DISCONNECTED;
                instance.disconnect();
            }

        };
        this.startListen = function (appname, options) {
            DBG_INFO("startListen() is called");
            // check current connection status
            if (that.connectionStatus === CONNECTION_STATUS.DISCONNECTED) {
                //throw new Error("WebSocket connection had been disconnected.");
                DBG_INFO("Error: WebSocket connection had been disconnected.");
            }

            options = options || {};
            validate(options, Options_subscribe);

            // /remoteapp/tv/ui_service/#
            var topic = "/remoteapp/mobile/uuid/#";
            DBG_INFO("topic = ", topic);
            var cb_success = this.p_onStartListenSuccess;
            var cb_failed = this.p_onStartListenFailed;

            // get subscribe options for the current instance
            var opt = new Options(appName, cb_success, cb_failed, options);
            DBG_INFO("APP start subscribe.");
            instance.subscribe(topic, opt);
            // instance.subscribe("/remoteapp/mobile/#", opt);
        };
        this.stopListen = function (appname, options) {
            DBG_INFO("stopListen() is called");
            // check current connection status
            if (that.connectionStatus === CONNECTION_STATUS.DISCONNECTED) {
                DBG_INFO("Error: WebSocket connection had been disconnected.");
                //throw new Error("WebSocket connection had been disconnected.");
            }

            options = options || {};
            validate(options, Options_unsubscribe);

            //topic = "/remoteapp/app/${luncher}/#"
            var topic = "/remoteapp/app/" + appname + "/#";
            DBG_INFO("topic = ", topic);
            var cb_success = this.p_onStopListenSuccess;
            var cb_failed = this.p_onStopListenFailed;

            // get subscribe options for the current instance
            var opt = new Options(appName, cb_success, cb_failed, options);
            instance.unsubscribe(topic, opt);
        };
        this.sendMessage_State = function (topic, data) {
            DBG_INFO("sendMessage_AppState() is called");
            // check current connection status
            if (that.connectionStatus === CONNECTION_STATUS.DISCONNECTED) {
                //throw new Error("WebSocket connection had been disconnected.");
                DBG_INFO("Error: WebSocket connection had been disconnected.");
            }

            //TOPIC: /remoteapp/app/state/main
            // parse JSObject into cJson data
            var message = new Paho.MQTT.Message(JSON.stringify(data));
            //message.destinationName = "/remoteapp/app/state/main";
            message._setDestinationName(topic);
            message._setRetained(true);
            message._setQos(1);

            DBG_INFO("topic   = ", message.destinationName);
            DBG_INFO("payload = ", message.payloadString);
            DBG_INFO("bytes   = ", message.payloadBytes);
            DBG_INFO("retained   = ", message.retained);
            DBG_INFO("qos   = ", message.qos);
            instance.send(message);
        };
        this.sendMessage_Action = function (topic, msg) {
            DBG_INFO("sendMessage is called");
            // check current connection status
            if (that.connectionStatus === CONNECTION_STATUS.DISCONNECTED) {
                //throw new Error("WebSocket connection had been disconnected.");
                DBG_INFO("Error: WebSocket connection had been disconnected.");
            }

            //TOPIC: /remoteapp/app/state/main
            // parse JSObject into cJson data
            var message = new Paho.MQTT.Message(JSON.stringify(msg));
            //message.destinationName = "/remoteapp/app/state/main";
            message._setDestinationName(topic);
            DBG_INFO("topic   = ", message.destinationName);
            DBG_INFO("payload = ", message.payloadString);
            DBG_INFO("bytes   = ", message.payloadBytes);
            instance.send(message);
        };


        this.sendMessage_Preview = function (appname, preview) {
            DBG_INFO("sendMessage_Preview() is called");
            // check current connection status
            if (that.connectionStatus === CONNECTION_STATUS.DISCONNECTED) {
                //throw new Error("WebSocket connection had been disconnected.");
                DBG_INFO("Error: WebSocket connection had been disconnected.");
            }

            preview = preview || {};
            for (var a in preview) {
                DBG_INFO(a + " = " + (typeof a));
                DBG_INFO(a + " = " + (typeof preview[a]));
            }
            validate(preview, Messages_Preview);

            //TOPIC: remoteapp/mobile/${uuid}/data/${actionname}
            // parse JSObject into cJson data
            var message = new Paho.MQTT.Message(JSON.stringify(preview));
            message.destinationName = "remoteapp/mobile/${uuid}/data/${luncher}";
            DBG_INFO("topic   = ", message.destinationName);
            DBG_INFO("payload = ", message.payloadString);
            DBG_INFO("bytes   = ", message.payloadBytes);
            instance.send(message);
        };

        // callback adapters for each instance
        this.p_onConnectToServiceSuccess = function (invocationContext) {
            // log + personal process before app
            DBG_INFO("invocationContext = ", invocationContext);
            that.connectionStatus = CONNECTION_STATUS.CONNECTED;

            // invoke the app's callback
            that._onConnectToServiceSuccess(invocationContext);
        };
        this.p_onConnectToServiceFailed = function (invocationContext, errorCode, errorText) {
            // log + personal process before app
            DBG_INFO("--->> p_onConnectToServiceFailed()...");
            DBG_INFO("invocationContext = ", invocationContext);
            that.connectionStatus = CONNECTION_STATUS.DISCONNECTED;

            // invoke the app's callback
            that._onConnectToServiceFailed(invocationContext, errorCode, errorText);
        };
        this.p_onStartListenSuccess = function (returnCode) {
            DBG_INFO("--->> p_onStartListenSuccess()...");
            // log + personal process before app

            // invoke the app's callback
            that._onStartListenSuccess(returnCode);
        };
        this.p_onStartListenFailed = function (returnCode) {
            DBG_INFO("--->> p_onStartListenFailed()...");
            // log + personal process before app

            // invoke the app's callback
            that._onStartListenFailed(returnCode);
        };
        this.p_onStopListenSuccess = function (invocationContext) {
            DBG_INFO("--->> p_onStopListenSuccess()...");
            // log + personal process before app

            // invoke the app's callback
            that._onStopListenSuccess(invocationContext);
        };
        this.p_onStopListenFailed = function (invocationContext, errorCode, errorMessage) {
            DBG_INFO("--->> p_onStopListenFailed()...");
            // log + personal process before app

            // invoke the app's callback
            that._onStopListenFailed(invocationContext, errorCode, errorMessage);
        };
        this.p_onSendMessageComplete = function (payloadMessage) {
            DBG_INFO("--->> p_onSendMessageComplete()...");
            // log + personal process before app

            // invoke the app's callback
            that._onSendMessageComplete(payloadMessage);
        };
        this.p_onMessageArrived = function (payloadMessage) {
            // DBG_INFO("--->> p_onMessageArrived()...");
            // log + personal process before app
            DBG_INFO("---msgArrived,payloadMessage:--- " +
                "\n [topic] = " + payloadMessage['destinationName'] +
                ";\n [payloadString] = " + payloadMessage['payloadString'] +
                ";\n [payloadBytes]  = " + payloadMessage['payloadBytes'] +
                ";\n [Qos]= " + payloadMessage['qos'] +
                ";\n [retained] = " + payloadMessage['retained'] +
                ";\n [duplicate] = " + payloadMessage['duplicate']);
            that.onMessageArrived(payloadMessage);
            // HandleMessage(MESSAGE_TYPE.ON_MESSAGE_ARRIVED, payloadMessage);
        };
        this.p_onServiceConnectionLost = function (errorCode, errorText) {
            // log + personal process before app
            this.connectionStatus = CONNECTION_STATUS.DISCONNECTED;

            // invoke the app's callback
            that._onServiceConnectionLost(errorCode, errorText);
        };

        // callbacks from app  -- done
        this._getOnConnectToServiceSuccess = function () {
            return this._onConnectToServiceSuccess;
        };
        this._setOnConnectToServiceSuccess = function (newConnectSuccess) {
            if (typeof newConnectSuccess === "function")
                this._onConnectToServiceSuccess = newConnectSuccess;
            else
                throw new Error(format(ERROR.INVALID_TYPE, [typeof ConnectSuccess, "onConnectToServiceSuccess"]));
        };

        this._getOnConnectToServiceFailed = function () {
            return this._onConnectToServiceFailed;
        };
        this._setOnConnectToServiceFailed = function (newConnectFailed) {
            if (typeof newConnectFailed === "function")
                this._onConnectToServiceFailed = newConnectFailed;
            else
                throw new Error(format(ERROR.INVALID_TYPE, [typeof newConnectFailed, "onConnectToServiceFailed"]));
        };

        this._getOnStartListenSuccess = function () {
            return this._onStartListenSuccess;
        };
        this._setOnStartListenSuccess = function (newStartSucess) {
            if (typeof newStartSucess === "function")
                this._onStartListenSuccess = newStartSucess;
            else
                throw new Error(format(ERROR.INVALID_TYPE, [typeof newStartSucess, "onStartListenSuccess"]));
        };

        this._getOnStartListenFailed = function () {
            return this._onStartListenFailed;
        };
        this._setOnStartListenFailed = function (newStartFailed) {
            if (typeof newStartFailed === "function")
                this._onStartListenFailed = newStartFailed;
            else
                throw new Error(format(ERROR.INVALID_TYPE, [typeof newStartFailed, "onStartListenFailed"]));
        };

        this._getOnStopListenSuccess = function () {
            return this._onStopListenSuccess;
        };
        this._setOnStopListenSuccess = function (newStopSuccess) {
            if (typeof newStopSuccess === "function")
                this._onStopListenSuccess = newStopSuccess;
            else
                throw new Error(format(ERROR.INVALID_TYPE, [typeof newStopSuccess, "onStopListenSuccess"]));
        };

        this._getOnStopListenFailed = function () {
            return this._onStopListenFailed;
        };
        this._setOnStopListenFailed = function (newStopFailed) {
            if (typeof newStopFailed === "function")
                this._onStopListenFailed = newStopFailed;
            else
                throw new Error(format(ERROR.INVALID_TYPE, [typeof newStopFailed, "onStopListenFailed"]));
        };

        this._getOnServiceConnectionLost = function () {
            return this._onServiceConnectionLost;
        };
        this._setOnServiceConnectionLost = function (newConnectionLost) {
            if (typeof newConnectionLost === "function") {
                this._onServiceConnectionLost = newConnectionLost;
                instance.onConnectionLost = this.p_onServiceConnectionLost;
            } else {
                throw new Error(format(ERROR.INVALID_TYPE, [typeof newConnectionLost, "onServiceConnectionLost"]));
            }
        };

        this._getOnSendMessageComplete = function () {
            return this._onSendMessageComplete;
        };
        this._setOnSendMessageComplete = function (newSendComplete) {
            if (typeof newSendComplete === "function") {
                this._onSendMessageComplete = newSendComplete;
                instance.onMessageDelivered = this.p_onSendMessageComplete;
            } else {
                throw new Error(format(ERROR.INVALID_TYPE, [typeof newSendComplete, "onSendMessageComplete"]));
            }
        };

        this._getOnMessageArrived = function () {
            return this._onMessageArrived;
        };
        this._setOnMessageArrived = function (newMessageArrived) {
            if (typeof newMessageArrived === "function") {
                this._onMessageArrived = newMessageArrived;
                instance.onMessageArrived = this.p_onMessageArrived;
            } else {
                throw new Error(format(ERROR.INVALID_TYPE, [typeof newMessageArrived, "onMessageArrived"]));
            }
        };
    };

    /**
     * basic callbacks for each app
     */
    Instance.prototype._onConnectToServiceSuccess;
    Instance.prototype._onConnectToServiceFailed;
    Instance.prototype._onStartListenSuccess;
    Instance.prototype._onStartListenFailed;
    Instance.prototype._onStopListenSuccess;
    Instance.prototype._onStopListenFailed;
    Instance.prototype._onServiceConnectionLost;
    Instance.prototype._onSendMessageComplete;
    Instance.prototype._onMessageArrived;

    Instance.prototype = {
        get onConnectToServiceSuccess() {
            return this._getOnConnectToServiceSuccess();
        },
        set onConnectToServiceSuccess(newConnectSuccess) {
            this._setOnConnectToServiceSuccess(newConnectSuccess);
        },

        get onConnectToServiceFailed() {
            return this._getOnConnectToServiceFailed();
        },
        set onConnectToServiceFailed(newConnectFailed) {
            this._setOnConnectToServiceFailed(newConnectFailed);
        },

        get onStartListenSuccess() {
            return this._getOnStartListenSuccess();
        },
        set onStartListenSuccess(newStartSucess) {
            this._setOnStartListenSuccess(newStartSucess);
        },

        get onStartListenFailed() {
            return this._getOnStartListenFailed();
        },
        set onStartListenFailed(newStartFailed) {
            this._setOnStartListenFailed(newStartFailed);
        },

        get onStopListenSuccess() {
            return this._getOnStopListenSuccess();
        },
        set onStopListenSuccess(newStopSuccess) {
            this._setOnStopListenSuccess(newStopSuccess);
        },

        get onStopListenFailed() {
            return this._getOnStopListenFailed();
        },
        set onStopListenFailed(newStopFailed) {
            this._setOnStopListenFailed(newStopFailed);
        },

        get onServiceConnectionLost() {
            return this._getOnServiceConnectionLost();
        },
        set onServiceConnectionLost(newConnectionLost) {
            this._setOnServiceConnectionLost(newConnectionLost);
        },

        get onSendMessageComplete() {
            return this._getOnSendMessageComplete();
        },
        set onSendMessageComplete(newSendComplete) {
            this._setOnSendMessageComplete(newSendComplete);
        },

        get onMessageArrived() {
            return this._getOnMessageArrived();
        },
        set onMessageArrived(newMessageArrived) {
            this._setOnMessageArrived(newMessageArrived);
        }
    };

    // public module
    return {
        Instance: Instance
    };
})();

//Remoteapp_Client_Interface
var remoteApp;

/**
 * remoteappClientInit
 * @param appname :  "ui_service"
 * @param clientCallback
 */
function remoteappClientInit(appname, clientCallback) {
    //validate parameters
//    if ("ui_service" != appname) {
//        throw new Error(appname + " is not an available appname")
//    }
    if (typeof (clientCallback) != "function") {
        throw new Error("clientCallback is not a function!");
    }
    //get the instance
    remoteApp = new Remoteapp.Instance(appname);
    //set callbacks
    remoteApp._onConnectToServiceSuccess = app_onConnectToServiceSuccess;
    remoteApp._onConnectToServiceFailed = app_onConnectToServiceFailed;
    remoteApp._onStartListenSuccess = app_onStartListenSuccess;
    remoteApp._onStartListenFailed = app_onStartListenFailed;
    remoteApp._onStopListenSuccess = app_onStopListenSuccess;
    remoteApp._onStopListenFailed = app_onStopListenFailed;
    remoteApp._setOnServiceConnectionLost(app_onServiceConnectionLost);
    remoteApp._setOnSendMessageComplete(app_onSendMessageComplete);
    //onMessageArrived callback
    // remoteApp._onMessageArrived = app_onMessageArrived;
    remoteApp._setOnMessageArrived(function (message) {
        // DBG_INFO(message);
        // /remoteapp/tv/ui_service/uuid/actions/4
        var destinationName = message.destinationName;
        var topicArray = destinationName.substring(1).split("/");
        if (topicArray.length == 6) {
            var uuid = topicArray[3];
            var actions = topicArray[5];
            var data = message.payloadString;
            clientCallback(uuid, actions, data);
        } else {
            DBG_INFO("cannot resolve topic：" + topicArray);
        }
    });

    //connect to service by username and password below
    var options = {
        userName: "hisenseservice",
        password: "multimqttservice"
    };
    remoteApp.connectToService(appname, options);
}

/**
 * Uninit the Client , disconnect from server and release resources
 * @param appname
 */
function remoteAppClientUninit(appname) {
    if (remoteApp != null) {
        remoteApp.disconnectFromService();
        remoteApp = null;
    }
}

//validate function
var validate = function (obj, keys) {
    for (var key in obj) {
        if (obj.hasOwnProperty(key)) {
            if (keys.hasOwnProperty(key)) {
                if (typeof obj[key] !== keys[key])
                    throw new Error(format(ERROR.INVALID_TYPE, [typeof obj[key], key]));
            } else {
                var errorStr = ":Unknown property, " + key + ". Valid properties are:";
                for (var key in keys)
                    if (keys.hasOwnProperty(key))
                        errorStr = errorStr + " " + key;
                throw new Error(errorStr);
            }
        }
    }
};

function pubStateLiveTV(type, id, eventid, progname, starttime, endtime, detail) {

    var data = {
        type: type,
        id: id,
        eventid: eventid,
        progname: progname,
        starttime: +starttime,
        endtime: +endtime,
        detail: detail
    };
    validate(data, Message_LiveTV);
    //publish message
    remoteApp.sendMessage_State(TOPIC_TVSTATE_LIVETV, data);

}

function pubStateApp(name, url) {
    var data = {
        name: name,
        url: url
    };
    validate(data, Message_APP);
    remoteApp.sendMessage_State(TOPIC_TVSTATE_APP, data);
}

function pubStateMediaDLNA(name, totaltime, curtime, starttime, playstate, mediatype) {
    var data = {
        name: name,
        totaltime: +totaltime,
        curtime: +curtime,
        starttime: +starttime,
        playstate: playstate,
        mediatype: mediatype
    };
    validate(data, Message_DDT);
    remoteApp.sendMessage_State(TOPIC_TVSTATE_MEDIADLNA, data);
}

function pubStateMediaDmp(name, totaltime, curtime, starttime, playstate, mediatype) {
    var data = {
        name: name,
        totaltime: +totaltime,
        curtime: +curtime,
        starttime: +starttime,
        playstate: playstate,
        mediatype: mediatype
    };
    validate(data, Message_DDT);
    remoteApp.sendMessage_State(TOPIC_TVSTATE_MEDIADMP, data);
}

function pubStateTShift(name, totaltime, curtime, starttime, playstate, mediatype) {
    var data = {
        name: name,
        totaltime: +totaltime,
        curtime: +curtime,
        starttime: +starttime,
        playstate: playstate,
        mediatype: mediatype
    };
    validate(data, Message_DDT);
    remoteApp.sendMessage_State(TOPIC_TVSTATE_TSHIFT, data);
}

function pubActionVkbinvoke(openvkb) {
    if (typeof (openvkb) != "string") {
        throw new Error("type Error!openvkb is not a string");
    }
    remoteApp.sendMessage_Action(TOPIC_TV_ACTION_VKBINVOKE, openvkb);
}

function pubActionBrowserlaunch() {

    remoteApp.sendMessage_Action(TOPIC_TV_ACTION_BROWSERLAUNCH, "/");
}

function pubActionAppversion(uuid, msg) {
    // /remoteapp/mobile/uuid/ui_service/data/appversion
    //var msg = (new Date()).valueOf();
    remoteApp.sendMessage_Action(REPLY_TOPIC_ROOT + uuid + "/ui_service/data/appversion", msg);
}

function pubActionApplist(uuid, appInfo) {
    // /remoteapp/mobile/uuid/ui_service/data/appversion
    if (typeof (appInfo) != "object") {
        throw new Error("type Error！appInfo is not a object");
    }
    for (var i = 0; i < appInfo.length; i++) {
        DBG_INFO("validate list info");
        validate(appInfo[i], Message_InfoItem);
    }
    remoteApp.sendMessage_Action(REPLY_TOPIC_ROOT + uuid + "/ui_service/data/applist", appInfo);
}

function pubDataChannelChange(uuid, channelId, channelName) {
    var data = {
        channelId: channelId,
        channelName: channelName
    }
    // /remoteapp/mobile/uuid/ui_service/data/changechannel
    remoteApp.sendMessage_Action(REPLY_TOPIC_ROOT + uuid + "/ui_service/data/changechannel", data);
}

// universal interface
function pubMsgToTopic(topic, msg) {
    remoteApp.sendMessage_Action(topic, msg);
}

/*
 CallBacks
 */

function app_onConnectToServiceSuccess(invocationContext) {
    DBG_INFO("%cconnectToService success", "color:red");
    DBG_INFO("------startListen------");

    //subscribe topics
    var options = {
        // qos:2
    };
    remoteApp.startListen("ui_service", options);
}

function app_onConnectToServiceFailed(invocationContext) {
    DBG_INFO("connectToService failed");
}

function app_onStartListenSuccess(invocationContext) {
    DBG_INFO("startListen success");
}

function app_onStartListenFailed(invocationContext) {
    DBG_INFO("startListen failed");
}

function app_onStopListenSuccess(invocationContext) {
    DBG_INFO("stopListen success");
}

function app_onStopListenFailed(invocationContext) {
    DBG_INFO("stopListen failed");
}

function app_onServiceConnectionLost(invocationContext) {
    DBG_INFO("%cconnectLost", "color:red");
}

function app_onSendMessageComplete(invocationContext) {
    DBG_INFO("MessageSent");
}
function app_onMessageArrived(invocationContext) {
    DBG_INFO("app_onMessageArrived callback called.");
}
















