#include "FFNvDecoderManager.h"
#include <iostream>

// #include "cuda_kernels.h"
// #include "NvJpegEncoder.h"

#include <pthread.h>
#include <thread>

#include <chrono>

#include <unistd.h>


#ifdef _WIN32
#include "Winsock2.h"
#pragma comment(lib, "ws2_32.lib")
#endif

#ifdef __linux__
#include "arpa/inet.h"
#endif

#include "utiltools.hpp"

#define MIN_RTP_PORT		10000
#define MAX_RTP_PORT		60000

// ȡ MIN_RTP_PORT(10000)~MAX_RTP_PORT(60000)֮�������˿�(ż���������������˿ڿ���)
int allocRtpPort() {

	static int s_rtpPort = MIN_RTP_PORT;
	if (MIN_RTP_PORT == s_rtpPort)
	{
		srand((unsigned int)time(NULL));
		s_rtpPort = MIN_RTP_PORT + (rand() % MIN_RTP_PORT);
	}

	if (s_rtpPort % 2)
		++s_rtpPort;

	while (true)
	{
		s_rtpPort += 2;
		s_rtpPort = s_rtpPort >= MAX_RTP_PORT ? MIN_RTP_PORT : s_rtpPort;

		int i = 0;
		for (; i < 2; i++)
		{
			sockaddr_in sRecvAddr;
			int s = socket(AF_INET, SOCK_DGRAM, 0);

			sRecvAddr.sin_family = AF_INET;        
			sRecvAddr.sin_addr.s_addr = htonl(INADDR_ANY);    
			sRecvAddr.sin_port = htons(s_rtpPort + i); 

			int nResult = bind(s, (sockaddr *)&sRecvAddr, sizeof(sRecvAddr));
			if (nResult != 0)
			{
				break;
			}

			nResult = close(s);
			if (nResult != 0)
			{
				printf("closesocket failed:%d\n", nResult);
				break;
			}
		}

		if (i == 2)
			break;
	}

	return s_rtpPort;
}





unsigned char *pHwRgb[2] = {nullptr, nullptr};

int sum1 = 0;
int sum2 = 0;

// cudaStream_t stream[2];

string data_home = "/mnt/data/cmhu/tmp/";


// #define checkCudaErrors(S) do {CUresult  status; \
//         status = S; \
//         if (status != CUDA_SUCCESS ) std::cout << __LINE__ <<" checkCudaErrors - status = " << status << std::endl; \
//         } while (false)


// static void gpu_helper(int gpuid)
// {
//     cudaSetDevice(gpuid);

//     // int *dn;
//     // cudaMalloc((void **)&dn, 1 * sizeof(int));

// 	size_t free_byte;
// 	size_t total_byte;

// 	CUresult cuda_status = cuMemGetInfo(&free_byte, &total_byte);

// 	const char *pStr = nullptr;
// 	if (CUDA_SUCCESS != cuda_status) {
// 		cuGetErrorString(cuda_status, &pStr);
// 		printf("Error: cudaMemGetInfo fails, %s \n", pStr);
// 		return;
// 	}

// 	double free_db = (double)free_byte;
// 	double total_db = (double)total_byte;
// 	double used_db_1 = (total_db - free_db) / 1024.0 / 1024.0;

// 	std::cout <<"显存已使用 " << used_db_1 << " MB\n";

//     // cudaFree(dn);
// }

// int CheckCUDAProperty( int devId )
// {
//     cuInit(0);

// 	CUdevice dev = devId;
// 	size_t memSize = 0;
// 	char devName[256] = {0};
// 	int major = 0, minor = 0;
// 	CUresult rlt = CUDA_SUCCESS;

//     rlt = cuDeviceGetAttribute(&major, CU_DEVICE_ATTRIBUTE_COMPUTE_CAPABILITY_MAJOR, dev);
//     checkCudaErrors( rlt );

//     rlt = cuDeviceGetAttribute(&minor, CU_DEVICE_ATTRIBUTE_COMPUTE_CAPABILITY_MINOR, dev);
// 	checkCudaErrors( rlt );

// 	rlt = cuDeviceGetName( devName, sizeof( devName ), dev );
// 	checkCudaErrors( rlt );

