package cn.tee3.avd;
/**
 * Created by bob.liu on 2015/8/24.
 */
import android.content.Context;
import android.os.Handler;
import android.os.Message;

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

/**
 * 音视频流导入接口.
 *
 * 完成独立的音视频导入功能接口。
 */
public class AVImporter {
    private static final String TAG = "AVImporter";

    public static interface EngineInitResultListener{
        /** 初始化引擎操作异步返回
         *
         * @param[in] result 初始化错误代码。
         * @sa init
         */
        void onEngineInitResult(int result);
    }
    /** 初始化引擎
     *
     * @param[in] severuri 服务器uri。
     * @param[in] appkey 叁体公司分发给应用公司的应用秘钥。
     * @param[in] secretkey 叁体公司分发给应用公司的安全秘钥。
     * @param[in] initresult 引擎初始化回调函数指针。
     * @note AVD引擎在做任何功能操作前，必须先要初始化引擎成功。引擎初始化是一个异步操作，调用此接口后等待回调中函数initresult回调，判断是否初始化成功。
     * @return 返回错误代码。
     */
    public static int initEngine(Context context, String severuri, String appkey, String secretkey, EngineInitResultListener cb) {
        Tlog.i(TAG, "initEngine, context:" + context + ",severuri:" + severuri + ",appkey:" + appkey + ",cb:" + cb);
        if (isEngineWorking()) {
            Tlog.w(TAG, "initEngine, isEngineWorking is ok. not do real init.");
            return 0;
        }
        appcontext = context;
        AVDEngine.instance();
        int ret = AVDEngine.initJavaForNoEngine(context, false);
        if (ErrorCode.AVD_OK != ret) {
            Tlog.e(TAG, "initEngine initJavaForNoEngine failed.ret="+ret);
            return ret;
        }

        if (null == engine_listener4native) {
            engine_listener4native = new SDKEngineListener();
        }
        if (null == engine_listener4native) {
            Tlog.e(TAG, "initEngine new SDKEngineListener failed.");
            return ErrorCode.Err_Out_Of_Memory;
        }
        engine_listenerHandler = new Handler() {
            public void handleMessage(Message msg) {
                Tlog.i(TAG, "initEngine handleMessage, msg:"+msg.toString());
                if (null == engine_cb) {
                    super.handleMessage(msg);
                    return;
                }
                switch (msg.what) {
                    case msg_onEngineInitResult:
                        engine_cb.onEngineInitResult(msg.arg1);
                        break;
                }
                super.handleMessage(msg);
            }
        };

        ret = nativeinitEngine(engine_listener4native, severuri, appkey, secretkey);
        if (0 == ret) {
            engine_cb = cb;
        }
        Tlog.i(TAG, "initEngine, end. ret="+ret);
        return ret;
    }
    private static EngineInitResultListener engine_cb = null;
    private static SDKEngineListener engine_listener4native = null;
    private static Handler engine_listenerHandler = null;
    private static Context appcontext = null;
    /** 反初始化引擎
     *
     * @note AVD反初始化后，引擎中涉及的所有房间对象都无效，引擎上的功能函数都无法正常使用。
     * @return 返回错误代码。
     */
    public static int uninitEngine() {
        Tlog.i(TAG, "uninitEngine,");
        AVImporter.clearImporters();
        int ret = nativeuninitEngine();
        appcontext = null;
        engine_cb = null;
        engine_listenerHandler = null;
        engine_listener4native = null;
        Tlog.i(TAG, "uninitEngine, end.");
        return ret;
    }

    public static boolean isEngineWorking() {
        return nativeisEngineWorking();
    }

    /** 根据错误代码获取关联的错误信息描述
     * @param[in] code 错误代码。
     * @return 返回错误信息描述。
     */
    public static String getErrorMessage(int code) {
        return AVDEngine.getErrorMessage(code);
    }
    /** 设置引擎日志配置
     *
     * @param[in] params 日志参数：日志等级[sensitive verbose info warning error none ]，以上只可取一个值；[debug]是否在调试输出；两个值时中间空格分隔。
     * @param[in] filename 日志文件名，若无日志文件名，则不写文件。
     *
     * @return 返回错误代码。
     */
    public static int setLogParams(String params, String filename) {
        return AVDEngine.instance().setLogParams(params,filename);
    }

