package cn.tee3.avd;

import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.util.Log;

import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;

/**
 * 房间接口类
 *
 * 房间接口定义了房间对象创建和释放、房间功能操作。
 */
public class Room {
    private static final String TAG = "Room";
    /** 房间网络状态 */
    public static enum ConnectionStatus
    {
        ready,                          /**< 初始状态  */
        connecting,                     /**< 正在连接服务器  */
        connected,                      /**< 已经连接上服务器  */
        connectFailed,                  /**< 连接服务器失败  */
        dataConnecting,                 /** 媒体数据通道正在连接中 */
        dataConnected                   /** 媒体数据通道已连接成功 */
    }

    public static enum OutgoingInviteType{
//        OutgoingInviteTypeSIP(1),
//        OutgoingInviteTypeH323(2),
//        OutgoingInviteTypeRTSP(3),
    }

    public static enum OutgoingStatus{
//        OutStatus_RpcFailed = 912,          //rpc remote server faild
//        OutStatus_DeviceInroom = 914,       //device is already in room
//        OutStatus_WrongDeviceAddr = 915,    //wrong device addr
//        OutStatus_CallReject = 916,         //call be rejected
//        OutStatus_CallFailed = 917,         //call device faild
//        OutStatus_DeviceBusy = 918,         //device is busy
//        OutStatus_CallTimeout = 919,        //call device timeout
//        OutStatus_RemoteNumNotExist = 920,  //remote number non existing
//        OutStatus_RemoteAddrUnavlid = 921,  //remote addr unavailable
//        OutStatus_RemoteTerminate = 922,    //remote terminated

    }


    public interface JoinResultListener{
        /**
         * 加入房间回调接口
         * @param[in] result 加入错误代码。
         * @sa join
         */
        void onJoinResult(int result);
    }
    /** 房间选项
     *
     * @note 房间选项枚举列出了用于设置房间的选项，在以下函数中使用。
     * @sa setOption
     * @sa getOption
     */
    public static enum Option{
        ro_video_drop_data(0),					/**< 值类型为bool, 字符类型为"true" or "false"：表示接收到视频数据后不解码直接丢弃，一般用于模拟客服端做并发测试。
         * 默认值为false，设置为true后将丢弃该房间所有接收到的视频数据。
         * 必须在join调用之前设置才有效。
         */
        ro_audio_drop_data(1),					/**< 值类型为bool, 字符类型为"true" or "false"：表示接收到音频数据后不解码直接丢弃，一般用于模拟客服端做并发测试。
         * 默认值为false，设置为true后将丢弃该房间所有接收到的音频数据。
         * 必须在join调用之前设置才有效。
         */
        ro_media_use_dtls(2),					/**< 值类型为bool, 字符类型为"true" or "false"：表示音视频是否启用DTLS加密，DTLS加密数据更加安全，但是会加重Server
         * 的负担，在UDP丢包严重的网络环境下，媒体链接需要的时间会更多（30%丢包，链接时间10s-40s）。
         * 默认值为true，使用的是DTLS加密方式。
         * 必须在join调用之前设置才有效。
         */
        ro_room_reconnect_times(3),			/**< 值类型为int32, 字符类型为"3" or "2"等数字字符：设置每次断线后重连次数。系统默认为重连3次，如需改变需要应用层自己设置。
         * 在join之前或者之后设置都有效。
         */
        ro_room_connect_timeout(4),			/**< 值类型为uint32,  字符类型为"3000" or "5000" ：设置链接超时时间，单位毫秒，必须是正数，系统默认是5000毫秒。
         * 在join之前或者之后设置都有效。
         */
        ro_audio_auto_subscribe(5),            /**< 值类型为bool, 字符类型为"true" or "false"：表示是否自动订阅音频,true 自动订阅，false 不自动订阅
										* 默认为true
										 */
        ro_audio_option_codec(6),				/**< 值类型为string, 音频编码算法：字符类型为"isac" or "opus" or "pcmu"：分别表示isac语音编码 、 opus音乐编码、pcmu音乐编码；缺省为"isac"。
         * 必须在join调用之前设置才有效。
         */
        ro_audio_mixerdata_callback_buffered(7),/**< 值类型为bool, 字符类型为"true" or "false"：表示房间中混音数据回调是否缓冲为1024sample后回调：缓冲(true)和不缓冲（false），
         *	缺省为缓冲。
         * 在join之前或者之后设置都有效。
         */
        ro_video_default_camera(8),			/**< 值类型为枚举CameraType, 字符类型为"front" or "back"：表示前后置摄像头模式时房间中默认使用的摄像头：前置(front)和后置（back），
         *	缺省为前置。
         * 必须在join调用之前设置才有效。
         */
        ro_room_auto_rejoin(9),				/**< 值类型为int32, 字符类型为"true" or "false"：在重连失败之后是否尝试自动重新加入房间，系统默认为true。
         * 在join之前或者之后设置都有效。
         */
        ro_room_rejoin_times(10),				/**< 值类型为int32, 单位为毫秒，字符类型为"60000"（60秒后超时不再重连） or "-1"（一直重连）等数字字符：设置断线重连失败后自动重连超时时间，在启用ro_room_auto_rejoin为true后有效。
         * 系统默认为服务器返回时间，此时间可在后台配置，如果都失败将通过onConnectionStatus接口返回cs_connectFailed状态。在join之前或者之后设置都有效。
         */
        ro_room_options_apply(11),				/**< 值类型为String, 房间中各种选项（根据字符枚举）立即应用。当前字符枚举有："audio_options"：Engine中设置的音频选项应用如aec，agc等。
         */