// 	printf( "Using GPU Device %d: %s has SM %d.%d compute capability\n",
// 		    dev, devName, major, minor );

// 	rlt = cuDeviceTotalMem( &memSize, dev );
// 	checkCudaErrors( rlt );

// 	printf( "Total amount of global memory:   %4.4f MB\n",
// 		   (float)memSize / ( 1024 * 1024 ) );

// 	return 0;
// }

/**
 * 注意: gpuFrame 在解码器设置的显卡上,后续操作要十分注意这一点,尤其是多线程情况
 * */
static long lastpts = 0;
void postDecoded(const void * userPtr, DeviceRgbMemory * gpuFrame){
    AbstractDecoder* decoder = (AbstractDecoder*)userPtr;
    if (decoder!= nullptr)
    {
        long curpts = UtilTools::get_cur_time_ms();
        cout <<  decoder->getName() << "  " << gpuFrame->getWidth() << "x" << gpuFrame->getHeight() << "  " << curpts - lastpts << endl;
        lastpts = curpts;
        delete gpuFrame;
        gpuFrame = nullptr;

            // const char* gpu_pixfmt = av_get_pix_fmt_name((AVPixelFormat)gpuFrame->format);
            // cout << "pixfmt: " << gpu_pixfmt << endl;
            // cout << "keyframe: " << gpuFrame->key_frame << " width: " << gpuFrame->width << " height: "<< gpuFrame->height << endl;
            // cout << "decode successed ✿✿ヽ(°▽°)ノ✿ " << endl;

            // int sum = sum1;
            // if (decoder->getName() == "dec0")
            // {
            //     sum1 ++ ;
            //     sum = sum1;

            //     if (gpuFrame->format == AV_PIX_FMT_CUDA)
            //     {   
            //         // cout << "gpuid = " << atoi(decoder->m_cfg.gpuid.c_str()) << endl;
            //         cudaSetDevice(atoi(decoder->m_cfg.gpuid.c_str()));
            //         cudaError_t cudaStatus;
            //         if(pHwRgb[0] == nullptr){
            //             // cudaStreamCreate(&stream[0]);
            //             cuda_common::setColorSpace( ITU_709, 0 );
            //             cudaStatus = cudaMalloc((void **)&pHwRgb[0], 3 * gpuFrame->width * gpuFrame->height * sizeof(unsigned char));
            //         }
            //         cudaStatus = cuda_common::CUDAToBGR((CUdeviceptr)gpuFrame->data[0],(CUdeviceptr)gpuFrame->data[1], gpuFrame->linesize[0], gpuFrame->linesize[1], pHwRgb[0], gpuFrame->width, gpuFrame->height);
            //         cudaDeviceSynchronize();
            //         if (cudaStatus != cudaSuccess) {
            //             cout << "CUDAToBGR failed !!!" << endl;
            //             return;
            //         }

            //         string path = data_home + decoder->getName() + ".jpg";
            //         saveJpeg(path.c_str(), pHwRgb[0], gpuFrame->width, gpuFrame->height, stream[0]);  // 验证 CUDAToRGB 
            //     }
            // } else if (decoder->getName() == "dec2") 
            // {
            //     sum2 ++ ;
            //     sum = sum2;

            //     if (gpuFrame->format == AV_PIX_FMT_CUDA)
            //     {   
            //         // cout << "gpuid = " << atoi(decoder->m_cfg.gpuid.c_str()) << endl;
            //         cudaSetDevice(atoi(decoder->m_cfg.gpuid.c_str()));
            //         cudaError_t cudaStatus;
            //         if(pHwRgb[1] == nullptr){
            //             // cudaStreamCreate(&stream[1]);
            //             cuda_common::setColorSpace( ITU_709, 0 );
            //             cudaStatus = cudaMalloc((void **)&pHwRgb[1], 3 * gpuFrame->width * gpuFrame->height * sizeof(unsigned char));
            //         }
            //         cudaStatus = cuda_common::CUDAToBGR((CUdeviceptr)gpuFrame->data[0],(CUdeviceptr)gpuFrame->data[1], gpuFrame->linesize[0], gpuFrame->linesize[1], pHwRgb[1], gpuFrame->width, gpuFrame->height);
            //         cudaDeviceSynchronize();
            //         if (cudaStatus != cudaSuccess) {
            //             cout << "CUDAToBGR failed !!!" << endl;
            //             return;
            //         }

            //         string path = data_home + decoder->getName() + ".jpg";
            //         saveJpeg(path.c_str(), pHwRgb[1], gpuFrame->width, gpuFrame->height, stream[1]);  // 验证 CUDAToRGB 
            //     }
            // }
    }
}