    public static interface RoomJoinResultListener{
        /**
         * 加入房间回调接口
         * @param[in] result 加入错误代码。
         * @sa join
         */
        void onRoomJoinResult(String roomId, int result);
    }
    /**
     * 音视频流导入回调接口.
     */
    public static interface Listener {
        /** 导入状态通知
         *
         * @param[in] result 当前网络状态。
         */
        public void onStatus(int result);

        /** 错误通知
         *
         * @param[in] reason 错误状态。
         * @note
         *		Err_Importer_Video_Failure
         *		Err_Importer_Audio_Failure
         *		Err_User_DuplicateJoin  相同的userId加入房间，已经退出房间
         *		Err_Room_OutofVideo
         *		Err_Room_OutofAudio
         *		Err_Room_Close          房间关闭，已经退出房间
         *		Err_Network_Failure     网络错误，已经退出房间
         *		Err_Importer_BeKicked   被踢出房间，已经退出房间
         */
        public void onError(int reason);
    };

    private long nativeobj;
    private long nativeListener;
    private Listener listener4cb = null;
    private Handler listenerHandler = null;
    private SDKListener listener4native = null;
    private AVImporter(String roomId) {
        m_roomId = roomId.trim();
        initHandler();
        this.nativeobj = nativeobtain(m_roomId);
        if (0 == nativeobj) {
            throw new RuntimeException("Failed to Create AVImporter!");
        }
        Tlog.i(TAG, "AVImporter, roomId:"+m_roomId + "this="+this+",nativeobj="+this.nativeobj);
    }
    protected void dispose() {
        Tlog.i(TAG, "AVImporter dispose ");
        listener4cb = null;
        if (0 != this.nativeListener) {
            nativeFreeListener(this.nativeListener);
            this.nativeListener = 0;
        }
        if (0 != this.nativeobj) {
            nativerelease(this.nativeobj);
            this.nativeobj = 0;
        }
        listenerHandler = null;
        listener4native = null;
        Tlog.i(TAG, "dispose, out:");
    }
    ///
    private static List<AVImporter> rooms = null;

    /** 获取或创建音视频导入对象
     *
     * @param[in] roomId 房间Id。
     * @note 只支持单用户应用，不支持多用户应用。
     * @return 返回房间接口指针。
     */
    public static AVImporter obtain(String roomId) {
        Tlog.d(TAG, "obtain, roomId:"+roomId);
        if (null == rooms) {
            rooms = new LinkedList<AVImporter>();
        }
        synchronized (rooms) {
            Iterator<AVImporter> it = rooms.iterator();
            while (it.hasNext()) {
                AVImporter room = it.next();
                if (room.getRoomId().equals(roomId)) {
                    return room;
                }
            }
        }
        AVImporter room = null;
        try {
            room = new AVImporter(roomId);
        }
        catch (Exception e) {
            Tlog.e(TAG, "obtain, new AVImporter exception:"+e.toString());
            return null;
        }
        synchronized (rooms) {
            rooms.add(room);
        }
        Tlog.v(TAG, "obtain, out:");
        return room;
    }
    protected static void clearImporters() {
        if (null == rooms) {
            return ;
        }
        Tlog.w(TAG, "clearImporters, rooms:" + rooms.size());
        synchronized (rooms) {
            Iterator<AVImporter> it = rooms.iterator();
            while (it.hasNext()) {
                AVImporter room = it.next();
                room.dispose();
            }
            rooms.clear();
        }
    }

    /** 释放视频流导入对象
     *@note 释放视频流导入对象后，App层不能再使用当前视频流导入对象指针，所有引用房间指针处均需设置为NULL。
     */
    public static void destoryImporter(AVImporter room) {
        Tlog.i(TAG, "destoryImporter, room:"+room);
        if (null == room) {
            return ;
        }
        //
        synchronized (rooms) {
            rooms.remove(room);
        }
        room.dispose();
        Tlog.i(TAG, "destoryImporter, out:");
    }