        //only for android
        ro_video_mixerdata_callback_format(12), /**< 值类型为FakeVideoCapturer.FourccType中的枚举值, 字符类型为枚举值字符串，目前支持一下："I420" "YV12" "NV21" "NV12"：表示房间中合屏数据回调的格式。缺省为 "I420"
         */

        ro_custom_mcu_address(100);         /** 类型String ,客户设置自定义的mcu地址，默认不需要设置,格式“url:port”or"ip:port"
         */

        private int value;
        Option(int value) {
            this.value = value;
        }
        public int getValue() {
            return value;
        }
    };

    /**
     * 房间回调接口类
     *
     * 房间回调接口定义了房间对象操作的异步返回，房间通知，房间指示。
     */
    public static interface Listener {
        /// 异步返回
        /** 加入房间操作异步返回
         *
         * @param[in] result 加入错误代码。
         * @sa join
         */
        public void onJoinResult(int result);
        /// 指示
        /** 指示用户离开房间
         *
         * @param[in] reason 离开房间理由代码，正常离开为AVD_OK。
         * @param[in] fromId 指示发起用户Id。
         *
         */
        public void onLeaveIndication(int reason, String fromId);
        /// 通知
        /** 透明通道，接收到广播数据通知
         *
         * @param[in] data 接收到的数据指针。
         * @param[in] len 接收到的数据长度。
         * @param[in] fromId 发送方用户Id。
         *
         * @sa sendPublicData
         */
        public void onPublicData(byte[] data, int len, String fromId);
        /** 透明通道，接收到私有数据通知
         *
         * @param[in] data 接收到的数据指针。
         * @param[in] len 接收到的数据长度。
         * @param[in] fromId 发送方用户Id。
         *
         * @sa sendPrivateData
         */
        public void onPrivateData(byte[] data, int len, String fromId);
        //
        /** 房间应用层数据更改通知
         *
         * @param[in] key 应用层数据关键字。
         * @param[in] value 应用层数据关键字相关数据内容。
         *
         * @sa updateAppData
         */
        public void onAppDataNotify(String key, String value);
        /** 房间状态通知
         *
         * @param[in] status 房间状态。
         */
        public void onRoomStatusNotify(RoomInfo.RoomStatus status);
        /** 房间网络状态通知
         *
         * @param[in] status 当前网络状态。
         */
        public void onConnectionStatus(ConnectionStatus status);

        /** sip邀请状态通知
         *
         * @param[in] status 当前网络状态。
         */
        public void onOutgoingInviteStatusNotify(int type, String roomId, String addr, int status, String amsg);
    }

    private long nativeobj;
    private long nativeListener;
    private Listener listener4cb = null;
    private Handler listenerHandler = null;
    private SDKListener listener4native = null;
    private MVideo.LocalCameraSwitch loccameraSwitch = null;
    private Room(String roomId) {
        initHandler();
        this.nativeobj = nativeobtain(roomId);
        if (0 == nativeobj) {
            throw new RuntimeException("Failed to Create Room!");
        }
        this.loccameraSwitch = new MVideo.LocalCameraSwitch(this);
        Tlog.i(TAG, "Room, roomId:"+roomId + "this="+this+",nativeobj="+this.nativeobj);
    }