long start_time = 0;
long end_time = 0;
bool count_flag = false;
int count = 0;
int count_std = 100;


static int sum = 0;
unsigned char *pHwData = nullptr;

void postDecoded0(const void * userPtr, DeviceRgbMemory* gpuFrame){
    // std::this_thread::sleep_for(std::chrono::milliseconds(30000));

    AbstractDecoder* decoder = (AbstractDecoder*)userPtr;
    if (decoder!= nullptr)
    {
        // cout << "decode name: " << decoder->getName() << endl;
        // if (decoder->getName() == "dec")
        // {
        //     if (! count_flag)
        //     {
        //         count_flag = true;
        //         count = 0;
        //         end_time = start_time = UtilTools::get_cur_time_ms();
        //     }
        //     count++;
        //     sum ++ ;
        //     if (count >= count_std)
        //     {
        //         // end_time = UtilTools::get_cur_time_ms();
        //         // long time_using = end_time - start_time;
        //         // double time_per_frame = double(time_using)/count_std ;
        //         // cout << count_std << "帧用时:" << time_using << "ms 每帧用时:" << time_per_frame << "ms" << endl;
        //         cout << decoder->getName() << " keyframe: " << gpuFrame->key_frame << " width: " << gpuFrame->width << " height: "<< gpuFrame->height << endl;
        //         // cout << gpuFrame->pts << endl;

        //         count_flag = false;
        //     }
        //     // cout << "帧数:" << sum << endl;

        //     if (gpuFrame->format == AV_PIX_FMT_CUDA)
        //     {   
        //         cudaSetDevice(atoi(decoder->m_cfg.gpuid.c_str()));
        //         // cout << "gpu id : " << decoder->m_cfg.gpuid.c_str() << endl;
        //         cudaError_t cudaStatus;
        //         if(pHwData == nullptr){
        //             cuda_common::setColorSpace( ITU_709, 0 );
        //             cudaStatus = cudaMalloc((void **)&pHwData, 3 * gpuFrame->width * gpuFrame->height * sizeof(unsigned char));
        //         }
        //         cudaStatus = cuda_common::CUDAToBGR((CUdeviceptr)gpuFrame->data[0],(CUdeviceptr)gpuFrame->data[1], gpuFrame->linesize[0], gpuFrame->linesize[1], pHwData, gpuFrame->width, gpuFrame->height);
        //         cudaDeviceSynchronize();
        //         if (cudaStatus != cudaSuccess) {
        //             cout << "CUDAToBGR failed !!!" << endl;
        //             return;
        //         }

        //         string path = data_home + decoder->getName() + ".jpg";
        //         saveJpeg(path.c_str(), pHwData, gpuFrame->width, gpuFrame->height, nullptr);  // 验证 CUDAToRGB 
        //     }
        // }
    }
}

void decode_finished_cbk(const void* userPtr){
    cout << "当前时间戳: " << UtilTools::get_cur_time_ms() << endl;
}

bool decode_request_stream_cbk(const char* deviceId){
    cout << "需在此请求流" << endl;
    return true;
}

// string test_uri = "rtmp://192.168.10.56:1935/objecteye/1";
// string test_uri = "/home/cmhu/data/output_800x480.mp4";
// string test_uri = "/home/cmhu/data/output_1920x1080.mp4";
// string test_uri = "rtsp://176.10.0.2:8554/stream";
// string test_uri = "/mnt/f/fiss/test_data/h265.mp4";
string test_uri = "rtsp://176.10.0.4:8554/stream";