    private String m_roomId;
    ///
    /** 获取房间Id
     *
     * @return 返回房间Id。
     */
    public String getRoomId() {
        return m_roomId;
    }
    /** 设置回调接口
     *
     * @param[in] listener 回调接口指针。
     *
     * @return 返回错误代码。
     * @see Listener
     */
    public int setListener(Listener listener) {
        Tlog.d(TAG, "setListener, nativeRoom:" + nativeobj + ",listener:" + listener);
        this.listener4cb = listener;
        if (0 == this.nativeListener) {
            this.nativeListener = nativeCreateListener(this.listener4native);
            nativesetListener(this.nativeListener);
        }
        Tlog.v(TAG, "setListener, out:");
        return 0;
    }
    /** 加入房间
     *
     * @param[in,out] user 用户信息：用户Id(不能包含'{'、'}'字符)，用户名，用户数据等；当应用层不传入user.userId时，SDK将创建一个新的guid作为user.userId，同时返回给应用层。
     * @param[in] joinresult 加房间回调接口。
     * @note 加入房间时，可以指定结果单独回调接口；如果指定，则在指定单独接口回调，不会在Listener的onJoinResult回调。
     * @return 返回错误代码。
     * @sa User
     */
    private RoomJoinResultListener joinroom_cb = null;
    public int join(User user, RoomJoinResultListener joinresult) {
        Tlog.i(TAG, "join, user:" + user.toString() + ",joinresult:" + joinresult + "this=" + this + ",nativeobj=" + this.nativeobj + ",nativeListener=" + this.nativeListener);
        if (0 == this.nativeListener) {
            setListener(null);
        }
        int ret = nativejoin(user);
        if (0 == ret) {
            joinroom_cb = joinresult;
        }
        Tlog.i(TAG, "join, out:");
        return ret;
    }

    /** 启用导入音频
     *
     * @param[in] enable 启用。
     * @return 返回错误代码。
     */
    public int enableAudio(boolean enable) {
        return nativeenableAudio(enable);
    }
    /** 启用导入视频
     *
     * @param[in] enable 启用。
     * @return 返回错误代码。
     */
    public int enableVideo(boolean enable) {
        return nativeenableVideo(enable);
    }

    /** 判断房间是否正在运行
     * @return 是否在运行。
     */
    public boolean isWorking() {
        return nativeisWorking();
    }

    /// audio
    /** 音频流导入PCM原始数据
     *
     * @param[in] timestamp_ns 时间戳, nanos second。
     * @param[in] sampleRate 音频采样率
     * @param[in] channels 音频采集通道数
     * @param[in] data 音频内容数据指针
     * @param[in] len  音频内容大小
     * @return 返回错误代码：
     * 	AVD_OK						成功
     * @note sampleRate和channels可以变化(但是不能频繁变化，如每次都不一样，内部每次变化的时候需要重新处理缓冲区)，每次输入的音频采样数据不能太多，最大不超过4K。
     */
    public int audio_inputPCMFrame(long timestamp_ns, int sampleRate, int channels,  byte[] data, int len)  {
        return nativeinputPCMFrame(timestamp_ns, sampleRate, channels, data, len);
    }
    /** 音频流导入AAC编码后的数据
     *
     * @param[in] timestamp_ns 时间戳, nanos second。
     * @param[in] sampleRate 音频采样率
     * @param[in] channels 音频采集通道数
     * @param[in] data 音频内容数据指针
     * @param[in] len  音频内容大小
     * @param[in] samplesOfEncodedData  音频编码数据包含的采样数（一般情况AAC每次编码都是1024 * channels个采样）
     * @return 返回错误代码：
     * 	AVD_OK						成功
     */
    public int audio_inputAACFrame(long timestamp_ns, int sampleRate, int channels,  byte[] data, int len, int samplesOfEncodedData) {
        return nativeinputAACFrame(timestamp_ns, sampleRate, channels, data, len, samplesOfEncodedData);
    }