    protected MVideo.LocalCameraSwitch getLocalCameraSwith() {
        if (null == this.loccameraSwitch) {
            Tlog.e(TAG, "Room, getLocalCameraSwith null.");
        }
        return this.loccameraSwitch;
    }
    ///
    private static List<Room> rooms = null;
    /** 获取或创建房间接口
     *
     * @param[in] roomId 房间Id。
     * @note 只支持单用户应用，不支持多用户应用。
     * @return 返回房间接口指针。
     */
    public static Room obtain(String aroomId) {
        if (!AVDEngine.instance().isWorking()) {
            Tlog.e(TAG, "obtain, AVDEngine not in working.");
            return null;
        }
        if (null == aroomId) {
            Tlog.e(TAG, "obtain, roomId is null.");
            return null;
        }
        String roomId = aroomId.trim();
        Tlog.d(TAG, "obtain, roomId:"+roomId+",rooms="+((null == rooms) ? "0" : rooms.size()));
        if (null == rooms) {
            rooms = new LinkedList<Room>();
        }
        synchronized (rooms) {
            Iterator<Room> it = rooms.iterator();
            while (it.hasNext()) {
                Room room = it.next();
                if (room.getRoomId().equals(roomId)) {
                    Log.i(TAG, "obtain1111: room"+room);
                    return room;
                }
            }
        }
        Room room = null;
        try {
            room = new Room(roomId);
        }
        catch (Exception e) {
           Tlog.e(TAG, "obtain, new Room exception:"+e.toString());
           return null;
        }
        synchronized (rooms) {
            rooms.add(room);
        }
        Tlog.v(TAG, "obtain, out:");
        return room;
    }
    protected static void clearRooms() {
        if (null == rooms) {
            return ;
        }
        Tlog.w(TAG, "clearRooms, rooms:" + rooms.size());
        synchronized (rooms) {
            Iterator<Room> it = rooms.iterator();
            while (it.hasNext()) {
                Room room = it.next();
                Room.destoryRoom(room);
            }
            rooms.clear();
        }
    }

    /** 释放房间接口
     *@note 释放房间后，App层不能再使用当前房间指针，所有引用房间指针处均需设置为NULL。
     */
    public static void destoryRoom(Room room) {
        Tlog.i(TAG, "destoryRoom, room:"+room);
        if (null == room) {
            return ;
        }
        // module clear;
        for(Module mod : room.modules) {
            synchronized (mod) {
                mod.dispose();
            }
        }
        room.modules.clear();
        room.leave(0);
        //
        synchronized (rooms) {
            rooms.remove(room);
            for (Room item : rooms){
                Log.i(TAG, "rooms: pose"+ item);
            }
            Log.i(TAG, "rooms: roomSize"+rooms.size());
        }
        room.dispose();
        Tlog.i(TAG, "destoryRoom, out:");
    }

    /**
     * 是否存在房间
     *
     * @return
     */
    protected static boolean hasRooms() {
        if (null == rooms) {
            return false;
        }
        return !rooms.isEmpty();
    }

    /**
     * 设置房间回调接口
     *
     * @return 返回错误代码。
     * @param[in] listener 回调接口指针。
     * @see Listener
     */
    public int setListener(Listener listener) {
        Tlog.d(TAG, "setListener, nativeRoom:" + nativeobj + ",listener:" + listener);
        this.listener4cb = listener;
        if (null != listener) {
            initNativeListener();
        }
        Tlog.v(TAG, "setListener, out:");
        return 0;
    }

    private void initNativeListener() {
        if (0 != this.nativeListener) {
            return;
        }
        this.nativeListener = nativeCreateListener(this.listener4native);
        nativesetListener(this.nativeListener);
        Tlog.d(TAG, "initNativeListener, Create nativelistener:" + nativeListener);
    }

