/*
 * MIT License
 *
 * Copyright (c) 2016-2019 xiongziliang <771730766@qq.com>
 *
 * This file is part of ZLMediaKit(https://github.com/xiongziliang/ZLMediaKit).
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */
#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