    /// video
    /** 视频流导入原始图像
     *
     * @param[in] timestamp_ns 时间戳, nanos second
     * @param[in] format 原始图像格式，见FourCC枚举
     * @param[in] w 图像宽度
     * @param[in] h 图像高度
     * @param[in] data 图像内容数据指针
     * @param[in] len  图像内容大小
     * @param[in] rotation 图像需要旋转的角度，顺时针方向，取值：0,90,180,270
     * @param[in] mirror 镜面图像，左右翻转
     * @return 返回错误代码：
     * 	AVD_OK						成功
     * 	RTC_ERR_VIDEO_CONVERT_FAILED	转换图像格式失败，有可能是内存分配失败，可考虑重启程序。
     *	Err_Invalid_Arg				参数不合法，可忽略
     *	Err_Wrong_Status			状态不对，可忽略
     *
     */
    public int video_inputRAWFrame(long timestamp_ns, int w, int h, byte[] data, int len, int rotation, boolean mirror, FakeVideoCapturer.FourccType fourFormat) {
        if (FakeVideoCapturer.FourccType.ft_H264 == fourFormat) {
            return ErrorCode.Err_Not_Available;
        }
        return nativeinputRAWFrame(timestamp_ns, fourFormat.ordinal(), w, h, data, len, rotation, mirror);
    }
    /** 视频流导入h264编码后的数据
     *
     * @param[in] timestamp_ns 时间戳, nanos second
     * @param[in] w 图像宽度
     * @param[in] h 图像高度
     * @param[in] sample  图像内容数据指针
     * @param[in] sample_size 图像内容大小
     * @return 返回错误代码：
     * 	AVD_OK						成功
     *	Err_Invalid_Arg				参数不合法，可忽略
     *	Err_Wrong_Status			状态不对，可忽略
     *
     */
    public int video_input264Frame(long timestamp_ns, int w, int h, byte[] data, int len) {
        return nativeinput264Frame(timestamp_ns, w, h, data, len);
    }

    ///
    private static final int msg_onEngineInitResult = 1;
    private final int msg_onRoomJoinResult = 5;
    //
    private final int msg_onError = 10;
    private final int msg_onStatus = 11;
    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() {
            public void handleMessage(Message msg) {
                Tlog.v(TAG, "handleMessage, msg:"+msg.toString());
                if (null == listener4cb) {
                    super.handleMessage(msg);
                    return;
                }
                switch (msg.what) {
                    case msg_onError:
                        listener4cb.onError(msg.arg1);
                        break;
                    case msg_onStatus:
                        listener4cb.onStatus(msg.arg1);
                        break;
                    case msg_onRoomJoinResult:
                        if (null != joinroom_cb) {
                            joinroom_cb.onRoomJoinResult(m_roomId, msg.arg1);
                            joinroom_cb = null;
                        }
                        else {
                            Tlog.i(TAG, "handleMessage, joinroom_cb is null when onRoomJoinResult: roomid="
                                    +m_roomId +",ret="+msg.arg1);
                        }
                        break;
                }
                super.handleMessage(msg);
            }
        };
        return true;
    }
    private class SDKListener implements Listener {
        public SDKListener() {
        }
        public void onJoinResult(int result) {
            if (null == listenerHandler) {
                return ;
            }
            Message msg = Message.obtain(listenerHandler, msg_onRoomJoinResult, result, 0);
            listenerHandler.sendMessage(msg);
        }
        @Override
        public void onError(int result) {
            if (null == listenerHandler) {
                return ;
            }
            Message msg = Message.obtain(listenerHandler, msg_onError, result, 0);
            listenerHandler.sendMessage(msg);
        }
        @Override
        public void onStatus(int reason) {
            if (null == listenerHandler) {
                return ;
            }
            Message msg = Message.obtain(listenerHandler, msg_onStatus, reason, 0);
            listenerHandler.sendMessage(msg);
        }
    }

    private static class SDKEngineListener implements EngineInitResultListener {
        public SDKEngineListener() {
        }
        @Override
        public void onEngineInitResult(int result) {
            if (null == engine_cb) {
                return ;
            }
            Message msg = Message.obtain(engine_listenerHandler, msg_onEngineInitResult, result, 0);
            engine_listenerHandler.sendMessage(msg);
        }
    }
    ///
    private static native int nativeinitEngine(EngineInitResultListener listener, String severurl, String appkey, String token);
    private static native int nativeuninitEngine();
    private static native boolean nativeisEngineWorking();
    ///
    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);
    private native boolean nativeisWorking();
    private native int nativeenableAudio(boolean enable);
    private native int nativeenableVideo(boolean enable);
    private native int nativeinputPCMFrame(long timestamp_ns,int sampleRate, int channels,  byte[] data, int len);
    private native int nativeinputAACFrame(long timestamp_ns, int sampleRate, int channels,  byte[] data, int len, int samples);
    private native int nativeinputRAWFrame(long timestamp_ns,int format, int w, int h, byte[] sample, int sample_size, int rotation, boolean mirror);
    private native int nativeinput264Frame (long timestamp_ns, int w, int h, byte[] data, int len);
}