    private JoinResultListener joinResult = null;
    /** 当前用户加入房间
     *
     * @param[in] user 用户信息：用户Id，用户名，用户数据等；当应用层不传入user.deviceId时，SDK将创建一个新的guid作为user.deviceId，同时返回给应用层。
     * @param[in] password 密码：如果是主持人请用主持人密码；如果是其他参会者，请用房间密码。
     * @param[in] joinresult 加房间回调接口。
     *
     * @return 返回错误代码。
     * @sa User
     */
    public int join(User user, String password, JoinResultListener joinresult) {
        if (null == user) {
            Tlog.w(TAG, "join, user is null.");
            return ErrorCode.Err_Invalid_Arg;
        }
        Tlog.i(TAG, "join, user:" + user.toString() + ",joinresult:" + joinresult + "this=" + this + ",nativeobj=" + this.nativeobj + ",nativeListener=" + this.nativeListener);
        joinJava(joinresult);
        int ret = nativejoin(user, password);
        Tlog.i(TAG, "join, out:");
        return ret;
    }
    public int join(User user, String password, JoinResultListener joinresult, String roomToken) {
        if (null == user) {
            Tlog.w(TAG, "join, user is null.");
            return ErrorCode.Err_Invalid_Arg;
        }
        Tlog.i(TAG, "joinWithToken, user:" + user.toString()+",joinresult:"+joinresult+"this="+this+",nativeobj="+this.nativeobj+",nativeListener="+this.nativeListener);
        joinJava(joinresult);
        int ret = nativejoinWithToken(user, password, roomToken);
        Tlog.i(TAG, "join, out:");
        return ret;
    }
    private void joinJava(JoinResultListener joinresult) {
        this.joinResult = joinresult;
        if (0 == this.nativeListener) {
            initNativeListener();
        }
        beginNetProber();
    }
    /** 当前用户离开房间
     *
     * @param[in] reason 用户离开房间的原因代码，正常离开为AVD_OK。
     *
     * @note 当前用户离开房间后，当前房间对象和房间的各功能模块都处于离会状态，功能操作无效。
     * @return 返回错误代码。
     */
    public int leave(int reason)   {
        Tlog.i(TAG, "leave,this="+this+",nativeobj="+this.nativeobj+",nativeListener="+this.nativeListener);
        closeleaveJava();
        int ret = nativeleave(reason);
        Tlog.i(TAG, "leave, out:");
        return ret;
    }
    private void closeleaveJava() {
        if (null != _netProber) {
            try {
                NetworkProber.instance().unregisterReceiver(_netProber);
                _netProber = null;
            } catch (Exception e) {
                Tlog.e(TAG, "_netProber.unregisterReceiver,Exception e=" + e.toString());
            }
        }
        this.listener4cb = null;
    }
    /** 关闭房间
     * @note 关闭房间，房间中所有人将被踢出房间，接收到 onLeaveIndication, reason是 Err_Room_closed
     * @sa onLeaveIndication
     * @return 返回错误代码。
     */
    public int close() {
        Tlog.i(TAG, "close,this="+this+",nativeobj="+this.nativeobj+",nativeListener="+this.nativeListener);
        if (null != _netProber) {
            try {
                NetworkProber.instance().unregisterReceiver(_netProber);
                _netProber = null;
            } catch (Exception e) {
                Tlog.e(TAG, "_netProber.unregisterReceiver,Exception e=" + e.toString());
            }
        }
        int ret = nativeclose();
        this.listener4cb = null;
        Tlog.i(TAG, "close, out:");
        return ret;
    }
    /** 重新加入当前房间
     *
     * @note 当用户主动离开房间或者中间网络断线后离开房间后，可以重新调用此函数加入房间。
     * @return 返回错误代码。
     */
    public int reJoin() {
        Tlog.i(TAG, "reJoin,");
        return nativereJoin();
    }
    /** 房间中踢出用户ro
     *
     * @param[in] reason 踢出用户的理由代码。
     * @param[in] userId 被踢出用户Id。
     *
     * @note 被踢用户将会在onLeaveIndication中接收到回调，被踢用户直接调用 leave 离开房间即可。
     * @return 返回错误代码。
     * @sa onLeaveIndication
     */
    public int kickoutUser(int reason, String userId) {
        Tlog.i(TAG, "kickoutUser, reason:" + reason + ",userId:" + userId);
        int ret = nativekickoutUser(reason, userId);
        Tlog.v(TAG, "kickoutUser, out:");
        return ret;
    }
    public boolean isWorking() {
        return nativeisWorking();
    }
    /** 更新房间状态
     *
     * @param[in] status 房间状态。
     *
     * @return 返回错误代码。
     * @sa RoomStatus
     */
    public int updateRoomStatus(RoomInfo.RoomStatus status) {
        Tlog.d(TAG, "updateRoomStatus, status:"+status);
        return nativeupdateRoomStatus(status.ordinal());
    }
    /** 获取房间状态
     *
     * @return 返回房间状态。
     * @sa RoomStatus
     */
    public RoomInfo.RoomStatus getRoomStatus() {
        return nativegetRoomStatus();
    }
    /** 获取房间Id
     *
     * @return 返回房间Id。
     */
    public String getRoomId() {
        return nativegetRoomId();
    }
    /** 获取房间应用层Id
     *
     * @note 房间应用层Id在应用层安排当前房间的时候由应用层来指定，方便应用层查找、标示房间。
     * @return 返回房间应用层Id。
     */
    public String getAppRoomId() {
        return nativegetAppRoomId();
    }
    /** 获取房间名
     *
     * @return 返回房间名。
     */
    public String getRoomName() {
        return nativeGetRoomName();
    }
    /** 获取房间主题
     *
     * @return 返回房间主题。
     */
    public String getRoomTopic() {
        return nativegetRoomTopic();
    }
    /** 获取房间信息
     *
     * @return 返回房间信息。
     * @sa RoomInfo
     */
    public RoomInfo getRoomInfo() {
        return nativegetRoomInfo();
    }
    /** 透明通道，发送广播数据
     *
     * @param[in] data 发送的数据指针。
     * @param[in] len 发送的数据长度。
     *
     * @note 此透明通道用于承载应用层数据，广播给所有房间内用户。
     * @return 返回错误代码。
     */
    public int sendPublicData(byte[] data, int len)  {
        Tlog.d(TAG, "sendPublicData len: " + len);
        return nativesendPublicData(data, len);
    }