void createDecode(int index, const char* gpu_id){
    FFNvDecoderManager* pDecManager = FFNvDecoderManager::getInstance();
    MgrDecConfig config;
    config.name = "dec" + to_string(index);
    config.cfg.uri = test_uri;
    config.cfg.post_decoded_cbk = postDecoded;
    config.cfg.decode_finished_cbk = decode_finished_cbk;
    config.cfg.force_tcp = true;
    config.dec_type = DECODER_TYPE_FFMPEG;

    config.cfg.gpuid = gpu_id;
    // if (index % 2 == 0)
    // {
    //     config.cfg.gpuid = "0";
    // }
    // else
    // {
    //     config.cfg.gpuid = "0";
    // }
    
    AbstractDecoder* decoder = pDecManager->createDecoder(config);
    if (!decoder)
    {
        return ;
    }
    pDecManager->setPostDecArg(config.name, decoder);
    pDecManager->setFinishedDecArg(config.name, decoder);
    pDecManager->startDecodeByName(config.name);
}

void createGB28181Decode(int index, char* gpu_id, int port){
    FFNvDecoderManager* pDecManager = FFNvDecoderManager::getInstance();
    MgrDecConfig config;
    config.name = "dec" + to_string(index);
    config.cfg.uri = config.name;
    config.cfg.post_decoded_cbk = postDecoded;
    config.cfg.decode_finished_cbk = decode_finished_cbk;
    config.cfg.request_stream_cbk = decode_request_stream_cbk;
    config.cfg.force_tcp = true;

    config.dec_type = DECODER_TYPE_GB28181;
    config.cfg.port = port;//allocRtpPort();

    config.cfg.gpuid = gpu_id;
    
    AbstractDecoder* decoder = pDecManager->createDecoder(config);
    if (!decoder)
    {
        return ;
    }
    pDecManager->setPostDecArg(config.name, decoder);
    pDecManager->setFinishedDecArg(config.name, decoder);
    pDecManager->startDecodeByName(config.name);
}

void logFF(void *, int level, const char *fmt, va_list ap)
{
    vfprintf(stdout, fmt, ap);
}


int main(int argc, char* argv[]){

    test_uri = "rtsp://admin:admin@123456@192.168.60.176:554/cam/realmonitor?channel=1&subtype=0";//argv[1];
    char* gpuid = argv[2];
    int port = atoi(argv[3]);
    cout << test_uri << "   gpu_id:" << gpuid << "   port:" << port << endl;

    // av_log_set_callback(&logFF);

    // CheckCUDAProperty(atoi(gpuid));

    pthread_t m_decode_thread;
    pthread_create(&m_decode_thread,0,
        [](void* arg)
        {
            // cudaSetDevice(atoi(gpuid));
            while (true)
            {
                std::this_thread::sleep_for(std::chrono::minutes(1));
                FFNvDecoderManager* pDecManager = FFNvDecoderManager::getInstance();
                int count = pDecManager->count();
                cout << "当前时间:" << UtilTools::get_cur_time_ms() << "  当前运行路数: " << pDecManager->count() << endl;
            }  

            return (void*)0;
        }
    ,nullptr);


    FFNvDecoderManager* pDecManager = FFNvDecoderManager::getInstance();
    int i = 0;

    while (true)
    {
        int ch = getchar();
        if (ch == 'q')
        {
            break;
        }

        switch (ch)
        {
        case 'f':
        case 'F':
            createDecode(i, gpuid);
            i++;
            break;
        case 'g':
        case 'G':
            createGB28181Decode(i, gpuid, port);
            i++;
            break;
        case 'r':
        case 'R':
            pDecManager->resumeDecoder("dec0");
            break;
        case 'p':
        case 'P':
            pDecManager->pauseDecoder("dec0");
            break;

        case 'c':
        case 'C':
            i--;
            pDecManager->closeDecoderByName("dec" + to_string(i));
            break;

        case 'i':
        case 'I':
        {
            int w,h;
            pDecManager->getResolution("dec0", w,h);
            printf( "%s : %dx%d\n", "dec0" , w,h );
        }
            break;

        default:
            break;
        }

        /* code */
    }

    cout << "总共帧数:" << sum << endl;
    pDecManager->closeAllDecoder();
}