/*
 * Copyright (c) 2016 The ZLMediaKit project authors. All Rights Reserved.
 *
 * This file is part of ZLMediaKit(https://github.com/xia-chu/ZLMediaKit).
 *
 * Use of this source code is governed by MIT license that can be found in the
 * LICENSE file in the root of the source tree. All contributing project authors
 * may be found in the AUTHORS file in the root of the source tree.
 */

#ifdef ENABLE_HKDEVICE
#include "DeviceHK.h"
#include "Util/TimeTicker.h"
#include "Util/MD5.h"
namespace mediakit {

#define HK_APP_NAME  "live"

DeviceHK::DeviceHK() {
    InfoL << endl;
    static onceToken token( []() {
        NET_DVR_Init();
        NET_DVR_SetDVRMessageCallBack_V31([](LONG lCommand,NET_DVR_ALARMER *pAlarmer,char *pAlarmInfo,DWORD dwBufLen,void* pUser){
            WarnL<<pAlarmInfo;
            return TRUE;
        },NULL);
    }, []() {
        NET_DVR_Cleanup();
    });
}

DeviceHK::~DeviceHK() {
    InfoL << endl;
}

void DeviceHK::connectDevice(const connectInfo &info, const connectCB& cb, int iTimeOut) {
    NET_DVR_USER_LOGIN_INFO loginInfo;
    NET_DVR_DEVICEINFO_V40 loginResult;

    //login info
    strcpy(loginInfo.sDeviceAddress, info.strDevIp.c_str());
    loginInfo.wPort = info.ui16DevPort;
    strcpy(loginInfo.sUserName, info.strUserName.c_str());
    strcpy(loginInfo.sPassword, info.strPwd.c_str());

    //callback info
    typedef function< void(LONG lUserID, DWORD dwResult, LPNET_DVR_DEVICEINFO_V30 lpDeviceInfo)> hkLoginCB;
    loginInfo.bUseAsynLogin = TRUE;
    weak_ptr<Device> weakSelf = shared_from_this();
    loginInfo.pUser = new hkLoginCB([weakSelf,cb](LONG lUserID, DWORD dwResult, LPNET_DVR_DEVICEINFO_V30 lpDeviceInfo ) {
                        //TraceL<<lUserID<<" "<<dwResult<<" "<<lpDeviceInfo->sSerialNumber;
                        connectResult result;
                        if(dwResult==TRUE) {
                            result.strDevName=(char *)(lpDeviceInfo->sSerialNumber);
                            result.ui16ChnStart=lpDeviceInfo->byStartChan;
                            result.ui16ChnCount=lpDeviceInfo->byChanNum;
                            auto _strongSelf=weakSelf.lock();
                            if(_strongSelf) {
                                auto strongSelf=dynamic_pointer_cast<DeviceHK>(_strongSelf);
                                strongSelf->onConnected(lUserID,lpDeviceInfo);
                            }
                        } else {
                            WarnL<<"connect deviceHK failed:"<<NET_DVR_GetLastError();
                        }
                        cb(dwResult==TRUE,result);
                    });
    loginInfo.cbLoginResult = [](LONG lUserID, DWORD dwResult, LPNET_DVR_DEVICEINFO_V30 lpDeviceInfo , void* pUser) {
                auto *fun=static_cast<hkLoginCB *>(pUser);
                (*fun)(lUserID,dwResult,lpDeviceInfo);
                delete fun;
            };
    NET_DVR_SetConnectTime(iTimeOut * 1000, 3);
    NET_DVR_Login_V40(&loginInfo, &loginResult);
}

void DeviceHK::disconnect(const relustCB& cb) {
    m_mapChannels.clear();
    if (m_i64LoginId >= 0) {
        NET_DVR_Logout(m_i64LoginId);
        m_i64LoginId = -1;
        Device::onDisconnected(true);
    }

}

void DeviceHK::addChannel(int iChn, bool bMainStream) {
    DevChannel::Ptr channel( new DevChannelHK(m_i64LoginId, (char *) m_deviceInfo.sSerialNumber, iChn, bMainStream));
    m_mapChannels[iChn] = channel;
}

void DeviceHK::delChannel(int chn) {
    m_mapChannels.erase(chn);
}

void DeviceHK::onConnected(LONG lUserID, LPNET_DVR_DEVICEINFO_V30 lpDeviceInfo) {
    m_i64LoginId = lUserID;
    m_deviceInfo = *lpDeviceInfo;
    Device::onConnected();
}

void DeviceHK::addAllChannel(bool bMainStream) {
    InfoL << endl;
    for (int i = 0; i < m_deviceInfo.byChanNum; i++) {
        addChannel(m_deviceInfo.byStartChan + i, bMainStream);
    }
}

DevChannelHK::DevChannelHK(int64_t i64LoginId, const char* pcDevName, int iChn, bool bMainStream) :
        DevChannel(HK_APP_NAME,(StrPrinter<<MD5(pcDevName).hexdigest()<<"_"<<iChn<<endl).data()),
        m_i64LoginId(i64LoginId) {
    InfoL << endl;
    NET_DVR_PREVIEWINFO previewInfo;
    previewInfo.lChannel = iChn; //通道号
    previewInfo.dwStreamType = bMainStream ? 0 : 1; // 码流类型,0-主码流,1-子码流,2-码流3,3-码流4 等以此类推
    previewInfo.dwLinkMode = 1; // 0:TCP方式,1:UDP方式,2:多播方式,3 - RTP方式,4-RTP/RTSP,5-RSTP/HTTP
    previewInfo.hPlayWnd = 0; //播放窗口的句柄,为NULL表示不播放图象
    previewInfo.byProtoType = 0; //应用层取流协议,0-私有协议,1-RTSP协议
    previewInfo.dwDisplayBufNum = 1; //播放库播放缓冲区最大缓冲帧数,范围1-50,置0时默认为1
    previewInfo.bBlocked = 0;
    m_i64PreviewHandle = NET_DVR_RealPlay_V40(m_i64LoginId, &previewInfo,
                                        [](LONG lPlayHandle,DWORD dwDataType,BYTE *pBuffer,DWORD dwBufSize,void* pUser) {
                                            DevChannelHK *self=reinterpret_cast<DevChannelHK *>(pUser);
                                            if(self->m_i64PreviewHandle!=(int64_t)lPlayHandle) {
                                                return;
                                            }
                                            self->onPreview(dwDataType,pBuffer,dwBufSize);
                                        }, this);
    if (m_i64PreviewHandle == -1) {
        throw std::runtime_error( StrPrinter 	<< "设备[" << pcDevName << "/" << iChn << "]开始实时预览失败:"
                                                << NET_DVR_GetLastError() << endl);
    }
}

DevChannelHK::~DevChannelHK() {
    InfoL << endl;
    if (m_i64PreviewHandle >= 0) {
        NET_DVR_StopRealPlay(m_i64PreviewHandle);
        m_i64PreviewHandle = -1;
    }
    if (m_iPlayHandle >= 0) {
        PlayM4_StopSoundShare(m_iPlayHandle);
        PlayM4_Stop(m_iPlayHandle);
        m_iPlayHandle = -1;
    }
}

void DevChannelHK::onPreview(DWORD dwDataType, BYTE* pBuffer, DWORD dwBufSize) {
    //TimeTicker1(-1);
    switch (dwDataType) {
    case NET_DVR_SYSHEAD: { //系统头数据
        if (!PlayM4_GetPort(&m_iPlayHandle)) {  //获取播放库未使用的通道号
            WarnL << "PlayM4_GetPort:" << NET_DVR_GetLastError();
            break;
        }
        if (dwBufSize > 0) {
            if (!PlayM4_SetStreamOpenMode(m_iPlayHandle, STREAME_REALTIME)) { //设置实时流播放模式
                WarnL << "PlayM4_SetStreamOpenMode:" << NET_DVR_GetLastError();
                break;
            }
            if (!PlayM4_OpenStream(m_iPlayHandle, pBuffer, dwBufSize,
                    1024 * 1024)) {  //打开流接口
                WarnL << "PlayM4_OpenStream:" << NET_DVR_GetLastError();
                break;
            }

            PlayM4_SetDecCallBackMend(m_iPlayHandle,
                    [](int nPort,char * pBuf,int nSize,FRAME_INFO * pFrameInfo, void* nUser,int nReserved2) {
                        DevChannelHK *chn=reinterpret_cast<DevChannelHK *>(nUser);
                        if(chn->m_iPlayHandle!=nPort) {
                            return;
                        }
                        chn->onGetDecData(pBuf,nSize,pFrameInfo);
                    }, this);
            if (!PlayM4_Play(m_iPlayHandle, 0)) {  //播放开始
                WarnL << "PlayM4_Play:" << NET_DVR_GetLastError();
                break;
            }
            InfoL << "设置解码器成功!" << endl;
            //打开音频解码, 需要码流是复合流
            if (!PlayM4_PlaySoundShare(m_iPlayHandle)) {
                WarnL << "PlayM4_PlaySound:" << NET_DVR_GetLastError();
                break;
            }
        }
    }
        break;
    case NET_DVR_STREAMDATA: { //流数据(包括复合流或音视频分开的视频流数据)
        if (dwBufSize > 0 && m_iPlayHandle != -1) {
            if (!PlayM4_InputData(m_iPlayHandle, pBuffer, dwBufSize)) {
                WarnL << "PlayM4_InputData:" << NET_DVR_GetLastError();
                break;
            }
        }
    }
        break;
    case NET_DVR_AUDIOSTREAMDATA: { //音频数据
    }
        break;
    case NET_DVR_PRIVATE_DATA: { //私有数据,包括智能信息
    }
        break;
    default:
        break;
    }
}

void DevChannelHK::onGetDecData(char* pBuf, int nSize, FRAME_INFO* pFrameInfo) {
    //InfoL << pFrameInfo->nType;
    switch (pFrameInfo->nType) {
    case T_YV12: {
        if (!m_bVideoSeted) {
            m_bVideoSeted = true;
            VideoInfo video;
            video.iWidth = pFrameInfo->nWidth;
            video.iHeight = pFrameInfo->nHeight;
            video.iFrameRate = pFrameInfo->nFrameRate;
            initVideo(video);
        }
        char *yuv[3];
        int yuv_len[3];
        yuv_len[0] = pFrameInfo->nWidth;
        yuv_len[1] = pFrameInfo->nWidth / 2;
        yuv_len[2] = pFrameInfo->nWidth / 2;
        int dwOffset_Y = pFrameInfo->nWidth * pFrameInfo->nHeight;
        yuv[0] = pBuf;
        yuv[2] = yuv[0] + dwOffset_Y;
        yuv[1] = yuv[2] + dwOffset_Y / 4;
        inputYUV(yuv, yuv_len, pFrameInfo->nStamp);
    }
        break;
    case T_AUDIO16: {
        if (!m_bAudioSeted) {
            m_bAudioSeted = true;
            AudioInfo audio;
            audio.iChannel = pFrameInfo->nWidth;
            audio.iSampleBit = pFrameInfo->nHeight;
            audio.iSampleRate = pFrameInfo->nFrameRate;
            initAudio(audio);
        }
        inputPCM(pBuf, nSize, pFrameInfo->nStamp);
    }
        break;
    default:
        break;
    }
}

} /* namespace mediakit */

#endif //ENABLE_HKDEVICE