    /** 透明通道，发送私有数据
     *
     * @param[in] data 发送的数据指针。
     * @param[in] len 发送的数据长度。
     * @param[in] toId 目的用户Id。
     *
     * @note 此透明通道用于承载应用层数据，发送给房间内某一特定用户。
     * @return 返回错误代码。
     */
    public int sendPrivateData(byte[] data, int len, String toId) {
        Tlog.d(TAG, "sendPrivateData len=" + len + ", toId=" + toId);
        return nativesendPrivateData(data, len, toId);
    }
    /** 返回应用层的房间数据
     *
     * @param[in] key 房间数据关键字。
     *
     * @note 应用层的房间数据方便应用层在房间中保留关于房间的一些业务逻辑数据，可以获取和修改应用层的房间数据。
     * @return 返回应用层的相应关键字的房间数据。
     */
    public String getAppData(String key) {
        return nativegetAppData(key) + "";
    }
    /** 修改应用层的房间数据
     *
     * @param[in] key 房间数据关键字。
     * @param[in] value 房间数据对应关键字的信息内容
     *
     * @return 返回错误代码。
     */
    public int updateAppData(String key, String value) {
        Tlog.d(TAG, "updateAppData, key:"+key+",value:"+value);
        return nativeupdateAppData(key, value);
    }
    //
    /** 当前房间启用或关闭流量统计功能
     * @param[in] isEnable 启用或关闭。
     * @return 返回错误代码：
     * 	AVD_OK						成功
     */
    public int enableStats(boolean isEnable) {
        Tlog.i(TAG, "enableStats, isEnable:"+isEnable);
        return nativeenableStats(isEnable);
    }
    /** 获取房间的流量统计信息
     * @return 返回房间流量统计信息。
     * @sa RoomStats
     */
    public NetworkStats.RoomStats getRoomStats() {
        return nativegetRoomStats();
    }
    /** 获取某路媒体（音频或者视频）的流量统计信息
     * @param[in] deviceId 设备Id。
     * @return 返回媒体统计信息。
     * @sa MediaStats
     */
    public NetworkStats.MediaStats getMediaStats(String deviceId) {
        return nativegetMediaStats(deviceId);
    }
    public NetworkStats.MediaStats getAudioStats() {
        return nativegetAudioStats();
    }

    //
    /** 设置房间选项
     *
     * @param[in] type 房间选项类型。
     * @param[in] svalue 房间选项值。
     *
     * @return 返回错误代码。
     */
    private FakeVideoCapturer.FourccType video_mixerdata_callback_format = FakeVideoCapturer.FourccType.ft_I420;
    public int setOption(Option type, String svalue) {
        Tlog.i(TAG, "setOption, type:" + type + ",svalue:" + svalue);
        if (Option.ro_video_mixerdata_callback_format == type) {
            boolean isvalid = true;
            if      (svalue == "I420") {
                this.video_mixerdata_callback_format = FakeVideoCapturer.FourccType.ft_I420;
            }
            else if (svalue == "NV21") {
                this.video_mixerdata_callback_format = FakeVideoCapturer.FourccType.ft_NV21;
            }
            else if (svalue == "YV12") {
                this.video_mixerdata_callback_format = FakeVideoCapturer.FourccType.ft_YV12;
            }
            else if (svalue == "NV12") {
                this.video_mixerdata_callback_format = FakeVideoCapturer.FourccType.ft_NV12;
            }
            else {
                isvalid = false;
            }
            if (isvalid) {
//                MVideo mvideo = (MVideo)findModule(Module.Type.video);
//                if (null != mvideo) {
//                    mvideo.updateMixerDataColorFormat(video_mixerdata_callback_format.ordinal());
//                }
                return ErrorCode.AVD_OK;
            }
            else {
                return ErrorCode.Err_Not_Implemented;
            }
        }
        return nativesetOption(type.getValue(), svalue);
    }
    /** 获取房间选项
     *
     * @param[in] type 房间选项类型。
     *
     * @return 返回房间选项值。
     */
    public String getOption(Option type) {
        if (Option.ro_video_mixerdata_callback_format == type) {
            switch (this.video_mixerdata_callback_format) {
                case ft_I420:
                    return "I420";
                case ft_NV21:
                    return "NV21";
                case ft_NV12:
                    return "NV12";
                case ft_YV12:
                    return "YV12";
                default:
                    return "";
            }
        }
        return nativegetOption(type.ordinal());
    }
    protected int getIntOption(Option type) {
        if (Option.ro_video_mixerdata_callback_format == type) {
            return this.video_mixerdata_callback_format.ordinal();
        }
        return -1;
    }

    /** 创建模拟设备Id，在导入音视频时可用
     *
     * @param[in] fakeId 自定义Id。
     * @return 模拟设备Id。
     * @note 模拟摄像头信息，接口会生成deviceId，生成规则："userId_"+（用户输入的fakeId), 此处deviceId不能包含'_'和'{'、'}'此三个字符。
     * @sa previewLocalCamera
     * @sa publishLocalCamera
     */
    public String createFakeDeviceID(String fakeId) {
        Tlog.i(TAG, "createFakeDeviceID, fakeId:" +fakeId);
        return nativecreateFakeDeviceID(fakeId);
    }

    ///
    private static native long nativeobtain(String roomId);
    private static native void nativerelease(long nativeRoom);
    private static native long nativeCreateListener(Listener listener);
    private static native void nativeFreeListener(long nativeListener);
    //
    private native int nativesetListener(long nativelistener);
    private native int nativejoin(User user, String password);
    private native int nativejoinWithToken(User user, String password, String oemToken);
    private native int nativeleave(int reason);
    private native int nativeclose();
    private native int nativereJoin();
    private native int nativereConnect();
    private native int nativekickoutUser(int reason, String userId);
    private native boolean nativeisWorking();
    private native int nativeupdateRoomStatus(int status);
    private native RoomInfo.RoomStatus nativegetRoomStatus();
    private native String nativegetRoomId();
    private native String nativegetAppRoomId();
    private native String nativeGetRoomName();
    private native String nativegetRoomTopic();
    private native RoomInfo nativegetRoomInfo();
    private native int nativesendPublicData(byte[] data, int len);
    private native int nativesendPrivateData(byte[] data, int len, String toId);
    private native String nativegetAppData(String key);
    private native int nativeupdateAppData(String key, String value);
    private native String nativecreateFakeDeviceID(String fakeId);
    //
    private native int nativeenableStats(boolean isEnable);
    private native NetworkStats.RoomStats nativegetRoomStats();
    private native NetworkStats.MediaStats nativegetMediaStats(String deviceId);
    private native NetworkStats.MediaStats nativegetAudioStats();

    //
    private native int nativesetOption(int type, String svalue);
    private native String nativegetOption(int type);
    //
    protected native long nativegetMChat();
    protected native long nativegetMAudio();
    protected native long nativegetMUserManager();
    protected native long nativegetMVideo();
    protected native long nativegetMScreen();
    protected native long nativegetMLocalRecord();
    protected native long nativegetMSvrLive();
	protected native long nativegetMWhiteboard();
    ///
    private List<Module> modules = new LinkedList<Module>();
    protected Module getModule(Module.Type type) {
        Module mod = findModule(type);
        return (null == mod) ? Module.createModule(this,type) : mod;
    }
    protected void addModule(Module mod) {
        modules.add(mod);
    }
    protected Module findModule(Module.Type type) {
        for(Module mod : modules) {
            if (type == mod.getType()) {
                return mod;
            }
        }
        return null;
    }
    ///
    protected void dispose() {
        synchronized (this){
            if (0 == nativeobj) {
                return;
            }
            Tlog.i(TAG, "Room dispose ");
            listener4cb = null;
            if (null != this._netProber) {
                try {
                    NetworkProber.instance().unregisterReceiver(this._netProber);
                    _netProber = null;
                } catch (Exception e) {
                    Tlog.e(TAG, "NetworkProber.instance().unregisterReceiver,Exception e=" + e.toString());
                }
            }
            nativesetListener(0);
            Tlog.v(TAG, "Room dispose release native...");
            if (0 != this.nativeListener) {
                nativeFreeListener(this.nativeListener);
                this.nativeListener = 0;
            }
            if (0 != this.nativeobj) {
                nativerelease(this.nativeobj);
                this.nativeobj = 0;
            }
            listenerHandler = null;
            listener4native = null;
            loccameraSwitch = null;
            Tlog.i(TAG, "Room dispose, out:");
        }
    }

    ///
    private NetworkProberLister _netProber = null;
    class NetworkProberLister implements NetworkProber.Listener {
        public void onNetworkChanged(NetworkProber.Status status) {
            Tlog.i(TAG, "onNetworkChanged status="+status);
            if (status != NetworkProber.Status.Network_Diconnected) {
                nativereConnect();
            } // only for connection switch,
        }
    }
    private void beginNetProber() {
        if (null == this._netProber) {
            this._netProber = new NetworkProberLister();
            NetworkProber.instance().registerReceiver(this._netProber);
        }
    }
    ///
    private final int msg_onJoinResult = 1;
    private final int msg_onLeaveIndication = 2;
    private final int msg_onPublicData = 3;
    private final int msg_onPrivateData = 4;
    private final int msg_onAppDataNotify = 5;
    private final int msg_onRoomStatusNotify = 6;
    private final int msg_onConnectionStatus = 7;
    private final int msg_onOutgoingInviteStatusNotify = 8;
    private boolean initHandler() {
        Tlog.i(TAG, "initHandler listener4native:"+listener4native + ",listenerHandler="+listenerHandler);
        if (null == listener4native) {
            listener4native = new SDKListener();
        }
        if (null != listenerHandler) {
            return true;
        }

        AVDEngine.runOnLoopThreadSync(new Runnable() {
            @Override
            public void run() {
                initHandler_main();
            }
        });
        return true;
    }
    private boolean initHandler_main() {
        this.listenerHandler = new Handler(Looper.getMainLooper()) {
            public void handleMessage(Message msg) {
                Tlog.v(TAG, "handleMessage, msg:"+msg.toString());
                if (null == listener4cb && msg_onJoinResult != msg.what) {
                    super.handleMessage(msg);
                    return;
                }
                switch (msg.what) {
                    case msg_onJoinResult:
                        if      (null != joinResult) {
                            joinResult.onJoinResult(msg.arg1);
                        }
                        else if (null != listener4cb) {
                            listener4cb.onJoinResult(msg.arg1);
                        }
                        if(msg.arg1!= ErrorCode.AVD_OK){
                            closeleaveJava();
                        }
                        break;
                    case msg_onLeaveIndication: {
                        Listener templistener4cb = listener4cb;
                        leave(0);
                        if (null != templistener4cb) {
                            templistener4cb.onLeaveIndication(msg.arg1, (String) msg.obj);
                        }
                        }
                        break;
                    case msg_onPublicData:
                        listener4cb.onPublicData(
                                msg.getData().getByteArray("data"), msg.getData().getInt("len"), msg.getData().getString("fromId"));
                        break;
                    case msg_onPrivateData:
                        listener4cb.onPrivateData(
                                msg.getData().getByteArray("data"), msg.getData().getInt("len"), msg.getData().getString("fromId"));
                        break;
                    case msg_onAppDataNotify:
                        listener4cb.onAppDataNotify(
                                msg.getData().getString("key"), msg.getData().getString("value"));
                        break;
                    case msg_onRoomStatusNotify:
                        listener4cb.onRoomStatusNotify(RoomInfo.RoomStatus.values()[msg.arg1]);
                        break;
                    case msg_onConnectionStatus:
                        listener4cb.onConnectionStatus(ConnectionStatus.values()[msg.arg1]);
                        break;
                    case msg_onOutgoingInviteStatusNotify:
//                        OutgoingInviteType type = OutgoingInviteType.values()[msg.getData().getInt("type")];
//                        OutgoingStatus status = OutgoingStatus.values()[msg.getData().getInt("status")];
                        listener4cb.onOutgoingInviteStatusNotify(msg.getData().getInt("type"),msg.getData().getString("roomId"),msg.getData().getString("addr"),msg.getData().getInt("status"),msg.getData().getString("amsg"));
                        break;
                }
                super.handleMessage(msg);
            }
        };
        return true;
    }
    private class SDKListener implements Listener {
        public SDKListener() {
        }
        @Override
        public void onJoinResult(int result) {
            if (null == listenerHandler) {
                return ;
            }
            Message msg = Message.obtain(listenerHandler, msg_onJoinResult, result, 0);
            listenerHandler.sendMessage(msg);
        }
        @Override
        public void onLeaveIndication(int reason, String fromId) {
            if (null == listenerHandler) {
                return ;
            }
            Message msg = Message.obtain(listenerHandler, msg_onLeaveIndication, reason, 0, fromId);
            listenerHandler.sendMessage(msg);
        }
        @Override
        public void onPublicData(byte[] data, int len, String fromId) {
            if (null == listenerHandler) {
                return ;
            }
            Message msg = obtainMessage(msg_onPublicData, data, len, fromId);
            listenerHandler.sendMessage(msg);
        }
        @Override
        public void onPrivateData(byte[] data, int len, String fromId) {
            if (null == listenerHandler) {
                return ;
            }
            Message msg = obtainMessage(msg_onPrivateData, data, len, fromId);
            listenerHandler.sendMessage(msg);
        }
        @Override
        public void onAppDataNotify(String key, String value) {
            if (null == listenerHandler) {
                return ;
            }
            Message msg = obtainMessage(msg_onAppDataNotify, key, value);
            listenerHandler.sendMessage(msg);
        }
        @Override
        public void onRoomStatusNotify(RoomInfo.RoomStatus status) {
            if (null == listenerHandler) {
                return ;
            }
            Message msg = Message.obtain(listenerHandler, msg_onRoomStatusNotify, status.ordinal(),0);
            listenerHandler.sendMessage(msg);
        }
        @Override
        public void onConnectionStatus(ConnectionStatus status) {
            if (null == listenerHandler) {
                return ;
            }
            Message msg = Message.obtain(listenerHandler, msg_onConnectionStatus, status.ordinal(),0);
            listenerHandler.sendMessage(msg);
        }
        @Override
        public void onOutgoingInviteStatusNotify(int type, String roomId, String addr, int status, String amsg) {
            if (null == listenerHandler) {
                return ;
            }
            Message msg = obtainMessage(msg_onOutgoingInviteStatusNotify,type,roomId,addr,status,amsg);
            listenerHandler.sendMessage(msg);
        }
    }
    private Message obtainMessage(int what, String key, String value) {
        Message msg = Message.obtain(listenerHandler);
        msg.what = what;
        Bundle data = new Bundle();
        data.putString("key", key);
        data.putString("value", value);
        msg.setData(data);
        return msg;
    }
    private Message obtainMessage(int what, byte[] appdata, int len, String fromId) {
        Message msg = Message.obtain(listenerHandler);
        msg.what = what;
        Bundle data = new Bundle();
        data.putInt("len", len);
        data.putString("fromId", fromId);
        data.putByteArray("data", appdata);
        msg.setData(data);
        return msg;
    }

    private Message obtainMessage(int what, int type, String roomId, String addr, int status, String amsg) {
        Message msg = Message.obtain(listenerHandler);
        msg.what = what;
        Bundle data = new Bundle();
        data.putInt("type", type);
        data.putInt("status", status);
        data.putString("roomId", roomId);
        data.putString("addr", addr);
        data.putString("amsg", amsg);
        msg.setData(data);
        return msg;
    }
}
