#include <atlbase.h>
#include <atlconv.h>
#include <sqlite3.h>
#include <openssl/pem.h>
#include <openssl/err.h>
#include <openssl/pkcs12.h>
#include <HPSocket.h>
#include <fstream>
#include <cassert>
#include <string>
#include <queue>
#include <map>
#include <time.h>
#include <sys/timeb.h>
#include <ctime>
#include <stdio.h>
#include <stdlib.h>
#include <iomanip>
#include <algorithm>
#include <cmath>

#include <direct.h>
#include <WinSock2.h>
#include <Iphlpapi.h>
//#include <thread>
//#include <mutex>
#include "CppSQLite3.h"
#include "libhnsw.h"
#include "WmAceKG-x86.h"
#include "CommonTools.h"
#include "rapidjson/document.h"
#include "rapidjson/writer.h"
#include "rapidjson/pointer.h"
#include "rapidjson/stringbuffer.h"
#include "rapidjson/filereadstream.h"
#include "rapidjson/filewritestream.h"
#include "DirectoryDel.h"
#include "SocketServer.h"
#include "SocketClient.h"
#include "ThreadPool.h"
#include "spdlog/spdlog.h"
#include "spdlog/sinks/daily_file_sink.h"
#include "ini.h"
#include "videopipe.h"
#include "libretri.h"
#include "CharacterConversion.h"
#include "MarkSave.h"
#include <vector>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <iostream>

#include <ncnn/benchmark.h>
#include <ncnn/platform.h>
#include <ncnn/net.h>
#include "libstrret.h"
  // #undef NCNN_VULKAN

#pragma comment(lib, "IPHLPAPI.lib")

#define ERROR_SIMILARITY 0.95//纠错的相似度
#define FEATURE_COUNT 128 //特征的个数
#define DATABASE_SIZE 120000  //索引大小
#define APPOINT_LIBRARY 0  //指定目录
#define NCNN_USING 1		//ncnn

#define FRESH_DETECT 0
#define PREVENT_LOSS 1
#define VIDEOCOUNT   49
#define VIDEOCOINTIN 49
//纠错存 code和id
unordered_map<string, int>reassion;
string realProductCodes[10];

//空盘图向量
float feature_empty[FEATURE_COUNT];
string empty_pic = "empty_pic";
//视频流存图
int rest_count;
int rest_in = 0;
int rest_out = 0;
string dir_in;
string dir_out;

//视频流抓图时间
int  video_time;

using namespace std;
using namespace cv;
using namespace rapidjson;


struct VIDEO_FRAME_INFO
{
	UINT64 timestamp; // 时间戳
	Mat img;
};

struct DETECT_RESULT
{
	char productCodes[100];
	char sessionId[100];
	int state;
};

struct FOOD_PARAM_DATA
{
	Mat back;													// 用于创建算法的初始化图像,一般是裁剪具有标志物的空秤盘
	string back_mat_path;										// 加载temp的路径

	bool end_thread;											// 控制视频流线程

	Rect roi;													// 识别区域
	bool has_roi;												// 是否启用识别区域

	int max_in_frame_queue;										// 最大缓存图片数
	queue<VIDEO_FRAME_INFO> frame_in;							// q1
	queue<libvideopipe::FOOD_VIDEOPIPE_INPUT>  videopipe_in;	// q2
	vector<VIDEO_FRAME_INFO>  frame_out;	// q2

	HANDLE hMutex;												//互斥量
	bool debug_mode;											// 是否启用调试 

	libvideopipe::MotionVideoPipe* mvp;							//追踪器

	queue<DETECT_RESULT> result_queue;							//结果队列
	int queue_max;												//结果队列上限

	string detect_image_path;
	bool scan;													//是否扫描
};

//返回结果
DETECT_RESULT result;
// step0: 初始化param
FOOD_PARAM_DATA param_in;

//文件的路径
const char *gszFile = "featurexYz.db";
const char* log_path = "logs/daily.txt";
std::shared_ptr<spdlog::logger> logger;
vector<float>solve_cost;
//索引
libhnsw::Index* idx = nullptr;

// 测试提交
// api列表
// 租户
string tenantStr;
// mac地址
string macAddr;
// 开发版本
string dev;
float filterScore = 0.85f;
float markDownScoreLimit = 0.9f;

int mode;

// 版本文本
const char* dev_ini_path = "dev.ini";
// 租户json
const char* json_tenant_path = "tenant.json";
// posId json
const char* posId_json_path = "posId.json";
// pos机注册api
const char* wmpos_bind_url = "%s.wmdigit.com/%s/newretail/api/pos/machine/bind";
// pos机解绑api
const char* wmpos_unbind_url = "%s.wmdigit.com/%s/newretail/api/pos/machine/unbind?mac=%s&snNo=%s";
// 保存称重记录
const char* save_record_url = "%s.wmdigit.com/%s/newretail/api/search/product/saveImageIdentify";
// 上传文件
const char* upload_file_url = "%s.wmdigit.com/%s/newretail/api/dfs/upload";
// 上传视频流的照片文件
const char* upload_file1_url = "%s.wmdigit.com/%s/newretail/api/dfs/upload2";
// 重置mac地址为有线网卡的mac 地址
const char* reset_mac_url = "%s.wmdigit.com/%s/newretail/api/pos/machine/updateMacByOld";
// 验证租户是否正确
const char* query_tenant_url = "%s.wmdigit.com/0/newretail/api/sys/org/queryTenant?tenantCode=%s";
// pos机试用api
const char* wmpos_probation_url = "%s.wmdigit.com/free-sample/newretail/api/pos/probationSdk/download?macAddress=%s";
//自动配图
const char* pictured_json_url = "%s.wmdigit.com/%s/newretail/api/mall/productMatch/match";

// 是否连接网络
bool connectNet = true;

//用于打开摄像头
cv::VideoCapture cap;
//// 一帧图片
//cv::Mat frame;
// 三帧图片
cv::Mat frame_pipe;
cv::Mat frame_one;
cv::Mat frame_two;
cv::Mat frame_thr;

// 
Mat mask;

// 裁剪的坐标值
int x, y, width, height;
int ai_x, ai_y, ai_width, ai_height;
// 摄像头序号
int cameraNum;
// 存储的json文件路径
const char* json_path = "coordinate.json";

// 是否验证过
bool authed = false;

string wmphoto_path = "WMPhoto";

// 获取dll所在目录
EXTERN_C IMAGE_DOS_HEADER __ImageBase;//申明为全局变量


// posId
string posId;

string mac_date = "2021-11-24 14:00:00";

libbm25::Index *match_map = nullptr;

//视频流算法参数
float MarkThreshold;
float AiMarkThreshold;


//是否使用gpu和ai
bool using_ai = true;
bool drive_ai = 1;

//元芒加速库句柄
void* handle = nullptr;

//debug模式
bool debug = false;

//抓图线程安全
HANDLE PhotoMutex;

//视频流是否正在处理图片(因为出现过处理一帧需要2s的问题,改了算法逻辑,添加了该参数)
bool in_image = false;

//是否已经返回前端结果(因为前端node的线程问题,添加了改参数,防止一直访问结果队列。(待优化???))
bool re_ret = false;

//扫描开始,停止视频流
bool plu_input_stop_video = false;

//视频流是否初始化完成
bool g_video_init = false;

//防止多次初始化
bool init_finish = false;

// 是否触发视频流识别
bool video_detect = true;

/*******************************Socket相关************************************/
bool openSync = false;
// TCP服务端口
int port = 10083;
// 最大连接数
const int maxConnectionCount = 20;
// 真实Ip
char realIp[16];
// 连接Ids
vector<CONNID> connIds;

// udp节点
UdpNodeListenerImpl udpNodeListener;
CUdpNodePtr udp_node(&udpNodeListener);

// tcp客户端
ClientListenerImpl clientListener;
CTcpPackClientPtr s_pclient(&clientListener);

// tcp服务端
ServerListenerImpl serverListener;
CTcpPackServerPtr s_pserver(&serverListener);


void getFiles(string path, vector<string>& files)
{
	//文件句柄
	intptr_t hFile = 0;
	//文件信息
	struct _finddata_t fileinfo;
	string p;
	if ((hFile = _findfirst(p.assign(path).append("\\*").c_str(), &fileinfo)) != -1)
	{
		do
		{
			if ((fileinfo.attrib & _A_SUBDIR))
			{
				if (strcmp(fileinfo.name, ".") != 0 && strcmp(fileinfo.name, "..") != 0)
					getFiles(p.assign(path).append("\\").append(fileinfo.name), files);
			}
			else
			{
				files.push_back(p.assign(path).append("\\").append(fileinfo.name));
			}
		} while (_findnext(hFile, &fileinfo) == 0);
		_findclose(hFile);
	}
}

//删除日志
int delete_daily()
{
	string filePath = "logs\\";
	vector<string> files;

	getFiles(filePath, files);

	int size = files.size();
	sort(files.begin(), files.end());
	if (size > 7) {
		for (int i = 0; i < size - 7; i++) {
			remove(files[i].c_str());
		}
	}
	return 0;
}

float average_cost()
{
	if(solve_cost.size() == 0 && (!in_image))
	{
		return 67;
	}
	while (solve_cost.size() == 0 && in_image)
	{
		Sleep(100);
	}
	float ret = solve_cost[0];
	solve_cost.erase(solve_cost.begin());
	if (ret > 3000)
	{
		ret = 3000;
	}
	if (ret < 67) 
	{
		ret = 67;
	}
	return ret;
}


void sendTcpServerCreateBroadCast(const char* data) {
	BOOL result = udp_node->SendCast((const BYTE*)data, strlen(data));
	if (!result) {
		int code = SYS_GetLastError();
		logger->info("当前ip:{},发送广播消息失败,错误码:{}", realIp, code);
	}
	else {
		logger->info("ip:{},UDP广播消息:{},发送成功", realIp, data);
	}
}

void createUdpNode() {
	if (!udp_node->Start(char2TCAHR(realIp), 11184, CM_BROADCAST, L"255.255.255.255")) {
		int code = GetLastError();
		logger->info("当前ip:{},创建udp节点错误,错误码:{}", realIp, code);
	}
	else {
		logger->info("ip:{},UDP节点创建成功", realIp);
	}
}

void createClient(LPCTSTR remoteAddress) {
	if (s_pclient->HasStarted()) {
		s_pclient->Stop();
	}
	// 3. Start component object
	s_pclient->SetMaxPackSize(1024 * 100);
	if (!s_pclient->Start(remoteAddress, port)) {
		int code = GetLastError();
		logger->info("当前ip:{},socket客户端启动失败,错误码:{}", realIp, code);
	}
	else {
		logger->info("ip:{},socket客户端启动成功", realIp);
	}
}

// 1 代表索引添加 2 代表商品模型关系添加 
//{\"type\":1,\"feature\":\"1\",\"productCode\":\"1\",\"modelCodes\":[{\"modelCode\":1,\"productCode\":\"123\"},{\"modelCode\":2,\"productCode\":\"345\"}]}
void sendClientModelMessage(vector<int> modelCodes, vector<string> productCodes) {
	if (modelCodes.size() == 0) {
		return;
	}
	// 等待2秒客户端连接
	Sleep(2000);
	if (s_pclient->HasStarted()) {
		Document d;
		Document::AllocatorType& a = d.GetAllocator();
		Value a1(kArrayType);
		for (unsigned int i = 0; i < modelCodes.size(); i++)
		{
			Value o1(kObjectType);
			o1.AddMember("productCode", Value(productCodes[i], a), a);
			o1.AddMember("modelCode", Value(modelCodes[i]), a);
			a1.PushBack(o1, a);
		}
		Pointer("/type").Set(d, 2);
		Pointer("/modelCodes").Set(d, a1);
		StringBuffer buffer;
		Writer<StringBuffer> writer(buffer);
		d.Accept(writer);

		logger->info(buffer.GetString());
		s_pclient->Send((const BYTE*)buffer.GetString(), buffer.GetLength());
	}
	else if (s_pserver->HasStarted()) {
		Document d;
		Document::AllocatorType& a = d.GetAllocator();
		Value a1(kArrayType);
		for (unsigned int i = 0; i < modelCodes.size(); i++)
		{
			Value o1(kObjectType);
			o1.AddMember("productCode", Value(productCodes[i], a), a); 
			o1.AddMember("modelCode", Value(modelCodes[i]), a); 
			a1.PushBack(o1, a);
		}
		Pointer("/type").Set(d, 2);
		Pointer("/modelCodes").Set(d, a1);
		StringBuffer buffer;
		Writer<StringBuffer> writer(buffer);
		d.Accept(writer);

		logger->info(buffer.GetString());

		for (unsigned long i = 0; i < connIds.size(); i++)
		{
			s_pserver->Send(connIds[i], (const BYTE*)buffer.GetString(), buffer.GetLength());
		}
	}
	else {
		// do nothing
	}
}

int initTcpServer()
{
	if (!openSync) {
		return 0;
	}
	// 设置真实ip
	GetIp(realIp);

	// 创建udp节点
	createUdpNode();

	// 创建tcp服务
	s_pserver->SetMaxConnectionCount(maxConnectionCount);
	s_pserver->SetMaxPackSize(1024 * 100);
	if (!s_pserver->Start(char2TCAHR(realIp), port)) {
		int code = GetLastError();
		logger->info("当前ip:{},socket服务端启动失败,错误码:{}", realIp, code);
	}
	else {
		logger->info("当前ip:{},socket服务端启动成功", realIp);
		sendTcpServerCreateBroadCast("hello i am tcp server");
	}
	return 0;
}

// 获取mac地址
void GetMacByGetAdaptersAddresses(std::string& macOUT)
{
	bool ret = false;

	ULONG ulOutBufLen = sizeof(IP_ADAPTER_INFO);
	PIP_ADAPTER_INFO pAdapterInfo = (IP_ADAPTER_INFO*)malloc(sizeof(IP_ADAPTER_INFO));
	if (pAdapterInfo == NULL)
		return;

	if (GetAdaptersInfo(pAdapterInfo, &ulOutBufLen) == ERROR_BUFFER_OVERFLOW)
	{
		free(pAdapterInfo);
		pAdapterInfo = (IP_ADAPTER_INFO*)malloc(ulOutBufLen);
		if (pAdapterInfo == NULL)
			return;
	}
	if (GetAdaptersInfo(pAdapterInfo, &ulOutBufLen) == NO_ERROR)
	{
		for (PIP_ADAPTER_INFO pAdapter = pAdapterInfo; pAdapter != NULL; pAdapter = pAdapter->Next)
		{
			// 确保是以太网
			if (pAdapter->Type != MIB_IF_TYPE_ETHERNET || pAdapter->Type == 71 || strstr(pAdapter->Description, "Bluetooth") > 0)
				continue;
			
			// 过滤vpn 和 虚拟机
			if (strstr(pAdapter->Description, "VPN") > 0 || strstr(pAdapter->Description, "Virtual") > 0 || strstr(pAdapter->Description, "UU") > 0) {
				continue;
			}

			// 确保MAC地址的长度为 00-00-00-00-00-00
			if (pAdapter->AddressLength != 6)
				continue;

			char acMAC[32];
			sprintf_s(acMAC, 32, "%02X-%02X-%02X-%02X-%02X-%02X",
				int(pAdapter->Address[0]),
				int(pAdapter->Address[1]),
				int(pAdapter->Address[2]),
				int(pAdapter->Address[3]),
				int(pAdapter->Address[4]),
				int(pAdapter->Address[5]));

			cout << "找到了有线网卡: " << acMAC << endl;
			cout << "当前类型为: " << pAdapter->Type << endl;
			cout << "当前描述为: " << pAdapter->Description << endl;
			macOUT = acMAC;
			ret = true;
		}
		// 没有找到有线网卡 开始设置无线网卡
		if (!ret) {
			for (PIP_ADAPTER_INFO pAdapter = pAdapterInfo; pAdapter != NULL; pAdapter = pAdapter->Next)
			{
				// 确保是以太网
				if (pAdapter->Type == MIB_IF_TYPE_ETHERNET || pAdapter->Type != 71 || strstr(pAdapter->Description, "Bluetooth") > 0)
					continue;

				// 过滤vpn 和 虚拟机
				if (strstr(pAdapter->Description, "VPN") > 0 || strstr(pAdapter->Description, "Virtual") > 0 || strstr(pAdapter->Description, "UU") > 0) {
					continue;
				}

				// 确保MAC地址的长度为 00-00-00-00-00-00
				if (pAdapter->AddressLength != 6)
					continue;

				char acMAC[32];
				sprintf_s(acMAC, 32, "%02X-%02X-%02X-%02X-%02X-%02X",
					int(pAdapter->Address[0]),
					int(pAdapter->Address[1]),
					int(pAdapter->Address[2]),
					int(pAdapter->Address[3]),
					int(pAdapter->Address[4]),
					int(pAdapter->Address[5]));

				cout << "找到了无线网卡: " << acMAC << endl;
				cout << "当前类型为: " << pAdapter->Type << endl;
				cout << "当前描述为: " << pAdapter->Description << endl;
				macOUT = acMAC;
				break;
			}
		}
	}
	free(pAdapterInfo);
}

/**
 * 字符分割算法 ,
 * @param str
 * @param data
 * @return
 */
void split(char* str, float* data) {
	const char* regex = ",";
	str = strtok(str, regex);
	int i = 0;
	while (str) {
		data[i] = atof(str);
		i++;
		str = strtok(NULL, regex);
	}
}

inline bool exists_file(const std::string& name) {
	if (FILE* file = fopen(name.c_str(), "r")) {
		fclose(file);
		return true;
	}
	else {
		return false;
	}
}

/**
 * 字符分割算法 int
 * @param str
 * @param data
 * @return
 */
void splitStringColon(char* str, string* data) {
	const char* regex = ":";
	str = strtok(str, regex);
	int i = 0;
	while (str) {
		data[i] = str;
		i++;
		str = strtok(NULL, regex);
	}
}

/**
 * 字符分割算法 int
 * @param str
 * @param data
 * @return
 */
void splitString(char* str, string* data) {
	const char* regex = ",";
	str = strtok(str, regex);
	int i = 0;
	while (str) {
		data[i] = str;
		i++;
		str = strtok(NULL, regex);
	}
}

// 读取文件
string readTxt(string file)
{
	ifstream infile;
	infile.open(file.data());   //将文件流对象与文件连接起来 
	assert(infile.is_open());   //若失败,则输出错误消息,并终止程序运行 

	string s;
	while (getline(infile, s))
	{
		cout << s << endl;
	}
	infile.close();             //关闭文件输入流 
	return s;
}

void del_rod(char* src)
{
	char* fp = src;
	while (*src) {
		if (*src != '-') {
			*fp = *src;
			fp++;
		}
		src++;
	}
	*fp = '\0'; //封闭字符串
}

void del_rod(string& str)
{
	// 因为str.c_str() 是const的,不能直接修改,
	// 所以要复制一个临时的,然后修改后换回去
	char tmp[18 + 1];
	memcpy(&tmp, str.c_str(), str.size() + 1);
	del_rod(tmp);    // 重载del_rod,调用C风格的函数
	str = tmp;
}

std::string tool_get_dll_path()
{
	char dir[_MAX_DIR];
	char drive[_MAX_DRIVE];
	char DllPath[MAX_PATH];
	GetModuleFileNameA((HINSTANCE)&__ImageBase, DllPath, _countof(DllPath));


	//截取DLL所在目录(去掉DLL文件名)
	char fname[_MAX_FNAME];
	char ext[_MAX_EXT];
	_splitpath(DllPath, drive, dir, fname, ext);

	std::string new_dir = std::string(drive) + std::string(dir);
	return new_dir;
}

time_t StringToDatetime(string str)
{
	char* cha = (char*)str.data();             // 将string转换成char*。
	tm tm_;                                    // 定义tm结构体。
	int year, month, day, hour, minute, second;// 定义时间的各个int临时变量。
	sscanf(cha, "%d-%d-%d %d:%d:%d", &year, &month, &day, &hour, &minute, &second);// 将string存储的日期时间,转换为int临时变量。
	tm_.tm_year = year - 1900;                 // 年,由于tm结构体存储的是从1900年开始的时间,所以tm_year为int临时变量减去1900。
	tm_.tm_mon = month - 1;                    // 月,由于tm结构体的月份存储范围为0-11,所以tm_mon为int临时变量减去1。
	tm_.tm_mday = day;                         // 日。
	tm_.tm_hour = hour;                        // 时。
	tm_.tm_min = minute;                       // 分。
	tm_.tm_sec = second;                       // 秒。
	tm_.tm_isdst = 0;                          // 非夏令时。
	time_t t_ = mktime(&tm_);                  // 将tm结构体转换成time_t格式。
	return t_;                                 // 返回值。
}

int GetFileInfo(string& strPath,time_t& createTime)
{
	struct _stat tmpInfo;
	if (_stat(strPath.c_str(), &tmpInfo) != 0)
	{
		return FALSE;
	}
	createTime = tmpInfo.st_ctime;
	return TRUE;
}


// 私钥解密  
std::string rsa_pri_decrypt(const std::string& cipherText, const std::string& priKey)
{
	std::string strRet;
	RSA* rsa = RSA_new();
	BIO* keybio;
	// keybio = BIO_new_mem_buf((unsigned char*)priKey.c_str(), -1);
	keybio = BIO_new_file(priKey.c_str(), "r");

	// 此处有三种方法
	// 1, 读取内存里生成的密钥对,再从内存生成rsa
	// 2, 读取磁盘里生成的密钥对文本文件,在从内存生成rsa
	// 3,直接从读取文件指针生成rsa
	rsa = PEM_read_bio_RSAPrivateKey(keybio, &rsa, NULL, NULL);

	int len = RSA_size(rsa);
	char* decryptedText = (char*)malloc(len + 1);
	memset(decryptedText, 0, len + 1);

	// 解密函数
	int ret = RSA_private_decrypt(cipherText.length(), (const unsigned char*)cipherText.c_str(), (unsigned char*)decryptedText, rsa, RSA_PKCS1_PADDING);
	if (ret >= 0)
		strRet = std::string(decryptedText, ret);

	// 释放内存
	free(decryptedText);
	BIO_free_all(keybio);
	RSA_free(rsa);

	return strRet;
}

static inline bool is_base64(unsigned char c) {
	return (isalnum(c) || (c == '+') || (c == '/'));
}

// base64 解码
std::string base64Decode(std::string const& encoded_string) {
	std::string base64_chars =
		"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
		"abcdefghijklmnopqrstuvwxyz"
		"0123456789+/";
	size_t in_len = encoded_string.size();
	int i = 0;
	int j = 0;
	int in_ = 0;
	unsigned char char_array_4[4], char_array_3[3];
	std::string ret;

	while (in_len-- && (encoded_string[in_] != '=') && is_base64(encoded_string[in_])) {
		char_array_4[i++] = encoded_string[in_]; in_++;
		if (i == 4) {
			for (i = 0; i < 4; i++)
				char_array_4[i] = base64_chars.find(char_array_4[i]) & 0xff;

			char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4);
			char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2);
			char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3];

			for (i = 0; (i < 3); i++)
				ret += char_array_3[i];
			i = 0;
		}
	}

	if (i) {
		for (j = 0; j < i; j++)
			char_array_4[j] = base64_chars.find(char_array_4[j]) & 0xff;

		char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4);
		char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2);

		for (j = 0; (j < i - 1); j++) ret += char_array_3[j];
	}

	return ret;
}

void loadIndexFromDB(CppSQLite3DB& db, libhnsw::Index* newIndex) {
	double start_time = clock();//1计时开始
	if (db.tableExists("feature_code")) {
		CppSQLite3Query q = db.execQuery("select id,feature from feature_code where deleted = 0");
		while (!q.eof())
		{
			float features[FEATURE_COUNT];
			int id = q.getIntField(0);
			const char* sql_feature = q.getStringField(1);
			char* c = const_cast<char*>(sql_feature);
			split(c, features);
			newIndex->Addsample(features, id - 1);
			q.nextRow();
		}
		q.finalize();
	}
	double end_time = clock();//1计时开始
	logger->info("load index elapsed {}ms\n", end_time - start_time);
}

int retri_Init() {
	logger->info("初始化元芒图像加速库");
	int ret = 0;
	if (drive_ai == 0) {
		ret = RETRI_Init(NULL, RETRI_DEVICE::RETRI_CPU, &handle);
	}
	else
	{
		ret = RETRI_Init(NULL, RETRI_DEVICE::RETRI_GPU, &handle);
	}
	if (ret != 0) {
		logger->info("初始化元芒图像加速库失败:{0:x}",ret);
		return -1;
	}
	else {
		logger->info("初始化元芒图像加速库成功");
		return 0;
	}
}

/**
 * 初始化faiss索引
 */
int WMAI_API Init(char* path) {

	if (init_finish == true)
	{
		logger->info("二次初始化");
		return 0;
	}

#if APPOINT_LIBRARY
	char DllPath[MAX_PATH] = { 0 };
	GetModuleFileNameA((HINSTANCE)&__ImageBase, DllPath, _countof(DllPath));
	//截取DLL所在目录(去掉DLL文件名)
	char fname[_MAX_FNAME];
	char ext[_MAX_EXT];
	_splitpath(DllPath, drive, dir, fname, ext);

	string new_dir = string(drive) + dir;
	char* ini_path = new char[MAX_PATH];
	strcpy(ini_path, (new_dir + "dev.ini").c_str());
	dev_ini_path = ini_path;


	char* gsz_file = new char[MAX_PATH];
	string ascll_gsz_file = new_dir + "featurexYz.db";
	string utf_8_gsz_file = ASCII2UTF_8(ascll_gsz_file);
	strcpy(gsz_file, utf_8_gsz_file.c_str());
	gszFile = gsz_file;


	char* imagePath = new char[MAX_PATH];
	string ascll_imagePath = new_dir + "AiImageWmAceKG.db";
	string utf_8_imagePath = ASCII2UTF_8(ascll_imagePath);
	strcpy(imagePath, utf_8_imagePath.c_str());
	wmimage = imagePath;


	char* tenant_path = new char[MAX_PATH];
	strcpy(tenant_path, (new_dir + "tenant.json").c_str());
	json_tenant_path = tenant_path;


	char* posId_path = new char[MAX_PATH];
	strcpy(posId_path, (new_dir + "posId.json").c_str());
	posId_json_path = posId_path;


	char* coordinate = new char[MAX_PATH];
	strcpy(coordinate, (new_dir + "coordinate.json").c_str());
	json_path = coordinate;


	char* logPath = new char[MAX_PATH];
	strcpy(logPath, (new_dir + "logs/daily.txt").c_str());
	log_path = logPath;


	char* wmphotoPath = new char[MAX_PATH];
	strcpy(wmphotoPath, (new_dir + "WMphoto").c_str());
	wmphoto_path = wmphotoPath;

#else
	// do nothing
#endif // 0

	if (logger == nullptr)
	{
		logger = spdlog::daily_logger_mt("daily_logger", log_path, 2, 7);
		logger->flush_on(spdlog::level::info);
		delete_daily();
	}

	char* js_path = new char[MAX_PATH];
	string ascll_json_file = string(path) + "\\" + "coordinate.json";
	string utf_8_json_file = ASCII2UTF_8(ascll_json_file);
	std::strcpy(js_path, utf_8_json_file.c_str());
	json_path = js_path;

	FILE* fp = fopen(json_path, "rb"); // 非 Windows 平台使用 "r"
	if (fp) {
		char readBuffer[65536];
		FileReadStream is(fp, readBuffer, sizeof(readBuffer));
		Document d;
		d.ParseStream(is);
		x = d["x"].GetInt();
		y = d["y"].GetInt();
		width = d["width"].GetInt();
		height = d["height"].GetInt();
		ai_x = d["ai_x"].GetInt();
		ai_y = d["ai_y"].GetInt();
		ai_width = d["ai_width"].GetInt();
		ai_height = d["ai_height"].GetInt();
		cameraNum = d["cameraNum"].GetInt();
		fclose(fp);
	}
	mask = imread("mask\\mask.jpg");
	char* json_ten = new char[MAX_PATH];
	string ascll_ten_file = string(path) + "\\" + "tenant.json";
	string utf_8_ten_file = ASCII2UTF_8(ascll_ten_file);
	std::strcpy(json_ten, utf_8_ten_file.c_str());
	json_tenant_path = json_ten;
	
	FILE* fp2 = fopen(json_tenant_path, "rb"); // 非 Windows 平台使用 "r"
	if (fp2) {
		char readBuffer2[65536];                                    
		FileReadStream is2(fp2, readBuffer2, sizeof(readBuffer2)); 
		Document d2; 
		d2.ParseStream(is2); 
		tenantStr = d2["demo"].GetString(); 
		fclose(fp2); 
	}

	char* posId_json = new char[MAX_PATH];
	string ascll_posid_file = string(path) + "\\" + "posId.json";
	string utf_8_posid_file = ASCII2UTF_8(ascll_posid_file);
	std::strcpy(posId_json, utf_8_posid_file.c_str());
	posId_json_path = posId_json;


	FILE* fp3 = fopen(posId_json_path, "rb"); // 非 Windows 平台使用 "r"
	if (fp3) {
		char readBuffer3[65536]; 
		FileReadStream is3(fp3, readBuffer3, sizeof(readBuffer3)); 
		Document d3; 
		d3.ParseStream(is3); 
		posId = d3["posId"].GetString();
		fclose(fp3);
	}
	GetMacByGetAdaptersAddresses(macAddr); 
	del_rod(macAddr); 
	mINI::INIFile file(dev_ini_path); 
	mINI::INIStructure ini; 
	file.read(ini); 
	dev = ini["default"]["dev"]; 
	mode = atoi(ini["default"]["mode"].c_str()); 
	drive_ai = atoi(ini["default"]["drive_ai"].c_str()); 
	openSync = atoi(ini["default"]["openSync"].c_str()); 
	filterScore = atof(ini["default"]["filterScore"].c_str()); 
	video_time = atoi(ini["default"]["videoTime"].c_str());
	// 开始创建tcp服务端
	initTcpServer(); 

	//初始化图形加速库
	int ret = retri_Init(); 
	logger->info("图形加速库初始化结果:{0:x}", ret);

	//添加索引
	float idx_start = clock(); 

	char* gsz_file = new char[MAX_PATH];
	string ascll_gsz_file = string(path)+"\\" + "featurexYz.db";
	string utf_8_gsz_file = ASCII2UTF_8(ascll_gsz_file);
	std::strcpy(gsz_file, utf_8_gsz_file.c_str());
	gszFile = gsz_file;
	logger->info("gszFile  is {}", gszFile);


	CppSQLite3DB db; 
	db.open(gszFile); 
	db.setBusyTimeout(5000); 

	if (db.tableExists("feature_code")) {
		CppSQLite3Query queryColumn3 = db.execQuery("select * from sqlite_master where name='feature_code' and sql like '%deleted%';");
		if (queryColumn3.eof()) {
			//没有 "列名" 列
			//新增列
			db.execDML("ALTER TABLE feature_code ADD COLUMN deleted INTEGER DEFAULT 0;");
		}
	}


	idx =  new libhnsw::Index(FEATURE_COUNT, DATABASE_SIZE, libhnsw::BRUTE_FORCE_KNN_CF,libhnsw::INNER_PRODUCT);
	loadIndexFromDB(db, idx);
	float idx_end = clock();
	logger->info("idx init cost:{}",idx_end - idx_start);

	if (db.tableExists("product_record")) {
		CppSQLite3Buffer deleteProductRecord;
		deleteProductRecord.format("delete from product_record;");
		int row = db.execDML(deleteProductRecord);
		logger->info("{} row product_record has been deleted", row);
	}
	
	db.execDML("CREATE TABLE IF NOT EXISTS product_model (id integer PRIMARY KEY AUTOINCREMENT, product_code text NOT NULL, model_code integer NOT NULL);");
	db.execDML("CREATE TABLE IF NOT EXISTS product_model_counting (id integer PRIMARY KEY AUTOINCREMENT, product_code text NOT NULL, model_code integer NOT NULL, num integer NOT NULL);");
	db.execDML("CREATE TABLE IF NOT EXISTS feature_code (id integer PRIMARY KEY AUTOINCREMENT, code text NOT NULL, feature text NOT NULL, update_time TIMESTAMP DEFAULT NULL, deleted integer DEFAULT NULL);");
	db.execDML("CREATE TABLE IF NOT EXISTS feed_back_record (id integer PRIMARY KEY AUTOINCREMENT, code text NOT NULL, session_id text NOT NULL, hit integer NOT NULL);");
	//储存空盘向量
	db.execDML("CREATE TABLE IF NOT EXISTS empty_pan_feature (id integer PRIMARY KEY AUTOINCREMENT,feature text NOT NULL);");

	//检测表单
	if (!db.tableExists("record_count")) {
		db.execDML("CREATE TABLE IF NOT EXISTS record_count (id integer PRIMARY KEY AUTOINCREMENT, count integer NOT NULL);");;
	}

	//检测表单,此表单为计数

	db.execDML("CREATE TABLE IF NOT EXISTS video_count (id integer PRIMARY KEY AUTOINCREMENT, count_in integer NOT NULL)");
	//检测record_count表达数据
	
	CppSQLite3Buffer selectvideocount;
	selectvideocount.format("select count(*) from video_count"); 
	CppSQLite3Query q = db.execQuery(selectvideocount); 
	int pre1;
	while (!q.eof())
	{
		pre1 = q.getIntField(0); 
		break;
	}
	q.finalize();

	if (pre1 == 0)
	{
		rest_count = 0; 
		logger->info("rest_count is{}", rest_count);
	}
	else
	{
		CppSQLite3Buffer  selectvideo; 
		selectvideo.format("select count_in from video_count where id=1;"); 
		CppSQLite3Query q2 = db.execQuery(selectvideo); 
		while (!q2.eof())
		{
			rest_count = q2.getIntField(0);
			logger->info("rest_count is{}",rest_count);
			break;
		}
		q2.finalize(); 
	}

	
	//检测record_count表达数据
	CppSQLite3Buffer measure; 
	measure.format("select count(*) from record_count;"); 
	CppSQLite3Query q1 = db.execQuery(measure); 
	ret = 0;
	while (!q1.eof())
	{
		ret = q1.getIntField(0); 
		break;
	}
	q1.finalize(); 
	logger->info("record_count ret :{}", ret); 
	if (ret == 0)
	{
		int count = 1; 
		CppSQLite3Buffer insert; 
		insert.format("insert into record_count(count) values (%d)", count);
		db.execDML(insert);
	}

	if (!db.tableExists("product_record")) {
		db.execDML("CREATE TABLE IF NOT EXISTS product_record (id integer PRIMARY KEY AUTOINCREMENT, session_id TEXT NOT NULL, model_code INTEGER NOT NULL, score REAL NOT NULL,feature_value TEXT NOT NULL,image_path TEXT NOT NULL,product_ids TEXT NOT NULL,real_codes TEXT NOT NULL,time TEXT NOT NULL);");
		db.execDML("CREATE INDEX session_id_index ON product_record(session_id);"); 
	}
	// sqlite_master 为sqlite隐藏系统表
	CppSQLite3Query queryColumn = db.execQuery("select * from sqlite_master where name='product_record' and sql like '%real_codes%';");
	if (queryColumn.eof()) {
		//没有 "列名" 列
		//新增列
		db.execDML("ALTER TABLE product_record ADD COLUMN real_codes TEXT NOT NULL;");
	}
	CppSQLite3Query queryColumn2 = db.execQuery("select * from sqlite_master where name='feature_code' and sql like '%update_time%';");
	if (queryColumn2.eof()) {
		//没有 "列名" 列
		//新增列
		db.execDML("ALTER TABLE feature_code ADD COLUMN update_time TIMESTAMP DEFAULT NULL;");
		CppSQLite3Buffer updateFeatureCodeTime;
		updateFeatureCodeTime.format("update feature_code set update_time = datetime('now','localtime');");
		db.execDML(updateFeatureCodeTime);
	}
	queryColumn.finalize();
	queryColumn2.finalize();
	db.close();

	string dir_path = tool_get_dll_path();
	wmphoto_path = dir_path + "WMPhoto";
	DeleteDirectory(wmphoto_path.c_str());

	init_finish = true;
	logger->info("init end");
	return  0;
}



// Tenengrad模糊检测
float TenengradBlurDetect(const cv::Mat& srcimg) {
	cv::Mat x_grad, y_grad, src_gray;
	cv::cvtColor(srcimg, src_gray, 6);
	cv::Sobel(src_gray, x_grad, CV_16S, 1, 0, 3);
	cv::Sobel(src_gray, y_grad, CV_16S, 0, 1, 3);

	cv::Mat abs_grad_x, abs_grad_y;

	int width = srcimg.cols;
	int height = srcimg.rows;
	int step = srcimg.step;
	cv::convertScaleAbs(x_grad, abs_grad_x);
	cv::convertScaleAbs(y_grad, abs_grad_y);

	uchar* udata_x = abs_grad_x.data;
	uchar* udata_y = abs_grad_y.data;
	float sum = 0;
	for (int j = 0; j < height; j++) {
		for (int i = 0; i < width; i++) {
			float udata_x_d = udata_x[j * width + i] / 255;
			float udata_y_d = udata_y[j * width + i] / 255;
			sum = sum + udata_x_d * udata_x_d + udata_y_d * udata_y_d;
		}
	}
	// printf("TenengradBlurDetect=%f ms\n", ti);
	return 100 * sum / (width * height);
}

// 获取mac地址
bool checkMac(std::string& decryptText)
{
	bool ret = false;

	ULONG outBufLen = sizeof(IP_ADAPTER_ADDRESSES);
	PIP_ADAPTER_ADDRESSES pAddresses = (IP_ADAPTER_ADDRESSES*)malloc(outBufLen);
	if (pAddresses == NULL)
		return false;
	// Make an initial call to GetAdaptersAddresses to get the necessary size into the ulOutBufLen variable
	if (GetAdaptersAddresses(AF_UNSPEC, 0, NULL, pAddresses, &outBufLen) == ERROR_BUFFER_OVERFLOW)
	{
		free(pAddresses);
		pAddresses = (IP_ADAPTER_ADDRESSES*)malloc(outBufLen);
		if (pAddresses == NULL)
			return false;
	}

	if (GetAdaptersAddresses(AF_UNSPEC, 0, NULL, pAddresses, &outBufLen) == NO_ERROR)
	{
		// If successful, output some information from the data we received
		for (PIP_ADAPTER_ADDRESSES pCurrAddresses = pAddresses; pCurrAddresses != NULL; pCurrAddresses = pCurrAddresses->Next)
		{
			// 确保MAC地址的长度为 00-00-00-00-00-00
			if (pCurrAddresses->PhysicalAddressLength != 6)
				continue;
			char acMAC[32];
			sprintf(acMAC, "%02X-%02X-%02X-%02X-%02X-%02X",
				int(pCurrAddresses->PhysicalAddress[0]),
				int(pCurrAddresses->PhysicalAddress[1]),
				int(pCurrAddresses->PhysicalAddress[2]),
				int(pCurrAddresses->PhysicalAddress[3]),
				int(pCurrAddresses->PhysicalAddress[4]),
				int(pCurrAddresses->PhysicalAddress[5]));
			if (decryptText == acMAC) {
				ret = true;
				break;
			}
		}
	}

	free(pAddresses);
	return ret;
}


// 判断是否授权
bool probationAuth() {
	logger->info("没有找到正式密钥 开始使用试用密钥");
#if APPOINT_LIBRARY
	string new_dir = string(drive) + dir;
	string pprikey = new_dir + "pprikey.pem";
	string pwmkey = new_dir + "pwmkey.wm";
#else
	string pprikey = "pprikey.pem";
	string pwmkey = "pwmkey.wm";
#endif // 0

	if (!exists_file(pprikey) || !exists_file(pwmkey)) {
		return false;
	}
	string str = readTxt(pwmkey);
	string encryptText = base64Decode((char*)str.c_str());
	string decryptText = rsa_pri_decrypt(encryptText, pprikey);

	string macTime[2];
	char* decryptStr = const_cast<char*>(decryptText.c_str());
	logger->info("decryptStr:{}", decryptStr);
	splitStringColon(decryptStr, macTime);
	// 校验mac地址
	bool authorize = checkMac(macTime[0]);
	if (!authorize) {
		// mac不一致直接过期
		return authorize;
	}
	// 校验有效期
	long expireTime;
	std::stringstream sstr;
	sstr << macTime[1];
	sstr >> expireTime;
	// 过期时间unix时间戳 - 当前unix时间戳 > 0 || 过期时间unix时间戳 - 当前unix时间戳 > 15
	std::time_t t = std::time(0);
	long timeSpace = expireTime - t;
	if (timeSpace > 0 && timeSpace <= 15 * 86400) {
		int lastDays = timeSpace / 86400;
		logger->info("剩余使用天数为{}", lastDays);
		return true;
	}
	return false;
}

// 判断是否授权
bool authorize() {
#if APPOINT_LIBRARY
	string new_dir = string(drive) + dir;
	string prikey = new_dir + "prikey.pem";
	string wmkey = new_dir + "wmkey.wm";
#else
	string prikey = "prikey.pem";
	string wmkey = "wmkey.wm";
#endif // 0

	if (!exists_file(prikey) || !exists_file(wmkey)) {
		return probationAuth();
	}
	string str = readTxt(wmkey);
	string encryptText = base64Decode((char*)str.c_str());
	string decryptText = rsa_pri_decrypt(encryptText, prikey);
	bool authorize = checkMac(decryptText);
	return authorize;
}

//static int k;
int detect_squeezenet(const cv::Mat& bgr, std::vector<float>& cls_scores, float* rhs)
{
	// step1:初始化handle
	logger->info("进入元芒图像加速库");
	if (handle == nullptr) {
		logger->info("初始化元芒图像加速库");
		int hr = 0;
		if (drive_ai == 0) {
			hr = RETRI_Init(NULL, RETRI_DEVICE::RETRI_CPU, &handle);
		}
		else
		{
			hr = RETRI_Init(NULL, RETRI_DEVICE::RETRI_GPU, &handle);
		}
		if (hr != 0)
		{
			logger->info("元芒图形加速库初始化失败!error code:{o:x}\n", hr);
			return -1;
		}
		else
		{
			logger->info("元芒图形加速库初始化成功");
		}
	}
	// step2:推理
	RETRI_INPUT in_img;
	in_img.img = bgr.clone();
	RETRI_OUTPUT clsretri_output;
	cv::imwrite("input.jpg", in_img.img);
	//string inimg = tool_get_dll_path()+"in-image";
	
	//_mkdir(inimg.c_str());
	//char image_name[120] = {};
	//sprintf(image_name, "\\%lld.jpg", k);
	//std::string dir_path = inimg+"\\"+image_name;
	//imwrite(dir_path, in_img.img);
	//k++;
	
	int s_time = clock();
	int hr = RETRI_Process(in_img, &clsretri_output, handle);
	int e_time = clock();
	if (hr != 0)
	{
		logger->info("元芒图形加速库推理失败!error code:{0:x}\n", hr);
		return -2;
	}
	logger->info("元芒图形加速库耗时:{}ms\n", e_time - s_time);

	//step3.返回结果
	for (int i = 0; i < FEATURE_COUNT; i++)
	{
		rhs[i] = clsretri_output.feat[i];
	}
	return 0;
}

// 保存top10条记录 并且打印
int save_topk(const std::vector<float>& cls_scores, int topk, int* index, float* scores)
{
	//index = new int[topk];
	//scores = new float[topk];
	std::vector< std::pair<float, int> > vec;
	// partial sort topk with index
	int size = cls_scores.size();
	vec.resize(size);
	for (int i = 0; i < size; i++)
	{
		vec[i] = std::make_pair(cls_scores[i], i);
	}

	std::partial_sort(vec.begin(), vec.begin() + topk, vec.end(),
		std::greater< std::pair<float, int> >());

	// print topk and score
	for (int i = 0; i < topk; i++)
	{
		scores[i] = vec[i].first;
		index[i] = vec[i].second;
		//float score = vec[i].first;
		//int index = vec[i].second;
		//logger->info("{} = {}\n", index[i], scores[i]);
	}
	return 0;
}

float calculSimilar(float v1[], float v2[], int size) {
	// assert(v1.size() == v2.size());
	double ret = 0.0, mod1 = 0.0, mod2 = 0.0;
	for (int i = 0; i < size; ++i) {
		ret += v1[i] * v2[i];
		mod1 += v1[i] * v1[i];
		mod2 += v2[i] * v2[i];
	}
	if (mod1 == 0 || mod2 == 0) return 0;
	return (ret / sqrt(mod1) / sqrt(mod2) + 1) / 2.0;
}


int compare_feature_faiss(float* rhs, string* code, int& biggerNinety) {
	try {
		CppSQLite3DB db; 
		db.open(gszFile);  
		if (!db.tableExists("feature_code")) {
			return 0;  
		}
		//  ============ 搜索 ===================
		reassion.clear();  
		double startTime1 = clock();//1计时开始   

		//logger->info("detect feature:{}", rhs);
		///
		//char rhs_data[FEATURE_COUNT * 10] = { };
	/*	for (int i = 0; i < FEATURE_COUNT; ++i)
		{
			sprintf(rhs_data + strlen(rhs_data), "%f,", rhs[i]);
		}
		logger->info("detect feature:{}", rhs_data);*/
		
		std::vector<std::pair<float, int>>res = idx->Search(rhs,6);  
		for (unsigned int j = 0; j < res.size(); j++)
		{
			std::pair<float, int> r = res[j];   
			//logger->info("res[{}]:id {},prob {}\n", j, r.second, 1-r.first);
			int number = r.second;   

			// 商品向量
			float features[FEATURE_COUNT];  
			//获得对应的商品代

			number = number + 1;   
			 
			CppSQLite3Buffer selectFeatureCode;   
			selectFeatureCode.format("select code,feature,id from feature_code where id = %d;", number);   
			CppSQLite3Query q = db.execQuery(selectFeatureCode);   
			string sql_code;  
			int id;
			while (!q.eof())
			{
				sql_code = q.getStringField(0);
				const char* sql_feature = q.fieldValue(1);
				id = q.getIntField(2);
				char* c = const_cast<char*>(sql_feature);   
				split(c, features);
				q.nextRow();
				//logger->info("idx;{} feature:{}", j, sql_feature);
			}
			q.finalize();   

			float sim = calculSimilar(rhs, features, FEATURE_COUNT);   
			
			logger->info("{} is {}", sql_code.c_str(), sim);   
			if (sim > filterScore)
			{
				code[j + 3] = sql_code;   
				reassion.emplace(sql_code, id);
				// 如果大于过滤值 说明该向量最近被使用 修改最后更新时间
				CppSQLite3Buffer updateFeatureCode;
				updateFeatureCode.format("update feature_code set update_time = datetime('now','localtime') where id = %d", id);
				db.execDML(updateFeatureCode);
			}
			if (j == 0 && sim > 0.9) {
				logger->info("第一个向量大于0.9的置信度 置换第一个商品代码");
				biggerNinety = 1;
			}

	
			// scores[j+3] = feature;
		}
		db.close();

		for (int i = 0; i < 10; i++)
		{
			if (code[i].empty() || code[i] == "0") {
				continue;
			}
			for (int j = i + 1; j < 10; j++)
			{
				if (code[j].empty() || code[j] == "0") {
					continue;
				}
				if (code[i] == code[j]) {
					code[j] = "0";
				}
			}
		}

		double endTime1 = clock();//1计时结束
		double elasped = endTime1 - startTime1;
		logger->info("index time : elapsed {}ms\n", elasped);
	}
	catch (...) {
		logger->info("compare_feature_faiss failed");
	}
	return 0;
}

const char* getMaxLimpidPic(const char* imagepath1, const char* imagepath2, const char* imagepath3, int count) {
	if (count == 1) {
		return imagepath1;
	}
	if (count == 2) {
		return imagepath2;
	}
	if (count == 3) {
		return imagepath3;
	}
	throw "imread read error";
}

cv::Mat getMaxMat(const char* imagepath1, const char* imagepath2, const char* imagepath3, int& count) {
	cv::Mat m1 = cv::imread(imagepath1, 1);
	if (m1.empty())
	{
		logger->info("cv::imread {} failed\n", imagepath1);
		throw "cv::imread failed";
	}
	float f1 = TenengradBlurDetect(m1);
	cv::Mat m2 = cv::imread(imagepath2, 1);
	if (m2.empty())
	{
		logger->info("cv::imread {} failed\n", imagepath2);
		throw "cv::imread failed";
	}
	float f2 = TenengradBlurDetect(m2);
	cv::Mat m3 = cv::imread(imagepath3, 1);
	if (m3.empty())
	{
		logger->info("cv::imread {} failed\n", imagepath3);
		throw "cv::imread failed";
	}
	float f3 = TenengradBlurDetect(m3);
	float fmax = max(max(f1, f2), f3);
	if (fmax == f1) {
		count = 1;
		return m1;
	}
	else if (fmax == f2) {
		count = 2;
		return m2;
	}
	else if (fmax == f3) {
		count = 3;
		return m3;
	}
	else {
		throw "cv::imread failed";
	}
}

// 保存打称记录
int save_record(std::string sessionId, int index, float score, float time, string imagepath, string features, string productcode, string codes) {
	try {
		CppSQLite3DB db;
		db.open(gszFile);
		CppSQLite3Buffer insertProductCode;
		insertProductCode.format("insert into product_record(session_id, model_code, score,feature_value,image_path,product_ids,real_codes,time) values (%Q,%d,%f,%Q,%Q,%Q,%Q,%f);", sessionId.c_str(), index, score, features.c_str(), imagepath.c_str(), productcode.c_str(), codes.c_str(), time);
		db.execDML(insertProductCode);
		db.close();
		return 0;
	}
	catch (...) {
		logger->info("save_record failed");
		return -1;
	}
}

// 获取10位sessionId
string get_session_id()
{
	char prefix[50];
	time_t nowTime;
	tm* now;
	time(&nowTime);				//获取系统当前时间戳
	now = localtime(&nowTime);	//将时间戳转化为时间结构体

	srand((int)time(0));
	long l = rand() % 9000000000 + 1000000000;
	ostringstream os;
	os << l;
	string result;
	istringstream is(os.str());
	is >> result;

	sprintf(prefix, "%d%d%d%d%d%d%d%d", macAddr.c_str(), now->tm_year + 1900, now->tm_mon + 1, now->tm_mday,
		now->tm_hour + 8, now->tm_min, now->tm_sec, result.c_str());
	string s(prefix, prefix + strlen(prefix));
	return s;
}


int convert_model_code(int* index, float* scores, string* productcode) {
	CppSQLite3DB db;
	db.open(gszFile);
	if (!db.tableExists("product_model")) {
		for (int i = 0; i < 10; i++)
		{
			productcode[i] = "0";
		}
		return 0;
	}
	vector<string> indexProductCodes = { "0","0" ,"0" ,"0" ,"0" ,"0" ,"0" };
	CppSQLite3Query q;
	int count = 0;
	for (int i = 0; i < 10; i++)
	{
		if (count > 6) {
			break;
		}
		if (scores[i] > 0.01) {
			CppSQLite3Buffer selectProductCode;
			selectProductCode.format("select product_code from product_model where model_code = %d;", index[i]);
			cout << selectProductCode << endl;
			q = db.execQuery(selectProductCode);
			while (!q.eof())
			{
				if (count > 6) {
					break;
				}
				string productCode = q.getStringField(0);
				indexProductCodes[count] = productCode;
				++count;
				q.nextRow();
			}
		}
	}
	q.finalize();
	db.close();
	for (int i = 0; i < 10; i++)
	{
		if (productcode[i] != "") {
			continue;
		}
		if (i < 3) {
			productcode[i] = indexProductCodes[i];
		}
		else {
			productcode[i] = indexProductCodes[i - 3];
		}
	}
	return 0;
}

void saveImg(string pic_Name, cv::Mat mat) {
	cv::imwrite(pic_Name, mat);
}

// 拍照
cv::Mat getScaleBitmap(char* rawPath, char* cropPath, bool saveRawImg) {
	cv::Mat returnFrame, frame1, frame2, frame3; 
	double startTime1, endTime1, elasped;  
	startTime1 = clock();//1计时开始
	char pic_Name[128] = {};     //照片名称      
	char raw_pic_Name[128] = {};     //照片名称  
	if (!cap.isOpened())
	{
		logger->info("The camera open failed!");  
		return returnFrame;
	}
	if (frame_one.empty() || frame_thr.empty() || frame_two.empty()) {
		logger->info("camera not init,please wait");
		return returnFrame;
	}
	time_t nowTime;
	tm* now;                                        
	WaitForSingleObject(PhotoMutex, INFINITE);       
	frame1= frame_one.clone();               
	frame2= frame_two.clone();         
	frame3 =frame_thr.clone();            
	ReleaseMutex(PhotoMutex);
	endTime1 = clock();
	elasped = endTime1 - startTime1;            
	logger->info("camera load elapsed {}ms\n", elasped);      
	time(&nowTime);				//获取系统当前时间戳
	now = localtime(&nowTime);	//将时间戳转化为时间结构体

	_mkdir(wmphoto_path.c_str());                     
	if (saveRawImg) {
#if APPOINT_LIBRARY
		string new_dir = wmphoto_path;
		char photoPath[MAX_PATH];
		strcpy(photoPath, (new_dir + "\\raw%d%d%d%d%d%d.jpg").c_str());
#else
		char photoPath[MAX_PATH];
		std::strcpy(photoPath, (wmphoto_path+"\\raw%d%d%d%d%d%d.jpg").c_str());
#endif // 0
		sprintf(raw_pic_Name, photoPath, now->tm_year + 1900, now->tm_mon + 1, now->tm_mday, 
			now->tm_hour + 8, now->tm_min, now->tm_sec);
		cv::imwrite(raw_pic_Name, frame1);                                                
		std::strcpy(rawPath, raw_pic_Name);
	}
	if (x && y && width && height) {
#if APPOINT_LIBRARY
		string new_dir = wmphoto_path;
		char photoPath[MAX_PATH];
		strcpy(photoPath, (new_dir + "\\%d%d%d%d%d%d.jpg").c_str());
#else
		char photoPath[MAX_PATH];
		std::strcpy(photoPath, (wmphoto_path+"\\%d%d%d%d%d%d.jpg").c_str());
#endif // 0
		sprintf(pic_Name, photoPath, now->tm_year + 1900, now->tm_mon + 1, now->tm_mday,
			now->tm_hour + 8, now->tm_min, now->tm_sec);
		cv::Mat catImage1, catImage2, catImage3;

		copyTo(frame1,catImage1,mask);
		catImage1 = catImage1(cv::Rect(x, y, width, height));

		copyTo(frame2, catImage2, mask);
		catImage2 = catImage2(cv::Rect(x, y, width, height));

		copyTo(frame3, catImage3, mask);
		catImage3 = catImage3(cv::Rect(x, y, width, height));

		float f1 = TenengradBlurDetect(catImage1);
		float f2 = TenengradBlurDetect(catImage2);
		float f3 = TenengradBlurDetect(catImage3);
		float maxF = max(max(f1, f2), f3);       
		if (maxF == f1) {
			returnFrame = catImage1.clone();
			if (saveRawImg) {
				// 如果是主动拍照 路径同步返回
				cv::imwrite(pic_Name, catImage1);
			}
			else {
				std::thread t(saveImg, string(pic_Name), catImage1);	//将Mat数据写入文件
				t.detach();
			}
		}
		if (maxF == f2) {
			returnFrame = catImage2.clone();
			if (saveRawImg) {
				// 如果是主动拍照 路径同步返回
				cv::imwrite(pic_Name, catImage2);
			}
			else {
				std::thread t(saveImg, string(pic_Name), catImage2);	//将Mat数据写入文件
				t.detach();
			}
		}
		if (maxF == f3) {
			returnFrame = catImage3.clone();
			if (saveRawImg) {
				// 如果是主动拍照 路径同步返回
				cv::imwrite(pic_Name, catImage3);
			}
			else {
				std::thread t(saveImg, string(pic_Name), catImage3);	//将Mat数据写入文件
				t.detach();
			}
		}
		std::strcpy(cropPath, pic_Name);  
	}
	endTime1 = clock();//1计时开始
	elasped = endTime1 - startTime1;   
	logger->info("camera all elapsed {}ms\n", elasped);  
	return returnFrame;
}

void _stringCpy(string* des,string* rec,int len) {
	for (size_t i = 0; i < len; i++)
	{
		des[i] = rec[i];
	}
}

// 主动识别接口
int MatDetect(char* productcodes, char* sessionId) {
	double all_start_time = clock();//1计时开始  
	if (!authed) {
		authed = authorize();
		if (!authed) {
			logger->info("验证失败");
			return -2001;
		};
	}
	if (!x && !y && !width && !height) {
		logger->info("没有设置摄像头剪辑坐标");
		return -2009;
	}
	int thread = 4;
	int use_gpu = 0;

#if NCNN_VULKAN
	// ncnn::create_gpu_instance(); 
#endif // NCNN_VULKAN
	char rawPath[128];
	char cropPath[128];
	cv::Mat m = getScaleBitmap(rawPath, cropPath, false);
	if (m.empty())
	{
		logger->info("cv::imread failed\n");
		return -2002;
	}
	int biggerNinety = 0;
	// 拷贝图片地址
	//strncpy(image, cropPath, 128);
	string productcode[10];
	std::vector<float> cls_scores;
	float rhs[FEATURE_COUNT];
	double start_time = ncnn::get_current_time();
	int ret = detect_squeezenet(m, cls_scores, rhs);
	double elasped = ncnn::get_current_time() - start_time;
	logger->info("category elapsed {}ms\n", elasped);
	compare_feature_faiss(rhs, productcode, ref(biggerNinety));

	double save_record_start_time = clock();//1计时开始  
	string newSessionId = get_session_id();
	std::strcpy(sessionId, newSessionId.c_str());
	// 组装返回的商品代码
	char codes[10 * 10] = { };
	for (int i = 0; i < 10; ++i)
	{
		if (productcode[i].empty() || productcode[i] == "0") {
			continue;
		}
		sprintf(codes + strlen(codes), "%s,", productcode[i].c_str());
	}
	logger->info("productcodes:{}", codes);
	std::strcpy(productcodes, codes);

	string  product_codes = productcodes;

	char* realIds = const_cast<char*>(product_codes.c_str());
	splitString(realIds, realProductCodes);
	// 将图片向量进行逗号组装 
	char featureValue[FEATURE_COUNT * 10] = { };
	for (int i = 0; i < FEATURE_COUNT; ++i)
	{
		sprintf(featureValue + strlen(featureValue), "%f,", rhs[i]);
	}
	// 组装productIds
	char productIds[10 * 10] = { };
	for (int i = 0; i < 10; ++i)
	{
		if (productcode[i].empty()) {
			continue;
		}
		sprintf(productIds + strlen(productIds), "%s,", productcode[i].c_str());
	}



	//判断是否识别到了空盘
	double empty_find_begin = clock();
	//打开数据库
	CppSQLite3DB db;
	db.open(gszFile);

	for (int i = 1; i < 31; ++i)
	{
		
		float features[128];
		CppSQLite3Buffer select_empty;
		select_empty.format("select feature from empty_pan_feature where id=%d", i);
		CppSQLite3Query q = db.execQuery(select_empty);

		while (!q.eof())
		{
			const char* sql_feature = q.fieldValue(0);
			char* c = const_cast<char*>(sql_feature);
			split(c, features);
			break;
		}
		q.finalize();

		//判断是否识别到了空盘
		float sim = calculSimilar(rhs, features, FEATURE_COUNT);
		char rhs_data[FEATURE_COUNT * 10] = { };

		logger->info("相似度为 sim is{}", sim);
		if (sim >= 0.95)
		{
			result.state = 2;
			logger->info("result.state is {}", result.state);
			memset(productIds, '\0', sizeof(productIds));
			logger->info("productIds is {}", productIds);
			break;
		}
		//logger->info("result.state is {}", result.state);
		//param_in.result_queue.push(result);
	}

	db.close();
	double empty_find_end = clock();
	logger->info("result.state is {}", result.state);
	logger->info("判断是否为空盘的时间为:{}", empty_find_end- empty_find_begin);


	save_record(newSessionId, 0, 0, elasped, string(cropPath), string(featureValue), string(productIds), string(codes));

	double save_record_end_time = clock();//1计时开始  
	logger->info("save record elapsed {}ms\n", save_record_end_time - save_record_start_time);
	double all_end_time = clock();//1计时开始  
	logger->info("all time elapsed {}ms\n", all_start_time - all_end_time);
	return 0;
}

// 主动识别接口
int WMAI_API DemoDetect(const char* imagePath, char* productcodes, char* sessionId) {
	double all_start_time = clock();//1计时开始
	if (!authed) {
		authed = authorize();
		if (!authed) {
			cout << "验证失败" << endl;
			return -1;
		};
	}
	int use_gpu = 0;

#if NCNN_VULKAN
	// ncnn::create_gpu_instance(); 
#endif // NCNN_VULKAN
	cv::Mat m = cv::imread(imagePath, 1);
	if (m.empty())
	{
		logger->info("cv::imread {} failed\n", imagePath);
		return -1;
	}

	int biggerNinety = 0;
	string productcode[10];
	std::vector<float> cls_scores;
	float rhs[FEATURE_COUNT];
	double start_time = ncnn::get_current_time();
	int ret = detect_squeezenet(m, cls_scores, rhs);
	if (ret != 0) {
		logger->info("元芒图形加速识别失败,error:ret",ret);
		return -2004;
	}
	std::thread t(compare_feature_faiss, rhs, productcode, ref(biggerNinety)); 
	t.join();
	double elasped = ncnn::get_current_time() - start_time;
	logger->info("category elapsed %.2fms\n", elasped);

	double save_record_start_time = clock();//1计时开始
	string newSessionId = get_session_id().c_str();
	std::strcpy(sessionId, newSessionId.c_str());
	// 0 1 2 3 4 5 6 7 8 9
	// 3 0 1 2 4 5 6 7 8 9
	string returnCodes[10];
	_stringCpy(returnCodes, productcode, 10);
	if (biggerNinety) {
		string codeFour;
		for (int i = 3; i > 0; i--)
		{
			if (i == 3) {
				codeFour = returnCodes[i];
			}
			returnCodes[i] = returnCodes[i - 1];
		}
		returnCodes[0] = codeFour;
	}
	// 去重returnCodes
	for (int i = 0; i < 10; i++)
	{
		if (returnCodes[i].empty() || returnCodes[i] == "0") {
			continue;
		}
		for (int j = i + 1; j < 10; j++)
		{
			if (returnCodes[j].empty() || returnCodes[j] == "0") {
				continue;
			}
			if (returnCodes[i] == returnCodes[j]) {
				returnCodes[j] = "0";
			}
		}
	}
	// 组装返回的商品代码
	char codes[10 * 10] = { };
	for (int i = 0; i < 10; ++i)
	{
		if (returnCodes[i].empty() || returnCodes[i] == "0") {
			continue;
		}
		sprintf(codes + strlen(codes), "%s,", returnCodes[i].c_str());
	}
	logger->info("productcodes:{}", codes);
	std::strcpy(productcodes, codes);
	// 将图片向量进行逗号组装
	char featureValue[FEATURE_COUNT * 10] = { };
	for (int i = 0; i < FEATURE_COUNT; ++i)
	{
		sprintf(featureValue + strlen(featureValue), "%f,", rhs[i]);
	}
	// 组装productIds
	char productIds[10 * 10] = { };
	for (int i = 0; i < 10; ++i)
	{
		sprintf(productIds + strlen(productIds), "%s,", productcode[i].c_str());
	}

	double save_record_end_time = clock();//1计时开始
	std::thread t1(save_record, newSessionId, 0, 0, elasped, string(imagePath), string(featureValue), string(productIds), string(codes));
	t1.detach();
	logger->info("save record elapsed {}ms\n", save_record_end_time - save_record_start_time);
	double all_end_time = clock();//1计时开始
	logger->info("all time elapsed {}ms\n", all_start_time - all_end_time);
	return 0;
}

// 零食识别
int doSnacksAutoDetect(char* productcodes, char* sessionId, char* image) {
	double all_start_time = clock();//1计时开始  
	if (!authed) {
		authed = authorize();  
		if (!authed) {
			logger->info("验证失败");  
			return -2001;
		};
	}
	if (!x && !y && !width && !height) {
		logger->info("没有设置摄像头剪辑坐标");  
		return -2009;
	}
	int thread = 4;  
	int use_gpu = 0;  

#if NCNN_VULKAN
	// ncnn::create_gpu_instance(); 
#endif // NCNN_VULKAN
	char rawPath[128];  
	char cropPath[128];  
	cv::Mat m = getScaleBitmap(rawPath, cropPath, false);  
	if (m.empty())
	{
		logger->info("cv::imread failed\n"); 
		return -2002;
	}
	int biggerNinety = 0;  
	// 拷贝图片地址
	strncpy(image, cropPath, 128); 
	string productcode[10];      
	std::vector<float> cls_scores;   
	float rhs[FEATURE_COUNT];  
	double start_time = ncnn::get_current_time();   
	int ret = detect_squeezenet(m, cls_scores, rhs);   
	double elasped = ncnn::get_current_time() - start_time;   
	logger->info("category elapsed {}ms\n", elasped);     
	compare_feature_faiss(rhs, productcode, ref(biggerNinety));   

	double save_record_start_time = clock();//1计时开始  
	string newSessionId = get_session_id();   
	std::strcpy(sessionId, newSessionId.c_str());   
	// 组装返回的商品代码
	char codes[10 * 10] = { };   
	for (int i = 0; i < 10; ++i)
	{
		if (productcode[i].empty() || productcode[i] == "0") { 
			continue;
		}
		sprintf(codes + strlen(codes), "%s,", productcode[i].c_str()); 
	}
	logger->info("productcodes:{}", codes);   
	std::strcpy(productcodes, codes);

	string  product_codes = productcodes;   
	
	char* realIds = const_cast<char*>(product_codes.c_str());  
	splitString(realIds, realProductCodes);  
	// 将图片向量进行逗号组装 
	char featureValue[FEATURE_COUNT * 10] = { };  
	for (int i = 0; i < FEATURE_COUNT; ++i) 
	{ 
		sprintf(featureValue + strlen(featureValue), "%f,", rhs[i]);
	}
	// 组装productIds
	char productIds[10 * 10] = { };      
	for (int i = 0; i < 10; ++i)
	{
		if (productcode[i].empty()) {
			continue;
		}
		sprintf(productIds + strlen(productIds), "%s,", productcode[i].c_str());   
	}
	

	save_record(newSessionId, 0, 0, elasped, string(cropPath), string(featureValue), string(productIds), string(codes));   

	double save_record_end_time = clock();//1计时开始  
	logger->info("save record elapsed {}ms\n", save_record_end_time - save_record_start_time);  
	double all_end_time = clock();//1计时开始  
	logger->info("all time elapsed {}ms\n", all_start_time - all_end_time);
	return 0;
}

// 主动识别接口
int WMAI_API AutoDetect(char* productcodes, char* sessionId) 
{
	
	char image[128]; 
	int ret = doSnacksAutoDetect(productcodes, sessionId, image); 
	return ret;

}

int getTopNo(string* productCodes, const char* code) {
	for (int i = 0; i < 10; i++)
	{
		if (productCodes[i] == code) {
			return i + 1;
		}
	}
	return -1;
}

string getNowDate() {
	char prefix[50];
	time_t nowTime;
	tm* now;
	time(&nowTime);				//获取系统当前时间戳
	now = localtime(&nowTime);	//将时间戳转化为时间结构体
	sprintf(prefix, "%4d-%02d-%02d %02d:%02d:%02d.000", now->tm_year + 1900, now->tm_mon + 1, now->tm_mday,
		now->tm_hour, now->tm_min, now->tm_sec);
	return prefix;
}

int saveHnswCount = 0;

void saveHnswIndex(string featureValue,int id) {
	try {
		//将数据加入索引
		float features[FEATURE_COUNT];
		char c[FEATURE_COUNT * 10];
		strcpy_s(c, featureValue.c_str());
		split(c, features);

		idx->Addsample(features, id - 1);
		logger->info("cover label is {}", id - 1);
		logger->info("index size is {}", idx->get_cur_element_count());
	}catch(...){
		logger->info("add simple error");
	}
}

void saveDbIndex(CppSQLite3DB& db, const char* code, std::string& featureValue)
{
	db.setBusyTimeout(3000);

	//检测feature_code表达数据
	int size_f = 0;
	CppSQLite3Buffer measure;
	measure.format("select count(*) from feature_code;");
	CppSQLite3Query q = db.execQuery(measure);
	while (!q.eof())
	{
		size_f = q.getIntField(0);
		break;
	}
	q.finalize();

	//查询表单大小,确认是否有未使用的id
	CppSQLite3Buffer measureCount;
	measureCount.format("select count(*) from record_count;");
	CppSQLite3Query q1 = db.execQuery(measureCount);
	int size_r = 0;
	while (!q1.eof())
	{
		size_r = q1.getIntField(0);
		break;
	}
	q1.finalize();

	int lastId = idx->get_cur_element_count();
	logger->info("lastId is {}", lastId);
	if (size_f >= 30000) {
		// 找到时间最小的向量 进行替换
		int id = 0;
		int count = 0;
		// 找到第一个code的商品 替换他 
		CppSQLite3Buffer selectCount;
		if (size_r > 1) {
			//有积存id,取最后一条
			selectCount.format("select id , count from record_count order by id desc limit 1;");
			CppSQLite3Query q = db.execQuery(selectCount);
			while (!q.eof())
			{
				id = q.getIntField(0);
				count = q.getIntField(1);
				break;
			}
			q.finalize();
			logger->info("id:{}", id);
			logger->info("count:{}", count);

			//覆盖删除值
			CppSQLite3Buffer updateFeatureCode;
			updateFeatureCode.format("update feature_code set code = %Q,feature= %Q,update_time = datetime('now','localtime') ,deleted = 0 where id = %d;", code, featureValue.c_str(), count);
			db.execDML(updateFeatureCode);
			logger->info("覆盖成功");

			//删除用掉的id的记录
			CppSQLite3Buffer delCount;
			selectCount.format("delete from record_count where id = %d;", id);
			db.execDML(selectCount);
		}
		else
		{
			//没有积存id,读取标记
			selectCount.format("select count from record_count where id = 1;");
			CppSQLite3Query q = db.execQuery(selectCount);
			while (!q.eof())
			{
				count = q.getIntField(0);
				break;
			}
			q.finalize();

			//判断是否超出上限
			if (count > 30000) {
				CppSQLite3Buffer overDelete;
				overDelete.format("update record_count set count = 1 where id = 1");
				count = 1;
				CppSQLite3Buffer updateCount;
				updateCount.format("update record_count set count = 1 where id = 1");
				db.execDML(updateCount);
			}

			// 直接覆盖
			CppSQLite3Buffer updateFeatureCode;
			updateFeatureCode.format("update feature_code set feature = %Q,code = %Q,update_time = datetime('now','localtime'),deleted = 0 where id = %d", featureValue.c_str(), code, count);
			db.execDML(updateFeatureCode);
		}
		logger->info("count :{}", count);

		// 删除label
		// idx->Removesample(id - 1);
		// 保存索引
		//hnswExecutor->enqueue(saveHnswIndex, string(featureValue), id);
		saveHnswIndex(string(featureValue), count);

		CppSQLite3Buffer updateCount;
		updateCount.format("update record_count set count = count + 1 where id = 1");
		db.execDML(updateCount);
	}
	else {
		// 记录feature 和 商品代码之间的关系 到sqlite 和 faiss索引
		CppSQLite3Buffer insertFeatureCode;	
		insertFeatureCode.format("insert into feature_code(code, feature,update_time,deleted) values (%Q,%Q,datetime('now','localtime'),0);", code, featureValue.c_str());	
		db.execDML(insertFeatureCode);	

	
		int count = db.execScalar("select last_insert_rowid() from feature_code;");	
		logger->info("db add id is {}", count);
		//将数据加入索引
		// 保存索引
		//hnswExecutor->enqueue(saveHnswIndex, string(featureValue), count);
		saveHnswIndex(string(featureValue), count);	
	}
}

void saveDbIndex2(CppSQLite3DB& db, const char* code, std::string& featureValue)
{
	db.setBusyTimeout(3000);
	int lastId = idx->get_cur_element_count();
	logger->info("lastId is {}", lastId);
	if (lastId >= 50000) {
		// 找到时间最小的向量 进行替换
		int id = 0;
		// 找到第一个code的商品 替换他 
		CppSQLite3Buffer selectFeatureCode;
		selectFeatureCode.format("select id,min(update_time) from feature_code;");
		CppSQLite3Query q = db.execQuery(selectFeatureCode);
		while (!q.eof())
		{
			id = q.getIntField(0);
			break;
		}
		q.finalize();
		// 直接覆盖
		CppSQLite3Buffer updateFeatureCode;
		updateFeatureCode.format("update feature_code set feature = %Q,code = %Q,update_time = datetime('now','localtime') where id = %d", featureValue.c_str(), code, id);
		db.execDML(updateFeatureCode);
		// 保存索引
		saveHnswIndex(string(featureValue), id);
	}
	else {
		// 记录feature 和 商品代码之间的关系 到sqlite 和 faiss索引
		CppSQLite3Buffer insertFeatureCode;
		insertFeatureCode.format("insert into feature_code(code, feature,update_time) values (%Q,%Q,datetime('now','localtime'));", code, featureValue.c_str());
		db.execDML(insertFeatureCode);

		float features[FEATURE_COUNT];
		char c[FEATURE_COUNT * 10];
		std::strcpy(c, featureValue.c_str());
		split(c, features);

		int count = db.execScalar("select last_insert_rowid() from feature_code;");
		logger->info("db add id is {}", count);
		// 保存索引
		saveHnswIndex(string(featureValue), count);
	}
}


void sendClientFeatureMessage(const char* productCode, string featureValue) {
	if (s_pclient->HasStarted()) {
		Document d;

		Document::AllocatorType& allocator = d.GetAllocator();

		Value str_val;
		char strBuffer[FEATURE_COUNT * 10];
		int len = sprintf(strBuffer, featureValue.c_str());
		str_val.SetString(strBuffer, len, allocator);

		Pointer("/type").Set(d, 1);
		Pointer("/productCode").Set(d, productCode);
		Pointer("/feature").Set(d, str_val, allocator);
		StringBuffer buffer;
		Writer<StringBuffer> writer(buffer);
		d.Accept(writer);
		logger->info(buffer.GetString());
		s_pclient->Send((const BYTE*)buffer.GetString(), buffer.GetLength());
	}
	else if (s_pserver->HasStarted()) {
		Document d;

		Document::AllocatorType& allocator = d.GetAllocator();

		Value str_val;
		char strBuffer[FEATURE_COUNT * 10];
		int len = sprintf(strBuffer, featureValue.c_str());
		str_val.SetString(strBuffer, len, allocator);

		Pointer("/type").Set(d, 1);
		Pointer("/productCode").Set(d, productCode);
		Pointer("/feature").Set(d, str_val, allocator);
		StringBuffer buffer;
		Writer<StringBuffer> writer(buffer);
		d.Accept(writer);
		logger->info(buffer.GetString());

		for (long i = 0; i < connIds.size(); i++)
		{
			logger->info("client connId is {}", connIds[i]);
			s_pserver->Send(connIds[i], (const BYTE*)buffer.GetString(), buffer.GetLength());
		}
	}
	else {
		// do nothing
	}
}

int getNumByScore(float score) {
	return score >= markDownScoreLimit ? 1 : 0;
}

// 生鲜回传
int doFruitsSetFeedBack(const char* code, char* sessionId, bool hit, char* productName) {
	CppSQLite3DB db;
	db.open(gszFile);
	// 回传记录保存
	CppSQLite3Buffer insertFeedBackRecord;
	insertFeedBackRecord.format("insert into feed_back_record(code, session_id,hit) values (%Q,%Q,%d);", code, sessionId, hit);
	db.execDML(insertFeedBackRecord);
	// 根据sessionId查询对应的打称记录 如果置信度>0.85 那么将商品代码 模型代码的计数器+1 如果计数器>=2 将商品模型的关系记录
	// 首先查询根据sessionId查询product_record 得到模型代码 和 置信度
	if (!db.tableExists("product_record")) {
		logger->info("product_record not found , please check AutoDetect success");
		db.close();
		return -2004;
	}
	int modelCode = 0;
	float score;
	string featureValue;
	string imagePath;
	string productIds;
	string time;
	string realCodes;
	for (int i = 0; i < 5; i++)
	{
		CppSQLite3Buffer selectProductRecord;
		selectProductRecord.format("select model_code,score,feature_value,image_path,product_ids,time,real_codes from product_record where session_id = %Q;", sessionId);
		CppSQLite3Query q = db.execQuery(selectProductRecord);
		while (!q.eof())
		{
			modelCode = q.getIntField(0);
			score = q.getFloatField(1);
			featureValue = q.getStringField(2);
			imagePath = q.getStringField(3);
			productIds = q.getStringField(4);
			time = q.getStringField(5);
			realCodes = q.getStringField(6);
			q.nextRow();
		}
		q.finalize();
		if (!modelCode) {
			logger->info("sessionId not found , wait 200ms, times:{}", i + 1);
			Sleep(200);
			continue;
		}
		else {
			break;
		}
	}
	if (!modelCode) {
		logger->info("sessionId not found after 5 times");
		db.close();
		return -2005;
	}

	if (featureValue.empty())
	{
		return -2005;
	};
	// 首先查询商品模型表是否有该数据
	string productCodes[10];
	char* cIds = const_cast<char*>(productIds.c_str());
	splitString(cIds, productCodes);
	int topNo = getTopNo(productCodes, code);

	string realProductCodes[10];
	char* realIds = const_cast<char*>(realCodes.c_str());
	splitString(realIds, realProductCodes);
	int realTopNo = getTopNo(realProductCodes, code);
	logger->info("sessionId is {},hit top {},real hit top is {}", sessionId, topNo, realTopNo);
	// 打标
	markDown(code, db, modelCode, score);
	//}
	if (!hit|| !realProductCodes[1].empty()) {
		saveDbIndex(db, code, featureValue);
		sendClientFeatureMessage(code, featureValue);
	}
	db.close();

	if (connectNet) {
		Document d;
		string response;
		char uploadUrl[200];
		sprintf(uploadUrl, upload_file_url, dev.c_str(), tenantStr.c_str());
		logger->info("upload url is {},imagePath is {}", uploadUrl, imagePath.c_str());
		int curlCode = CommonTools::upload_file(uploadUrl, imagePath.c_str(), response);
		logger->info("response is {},curl code is {}", UTF_82ASCII(response), curlCode);
		if (curlCode != 0) {
			connectNet = false;
			logger->info("设备未联网不再上传");
			return 0;
		}
		d.Parse(response.c_str());
		int resultCode = d["code"].GetInt();
		if (resultCode == 0) {
			Value& s1 = d["data"]["url"];
			if (s1.IsNull()) {
				logger->info("上传失败");
				return 0;
			}
			string imageUrl = s1.GetString();
			d.Clear();

			Pointer("/recommendTime").Set(d, time);
			Pointer("/productCode").Set(d, code);
			Pointer("/productName").Set(d, productName);
			Pointer("/weightingStartTime").Set(d, getNowDate());
			Pointer("/sessionId").Set(d, sessionId);
			Pointer("/weighImageUrl").Set(d, imageUrl);
			Pointer("/aiType").Set(d, "WINMORE");
			Pointer("/topNo").Set(d, realTopNo);
			Pointer("/identifyCode/code").Set(d, to_string(modelCode));
			Pointer("/identifyCode/accuracy").Set(d, score);
			StringBuffer buffer;
			Writer<StringBuffer> writer(buffer);
			d.Accept(writer);

			char url[100];
			sprintf(url, save_record_url, dev.c_str(), tenantStr.c_str());
			logger->info("url2:{}", url);
			string response2;
			CommonTools::HttpPost(url, buffer.GetString(), response2, 3);
			cout << response2 << endl;
		}
		else {
			logger->info("upload error url is {}, error code is {}", uploadUrl, resultCode);
			connectNet = false;
		}
	}
	return 0;
}

void markDown(const char* code, CppSQLite3DB& db, int modelCode, float score)
{
	// 1.查商品code product_model_counting 是否有记录
	int id;
	string countProductCode;
	int countModelCode = 0;
	int num;
	// 3.根据商品Id查询计数器
	CppSQLite3Buffer selectProductModelCountByProductCode;
	selectProductModelCountByProductCode.format("select id,product_code,model_code,num from product_model_counting where product_code = %Q;", code);
	CppSQLite3Query productModelCountByProductCode = db.execQuery(selectProductModelCountByProductCode);
	while (!productModelCountByProductCode.eof())
	{
		id = productModelCountByProductCode.getIntField(0);
		countProductCode = productModelCountByProductCode.getStringField(1);
		countModelCode = productModelCountByProductCode.getIntField(2);
		num = productModelCountByProductCode.getIntField(3);
		productModelCountByProductCode.nextRow();
	}
	// 2.如果没有记录 判断 top1置信度 >= 0.9 计数器 = 1 置信度< 0.9 计数器 = 0 记录 code modelCode 如果 记录到product_model_counting
	if (!countModelCode) {
		CppSQLite3Buffer insertProductModelCount;
		insertProductModelCount.format("insert into product_model_counting(product_code, model_code, num) values (%Q,%d,%d);", code, modelCode, getNumByScore(score));
		db.execDML(insertProductModelCount);
	}
	else {
		// 3.如果有记录 并且modelCode = top1code 置信度>=0.9 计数器+1 更新
		if (countModelCode == modelCode && score >= markDownScoreLimit) {
			++num;
			CppSQLite3Buffer updateProductModelCountNum;
			updateProductModelCountNum.format("update product_model_counting set num = %d where id = %d", num, id);
			db.execDML(updateProductModelCountNum);
		}
		else if (countModelCode != modelCode && num <= 3) {
			CppSQLite3Buffer updateProductModelCountNum;
			updateProductModelCountNum.format("update product_model_counting set num = %d,model_code = %d where id = %d", getNumByScore(score), modelCode, id);
			db.execDML(updateProductModelCountNum);
		}
		else if (countModelCode != modelCode && num > 3) {
			num = num - 3;
			CppSQLite3Buffer updateProductModelCountNum;
			updateProductModelCountNum.format("update product_model_counting set num = %d,model_code = %d where id = %d", num, modelCode, id);
			db.execDML(updateProductModelCountNum);
		}
		else {
			logger->info("do nothing countModelCode is {} modelCode is {} score is {}", countModelCode, modelCode, score);
		}
	}
}

// 零食回传
int doSnacksSetFeedBack(const char* code, char* sessionId, bool hit, char* productName) {
	CppSQLite3DB db;
	db.open(gszFile);
	// 回传记录保存
	CppSQLite3Buffer insertFeedBackRecord;
	insertFeedBackRecord.format("insert into feed_back_record(code, session_id,hit) values (%Q,%Q,%d);", code, sessionId, hit);
	db.execDML(insertFeedBackRecord);
	// 根据sessionId查询对应的打称记录 如果置信度>0.85 那么将商品代码 模型代码的计数器+1 如果计数器>=2 将商品模型的关系记录
	// 首先查询根据sessionId查询product_record 得到模型代码 和 置信度
	if (!db.tableExists("product_record")) {
		logger->info("product_record not found , please check AutoDetect success");
		return -2004;
	}
	int modelCode = 0;
	float score;
	string featureValue;
	string imagePath;
	string productIds;
	string time;
	string realCodes;
	int realTopNo;
	
	if (!hit|| !realProductCodes[1].empty())
	{
		CppSQLite3Buffer selectProductRecord;
		selectProductRecord.format("select model_code,score,feature_value,image_path,product_ids,time,real_codes from product_record where session_id = %Q;", sessionId);
		CppSQLite3Query q = db.execQuery(selectProductRecord);
		
		while (!q.eof())
		{
			modelCode = q.getIntField(0);
			score = q.getFloatField(1);
			featureValue = q.getStringField(2);
			imagePath = q.getStringField(3);
			productIds = q.getStringField(4);
			time = q.getStringField(5);
			realCodes = q.getStringField(6);
			q.nextRow();
		}
		q.finalize();
		if (featureValue.empty())
		{
			return -2005;
		};

		// 获得topNo
		string productCodes[10];
		char* cIds = const_cast<char*>(productIds.c_str());
		splitString(cIds, productCodes);
		int topNo = getTopNo(productCodes, code);

		string realProductCodes[10];
		char* realIds = const_cast<char*>(realCodes.c_str());
		splitString(realIds, realProductCodes);
		realTopNo = getTopNo(realProductCodes, code);
		logger->info("sessionId is {},hit top {},real hit top is {}", sessionId, topNo, realTopNo);

		saveDbIndex(db, code, featureValue);
		sendClientFeatureMessage(code, featureValue);

		db.close();
	}
	if (connectNet) {
		Document d;
		string response;
		char uploadUrl[200];
		sprintf(uploadUrl, upload_file_url, dev.c_str(), tenantStr.c_str());
		logger->info("upload url is {},imagePath is {}", uploadUrl, (tool_get_dll_path()+imagePath).c_str());
		int resultCode = CommonTools::upload_file(uploadUrl, imagePath.c_str(), response); 
		logger->info("code:{},response:{}", resultCode, UTF_82ASCII(response));
		if (resultCode != 0) {
			connectNet = false;
			logger->info("设备未联网不再上传");
			return 0;
		}
		
		d.Parse(response.c_str()); 
		if (d["code"].GetInt() == 0) {
			Value& s1 = d["data"]["url"];
			if (s1.IsNull()) {
				logger->info("上传失败");
				return 0;
			}
			string imageUrl = s1.GetString();
			d.Clear();

			Pointer("/recommendTime").Set(d, time); 
			Pointer("/productCode").Set(d, code);
			Pointer("/productName").Set(d, productName);
			Pointer("/weightingStartTime").Set(d, getNowDate());
			Pointer("/sessionId").Set(d, sessionId);
			Pointer("/weighImageUrl").Set(d, imageUrl);
			Pointer("/aiType").Set(d, "WINMORE");
			Pointer("/topNo").Set(d, realTopNo);
			Pointer("/identifyCode/code").Set(d, to_string(modelCode));
			Pointer("/identifyCode/accuracy").Set(d, score);
			StringBuffer buffer;
			Writer<StringBuffer> writer(buffer);
			d.Accept(writer); 

			char url[100] = {};
			
			sprintf(url, save_record_url, dev.c_str(), tenantStr.c_str());

			string response2;
			CommonTools::HttpPost(url, buffer.GetString(), response2, 3);
		}
	}
	return 0;
}

// 保存未识别商品
int WMAI_API SetFeedBack(const char* code, char* sessionId, bool hit, char* productName)
{

	string name(productName);
	logger->info("code is {} sessionId is {} hit is {} productName is {}", code, sessionId,hit, UTF_82ASCII(name));
	logger->info("start feed back");
	int result = doSnacksSetFeedBack(code, sessionId, hit, productName);
	logger->info("end feed back");
	return result;
}


int checkTenant(char* tenant) {
	Document d2;
	StringBuffer buffer2;
	Writer<StringBuffer> writer2(buffer2);
	d2.Accept(writer2);

	string response2;
	char tenantUrl[100];
	sprintf(tenantUrl, query_tenant_url, dev.c_str(), tenant);
	logger->info("tenantUrl is {}", tenantUrl);
	CommonTools::HttpGet(tenantUrl, response2, 3);
	logger->info("reponse is {}", UTF_82ASCII(response2));
	d2.Parse(response2.c_str());
	Value& s2 = d2["code"];
	if (s2.GetInt() != 0) {
		logger->info("tenantCode:{} 没有找到", tenant);
		return -4004;
	}
	else {
		bool tenantTrue = d2["data"].GetBool();
		if (!tenantTrue) {
			logger->info("tenantCode:{} 没有找到", tenant);
			return -4004;
		}
	}
	return 0;
}

// 注册pos机
int WMAI_API InitPos(char* tenant, char* posCode, char* snNo) {
	logger->info("{},{},{}", tenant, posCode, snNo);
	posId = posCode;
	int checkTenantCode = checkTenant(tenant);
	if (checkTenantCode != 0) {
		return checkTenantCode;
	}
	//tenantStr = tenant;
	Document d;
	Pointer("/code").Set(d, posCode);
	string mac = "";
	GetMacByGetAdaptersAddresses(mac);
	Pointer("/macAddress").Set(d, mac);
	Pointer("/snNo").Set(d, snNo);
	StringBuffer buffer;
	Writer<StringBuffer> writer(buffer);
	d.Accept(writer);

	char url[100];
	sprintf(url, wmpos_bind_url, dev.c_str(), tenant);
	logger->info("url is {}", url);

	string response;
	CommonTools::HttpPut(url, buffer.GetString(), response, 3);
	logger->info("reponse is {}", UTF_82ASCII(response));
	d.Parse(response.c_str());
	Value& s = d["code"];
	if (s.GetInt() == 0) {
		string wmKeyUrl = d["data"]["wmKeyUrl"].GetString();
		string privateKeyUrl = d["data"]["privateKeyUrl"].GetString();
		posId = d["data"]["id"].GetString();

#if APPOINT_LIBRARY
		string new_dir = string(drive) + dir;
		char wmkey_path[MAX_PATH];
		strcpy(wmkey_path, (new_dir + "wmkey.wm").c_str());
		const char* wmkey = wmkey_path;
		char prikey_path[MAX_PATH];
		strcpy(prikey_path, (new_dir + "prikey.pem").c_str());
		const char* prikey = prikey_path;
#else
		const char* wmkey = "wmkey.wm";
		const char* prikey = "prikey.pem";
#endif // 0
		CommonTools::download_file(wmKeyUrl.c_str(), wmkey);
		CommonTools::download_file(privateKeyUrl.c_str(), prikey);

		tenantStr = tenant;
		if (!tenantStr.empty()) {
			Document d2;
			Pointer("/demo").Set(d2, tenant);
			FILE* fp = fopen(json_tenant_path, "wb");// 非 Windows 平台使用 "w"
			char writeBuffer[65536];
			FileWriteStream os(fp, writeBuffer, sizeof(writeBuffer));
			Writer<FileWriteStream> writer(os);
			d2.Accept(writer);
			fclose(fp);
		}

		Document d3;
		Pointer("/posId").Set(d3, posId);
		FILE* fp2 = fopen(posId_json_path, "wb"); // 非 Windows 平台使用 "w"
		char writeBuffer2[65536];
		FileWriteStream os2(fp2, writeBuffer2, sizeof(writeBuffer2));
		Writer<FileWriteStream> writer2(os2);
		d3.Accept(writer2);
		fclose(fp2);
		return 0;
	}
	else if (s.GetInt() == 65014) {
		logger->info("snCode没有找到{}", snNo);
		return -4001;
	}
	else if (s.GetInt() == 65017) {
		logger->info("该posCode:{},已经绑定过,并且绑定的mac地址和传入的mac地址不一致", posCode);
		return -4002;
	}
	else if (s.GetInt() == 65006) {
		logger->info("此POS的MAC地址:{} 绑定过其它POS机,请联系管理员确认POS机编号!", mac.c_str());
		return -4003;
	}
	else {
		logger->info("initPos failed errorCode: {}", s.GetInt());
		return -2006;
	}
}

// 解绑pos机
int WMAI_API UnbindPos(char* tenant, char* snNo) {
	int checkTenantCode = checkTenant(tenant);
	if (checkTenantCode != 0) {
		return checkTenantCode;
	}

	const char* pwmkey = "pwmkey.wm";
	const char* pprikey = "pprikey.pem";

	remove(pwmkey);
	remove(pprikey);

	string mac;
	GetMacByGetAdaptersAddresses(mac);

	Document d;
	StringBuffer buffer;
	Writer<StringBuffer> writer(buffer);
	d.Accept(writer);

	char url[200];
	sprintf(url, wmpos_unbind_url, dev.c_str(), tenant, mac.c_str() ,snNo);
	logger->info("url is {}", url);

	string response;
	CommonTools::HttpGet(url, response, 3);
	logger->info("reponse is {}", UTF_82ASCII(response));
	d.Parse(response.c_str());
	Value& s = d["code"];
	if (s.GetInt() == 0) {
#if APPOINT_LIBRARY
		string new_dir = string(drive) + dir;
		char wmkey_path[MAX_PATH];
		strcpy(wmkey_path, (new_dir + "wmkey.wm").c_str());
		const char* wmkey = wmkey_path;
		char prikey_path[MAX_PATH];
		strcpy(prikey_path, (new_dir + "prikey.pem").c_str());
		const char* prikey = prikey_path;
#else
		const char* wmkey = "wmkey.wm";
		const char* prikey = "prikey.pem";

#endif // 0

		remove(wmkey);
		remove(prikey);
		authed = false;


		return 0;
	}else if (s.GetInt() == 65014) {
		logger->info("snCode没有找到{}", snNo);
		return -4001;
	}
	else if (s.GetInt() == 65019) {
		logger->info("snCode:{}并未绑定无需解绑", snNo);
		return -4005;
	}
	else if (s.GetInt() == 65017) {
		logger->info("该pos现在的mac地址和服务器记录的mac地址不一致 无法解绑");
		return -4006;
	}
	else {
		logger->info("initPos failed errorCode: {}", s.GetInt());
		return -2006;
	}
}

void getNowMat() {

	int i = 1;
	while (cap.isOpened())
	{
		cap >> frame_pipe;
		if (frame_pipe.empty())
		{
			logger->info("frame_pipe empty");
			continue;
		}
		WaitForSingleObject(PhotoMutex, INFINITE);
		if (i == 1) {
			frame_one = frame_pipe.clone();            //读取当前帧
			++i;
		}
		else if (i == 2) {
			frame_two = frame_pipe.clone();            //读取当前帧
			++i;
		}
		else if (i == 3) {
			frame_thr = frame_pipe.clone();            //读取当前帧
			i = 1;
		}
		ReleaseMutex(PhotoMutex);
		if (cv::waitKey(30)) { 	 //延时30ms		
			continue;
		}
	}
	return;
}

// 设置摄像头序号
int WMAI_API SetCameraId(int num) {
	static int i=0;
	logger->info("开始打开摄像头:{}",i);
	cameraNum = num;
	Document d;

	logger->info("x:{},y:{},width:{},height:{}", x, y, width, height);
	if (width && height) {
		Pointer("/x").Set(d, x);
		Pointer("/y").Set(d, y);
		Pointer("/width").Set(d, width);
		Pointer("/height").Set(d, height);
		Pointer("/ai_x").Set(d, ai_x);
		Pointer("/ai_y").Set(d, ai_y);
		Pointer("/ai_width").Set(d, ai_width);
		Pointer("/ai_height").Set(d, ai_height);
	}
	Pointer("/cameraNum").Set(d, cameraNum);

	FILE* fp = fopen(json_path, "wb"); // 非 Windows 平台使用 "w"
	char writeBuffer[65536];
	FileWriteStream os(fp, writeBuffer, sizeof(writeBuffer));
	Writer<FileWriteStream> writer(os);
	d.Accept(writer);
	fclose(fp);
	PhotoMutex =CreateMutex(NULL, FALSE, (LPCWSTR)"PhotoMutex");

	if (!cap.isOpened()) {
		cap.set(cv::CAP_PROP_FRAME_WIDTH, 640);//宽度 
		cap.set(cv::CAP_PROP_FRAME_HEIGHT, 480);//高度
		cap.set(cv::CAP_PROP_FPS, 30);
		logger->info("准备打开摄像头序号{}", cameraNum);
		cap.open(cameraNum, CAP_DSHOW);
		std::thread t(getNowMat);
		t.detach();
	}
	Sleep(3000);
	logger->info("结束打开摄像头");
	return 0;
}

int WMAI_API GetScaleSetting(int &x1, int &y1, int &width1, int &height1) {
	x1 = x;
	y1 = y;
	width1 = width;
	height1 = height;
	return 0;
}

int WMAI_API SaveScaleSetting(int x1, int y1, int width1, int height1) {
	if (!(width1 && height1))
	{
		logger->info("coordinate error");
		return -2008;
	}

	if (x1 + width1 > 640 || y1 + height1 > 480) {
		logger->info("Scale not match 640*480 x={},y={},w={},h={}", x1, y1, width1, height1);
		return -2008;
	}
	x = x1;
	y = y1;
	width = width1;
	height = height1;

	Document d;

	Pointer("/x").Set(d, x);
	Pointer("/y").Set(d, y);
	Pointer("/width").Set(d, width);
	Pointer("/height").Set(d, height);
	Pointer("/ai_x").Set(d, ai_x);
	Pointer("/ai_y").Set(d, ai_y);
	Pointer("/ai_width").Set(d, ai_width);
	Pointer("/ai_height").Set(d, ai_height);
	Pointer("/cameraNum").Set(d, cameraNum);

	FILE* fp = fopen(json_path, "wb"); // 非 Windows 平台使用 "w"
	char writeBuffer[65536];
	FileWriteStream os(fp, writeBuffer, sizeof(writeBuffer));
	Writer<FileWriteStream> writer(os);
	d.Accept(writer);
	fclose(fp);
	return 0;
}

int WMAI_API GetScaleBitmap(char* rawPath, char* cropPath) {
	getScaleBitmap(rawPath, cropPath, true);
	return 0;
}

int WMAI_API SetNoRecommend(const char* code) {
	logger->info("开始清除商品:{}的学习记录", code);
	//计时开始
	double start_time = clock();
	CppSQLite3DB db;
	db.open(gszFile);

	//记录需要删除的id
	int id = 0;
	CppSQLite3Buffer selectFeatureCode;
	selectFeatureCode.format("select id from feature_code where code = %Q;", code);
	CppSQLite3Query q = db.execQuery(selectFeatureCode);
	while (!q.eof())
	{
		id = q.getIntField(0);

		CppSQLite3Buffer insert;
		insert.format("insert into record_count(count) values (%d)", id);
		db.execDML(insert);

		q.nextRow();
	}
	q.finalize();

	// 清除数据库
	if (db.tableExists("feature_code")) {
		CppSQLite3Buffer deleteSql;
		deleteSql.format("update feature_code set deleted = 1 where code = %Q;", code);
		db.execDML(deleteSql);
}
	if (db.tableExists("product_model")) {
		CppSQLite3Buffer deleteSql;
		deleteSql.format("delete from product_model where product_code = %Q", code);
		db.execDML(deleteSql);
	}

	// 清除map
	// productModelMap.erase(code);

	// 重置索引
	delete idx;
	idx = new libhnsw::Index(FEATURE_COUNT, DATABASE_SIZE, libhnsw::BRUTE_FORCE_KNN_CF);
	if (db.tableExists("feature_code")) {
		CppSQLite3Query q = db.execQuery("select id,feature from feature_code where deleted = 0");
		while (!q.eof())
		{
			float features[FEATURE_COUNT];
			int id = q.getIntField(0);
			const char* sql_feature = q.getStringField(1);
			char* c = const_cast<char*>(sql_feature);
			split(c, features);
			idx->Addsample(features, id - 1);
			q.nextRow();
		}
		q.finalize();
	}
	db.close();
	double end_time = clock();
	logger->info("all elapsed {}ms\n", end_time - start_time);
	return 0;
}

int WMAI_API ExportData(char* path)
{
#if APPOINT_LIBRARY
	string new_dir = string(drive) + dir;
	char data_path[128] = {};
	std::strcpy(data_path, (new_dir+"data-copy").c_str());

	ifstream in(gszFile, ios::binary);
	ofstream out(data_path, ios::binary);
	if (!in) {
		printf("open file error");
		return -2007;
	}
	if (!out) {
		printf("open file error");
		return -2007;
	}
	char flush[8];
	while (!in.eof()) {
		in.read(flush, 8);
		out.write(flush, in.gcount());
	}
	in.close();
	strcpy(path, data_path);
#else
	ifstream in(gszFile, ios::binary);
	ofstream out("data-copy", ios::binary);
	if (!in) {
		printf("open file error");
		return -2007;
	}
	if (!out) {
		printf("open file error");
		return -2007;
	}
	char flush[8];
	while (!in.eof()) {
		in.read(flush, 8);
		out.write(flush, in.gcount());
	}
	in.close();
	std::strcpy(path, "data-copy");
#endif // APPOINT_LIBRARY
	return 0;
}

int WMAI_API ImportData(char* path) {
	// 首先删除文件
	remove("featurexYz.db");
	ifstream in(path, ios::binary);
	ofstream out("featurexYz.db", ios::binary);
	if (!in) {
		printf("open file error");
		return -2007;
	}
	if (!out) {
		printf("open file error");
		return -2007;
	}
	char flush[8];
	while (!in.eof()) {
		in.read(flush, 8);
		out.write(flush, in.gcount());
	}
	in.close();
	out.close();
	// 删除索引文件
	return 0;
}

// 1 代表索引添加 2 代表商品模型关系添加 
//{\"type\":1,\"feature\":\"\",\"productCode\":\"\",\"modelCode\": 1}
void saveOtherMachineIndex(const BYTE* pData, int length) {
	string a = (char*)pData;
	string data = a.substr(0, length);
	logger->info("data is {}", data.c_str());
	CppSQLite3DB db;
	db.open(gszFile);

	StringStream s(data.c_str());
	Document d;
	d.ParseStream(s);
	// 1 代表索引添加 2 代表商品模型关系添加
	int type = d["type"].GetInt();
	if (type == 1) {
		string productCode = d["productCode"].GetString();
		string featureValue = d["feature"].GetString();
		if (!productCode.empty() && !featureValue.empty()) {
			saveDbIndex2(db, productCode.c_str(), featureValue);
		}
		else {
			logger->info("productCode or else null");
		}
	}
	else if (type == 2) {
		Value& items = d["modelCodes"];
		assert(a.IsArray());
		for (SizeType i = 0; i < items.Size(); i++) {
			string productCode = items[i]["productCode"].GetString();
			int modelCode = items[i]["modelCode"].GetInt();
			if (!productCode.empty() && modelCode != 0) {
				CppSQLite3Buffer selectProductModel;
				selectProductModel.format("select count(1) from product_model where product_code = %Q or model_code = %d;", productCode.c_str(), modelCode);
				int productModelCount = db.execScalar(selectProductModel);
				if (productModelCount < 1) {
					// 记录商品模型关系
					CppSQLite3Buffer insertProductModel;
					insertProductModel.format("insert into product_model(product_code, model_code) values (%Q,%d);", productCode.c_str(), modelCode);
					db.execDML(insertProductModel);
				}
			}
			else {
				logger->info("productCode or modelCode null");
			}
		}
	}
	db.close();
}

EnHandleResult ServerListenerImpl::OnHandShake(ITcpServer* pSender, CONNID dwConnID)
{
	logger->info("接收到客户端{}的握手请求,建立连接", dwConnID);
	connIds.push_back(dwConnID);
	return EnHandleResult();
}

EnHandleResult ServerListenerImpl::OnClose(ITcpServer* pSender, CONNID dwConnID, EnSocketOperation enOperation, int iErrorCode)
{
	logger->info("接收到客户端:{}的关闭请求", dwConnID);

	vector<CONNID>::iterator iter = connIds.begin();
	while (iter != connIds.end())
	{
		if (*iter == dwConnID)
		{
			iter = connIds.erase(iter);
		}
		else
		{
			iter++;
		}
	}
	return EnHandleResult();
}

EnHandleResult ServerListenerImpl::OnReceive(ITcpServer* pSender, CONNID dwConnID, const BYTE* pData, int iLength)
{
	logger->info("服务端Ip:{},接收到客户端: {}的回复", realIp, dwConnID);
	saveOtherMachineIndex(pData, iLength);
	//DWORD count;
	//CONNID connIdAll[maxConnectionCount];
	//bool success = pSender->GetAllConnectionIDs(connIdAll, count);
	//if (!success) {
	//}
	for (long i = 0; i < connIds.size(); i++)
	{
		if (connIds[i] == dwConnID) {
			// 忽略传递过来的Id
			continue;
		}
		pSender->Send(connIds[i], pData, iLength);
	}
	/*else {
		logger->info("获取客户端Id失败");
	}*/
	return EnHandleResult();
}

EnHandleResult ClientListenerImpl::OnReceive(ITcpClient* pSender, CONNID dwConnID, const BYTE* pData, int iLength)
{
	logger->info("客户端ip:{},接收到服务端: {}的消息", realIp, dwConnID);
	saveOtherMachineIndex(pData, iLength);
	return EnHandleResult();
}

/***********************************************************UDP****************************************************************************/

EnHandleResult UdpNodeListenerImpl::OnReceive(IUdpNode* pSender, LPCTSTR lpszRemoteAddress, USHORT usRemotePort, const BYTE* pData, int iLength)
{
	char* ip = TCHAR2char(lpszRemoteAddress);
	if (strncmp(ip, realIp, 16) == 0) {
		// 忽略本身传递的广播
		return EnHandleResult();
	}
	// 如果接到新机器的创建消息
	// 创建客户端连接新机器的服务
	logger->info("接收到地址{}的 UDP NODE 的消息:{},开始注册TCP客户端", ip, pData);
	// 创建新的客户端 连接新的tcp服务端
	createClient(lpszRemoteAddress);
	// 关闭之前的tcp服务端
	if (s_pserver->HasStarted()) {
		logger->info("关闭ip:{}的tcp服务", realIp);
		s_pserver->Stop();
	}
	return EnHandleResult();
}

EnHandleResult UdpNodeListenerImpl::OnError(IUdpNode* pSender, EnSocketOperation enOperation, int iErrorCode, LPCTSTR lpszRemoteAddress, USHORT usRemotePort, const BYTE* pData, int iLength)
{
	return EnHandleResult();
}

int WMAI_API ClearTrainedData() {
	if (exists_file(gszFile)) {
		int res = remove(gszFile);
		if (res != 0) {
			logger->info("hnswIndex dose not exist\n");
		}
		else {
			logger->info("remove gszfile successed\n");
		}
	}
	delete idx;
	idx = nullptr;
	return 0;
}

int WMAI_API QuickLearn(char* code, char* path) {
	cv::Mat m = cv::imread(path, 1);
	if (m.empty())
	{
		logger->info("cv::imread failed\n");
		return -2002;
	}
	string productcode[10];
	std::vector<float> cls_scores;
	float rhs[FEATURE_COUNT];
	double start_time = ncnn::get_current_time();
	int ret = detect_squeezenet(m, cls_scores, rhs);

	// 将图片向量进行逗号组装
	char featureValue[FEATURE_COUNT * 10] = { };
	for (int i = 0; i < FEATURE_COUNT; ++i)
	{
		sprintf(featureValue + strlen(featureValue), "%f,", rhs[i]);
	}
	string tmp2 = featureValue;
	CppSQLite3DB db;
	db.open(gszFile);
	saveDbIndex(db, code, tmp2);
	db.close();

	std::vector<std::pair<float, int>>res = idx->Search(rhs, 1);
	for (int j = 0; j < res.size(); j++)
	{
		std::pair<float, int> r = res[j];
		int number = r.second;
		logger->info("index label is {}",number);
	}
	return 0;
}



int WMAI_API WMrelease() {
	if (cap.isOpened()) {
		cap.release();
	}
	if (udp_node->HasStarted()) {
		udp_node->Stop();
	}
	if (s_pclient->HasStarted()) {
		s_pclient->Stop();
	}
	if (s_pserver->HasStarted()) {
		s_pserver->Stop();
	}
	delete idx;
	//delete hnswExecutor;
	param_in.end_thread = true;
	RETRI_Release(&handle);
	init_finish = false;
	return 0;
}

int WMAI_API Close() {
	logger->info("开始释放资源");
	
	if (rest_in>0) {
		CppSQLite3DB db;
		db.open(gszFile);
		//检测表单,此表单为计数

		db.execDML("CREATE TABLE IF NOT EXISTS video_count (id integer PRIMARY KEY AUTOINCREMENT, count_in integer NOT NULL)");

		int pre;
		CppSQLite3Buffer selectvideocount;
		selectvideocount.format("select count(*) from video_count");
		CppSQLite3Query q = db.execQuery(selectvideocount);

		while (!q.eof())
		{
			pre = q.getIntField(0);
			break;
		}
		q.finalize();

		if (pre == 0)
		{
			CppSQLite3Buffer insertvideocount;
			insertvideocount.format("insert into video_count(count_in) values (%d);", rest_in);
			db.execDML(insertvideocount);
		}
		else
		{
			CppSQLite3Buffer updatevideocount;
			updatevideocount.format("update video_count set count_in=%d where id = 1", rest_in);
			db.execDML(updatevideocount);
		}

		db.close();
	}
	
	DeleteDirectory(dir_in.c_str()); 
	DeleteDirectory(dir_out.c_str());  
	



	if (cap.isOpened()) {
		cap.release(); 
	}
	
	if (udp_node->HasStarted()) {
		udp_node->Stop(); 
	}
	
	if (s_pclient->HasStarted()) {
		s_pclient->Stop(); 
	}
	
	if (s_pserver->HasStarted()) {
		s_pserver->Stop(); 
	}
	
	if (idx != nullptr)
	{
		
		delete idx; 
		idx = nullptr;
	}
	
	param_in.end_thread = true; 
	RETRI_Release(&handle); 
	init_finish = false; 
	logger->info("释放资源结束");
	
	return 0;
}

int FoodInitParam(FOOD_PARAM_DATA* param_in)
{
	
	_mkdir("data");
	
	// step1:先配置下数据源
	param_in->back_mat_path = "data/back.jpg";
	param_in->debug_mode = false; 

	// step2:配置roi
	param_in->has_roi = true; 
	cv::Rect roi;
	roi.x = ai_x;
	roi.y = ai_y;
	roi.width = ai_width; 
	roi.height = ai_height;
	param_in->roi = roi; 




	//step4.裁剪下当前帧照片,保存当前图片
	param_in->back =cv::imread(param_in->back_mat_path);

	if (param_in->back.empty()) {
		param_in->back = frame_pipe.clone(); 
		if (param_in->back.empty()) {

			logger->info("图片是空的,\n");
			return -2002;

		}

		if (roi.area() != 0)
		{
			param_in->back = param_in->back(roi);
		}
		

		//获取空盘图向量
		RETRI_INPUT empty_img;
		empty_img.img = param_in->back.clone();
		RETRI_OUTPUT clsretri_output;
		int hr = RETRI_Process(empty_img, &clsretri_output, handle);
		if (hr != 0)
		{
			logger->info("元芒图形加速库推理失败!error code:{0:x}\n", hr);
			return -2008;
		}
		for (int i = 0; i < FEATURE_COUNT; i++)
		{
			feature_empty[i] = clsretri_output.feat[i];
		}


		char pathName[128] = {};
		sprintf(pathName, "%s", param_in->back_mat_path.c_str());
		logger->info("要保存的文件路径[{}]\n", pathName);
		cv::imwrite(pathName, param_in->back);
	}

	else {
		//获取空盘图向量
		RETRI_INPUT empty_img;
		empty_img.img = param_in->back.clone();
		RETRI_OUTPUT clsretri_output;
		int hr = RETRI_Process(empty_img, &clsretri_output, handle);
		if (hr != 0)
		{
			logger->info("元芒图形加速库推理失败!error code:{o:x}\n", hr);
			return -2008;
		}
		for (int i = 0; i < FEATURE_COUNT; i++)
		{
			feature_empty[i] = clsretri_output.feat[i];
		}
	}


	// step3:
	param_in->end_thread = false;
	param_in->max_in_frame_queue = 6;
	param_in->queue_max = 5;
	//step(-1):是否启用调试
	param_in->debug_mode = debug;

	libvideopipe::INIT_INFO video_pipe;
	//video_pipe.gpu_enable = drive_ai;
	video_pipe.gpu_enable =true;
	video_pipe.lat3d_enable = true;
	param_in->mvp = new libvideopipe::MotionVideoPipe;
	param_in->mvp->Init(video_pipe);

	solve_cost.push_back(67);
	return 0;
}

UINT64 food_get_current_time_millis()
{
	using namespace std;

	timeb now;
	ftime(&now);
	std::stringstream milliStream;
	// 由于毫秒数不一定是三位数,故设置宽度为3,前面补0
	milliStream << setw(3) << setfill('0') << right << now.millitm;

	stringstream secStream;
	secStream << now.time;
	string timeStr(secStream.str());
	timeStr.append(milliStream.str());

	UINT64 timeLong;
	stringstream transStream(timeStr);
	transStream >> timeLong;

	return timeLong;
}


DWORD WINAPI FoodRealtimeCaptureImage(LPVOID params)
{
	FOOD_PARAM_DATA* param_in = (FOOD_PARAM_DATA*)params;
	if (!param_in)
	{
		logger->info("FoodRealtimeCaptureImage:param_in NULL!\n", GetCurrentThreadId());
		return 0;
	}

	while (!(param_in->end_thread))
	{
		//float start = clock();
		Sleep(average_cost());
		// step1:获取当前帧

		Mat img = frame_pipe.clone();

		// step2:判断当前帧是否为空
		if (img.empty())
		{
			logger->info("PID[{}]ERROR: get img error!\n", GetCurrentThreadId());
			param_in->end_thread = true;
			continue;
		}

		// step3:配置FRAME_INFO
		VIDEO_FRAME_INFO frame;
		frame.timestamp = food_get_current_time_millis();
		if (param_in->roi.area() == 0)
			param_in->has_roi = false;
		if (param_in->has_roi) {
			frame.img = img(param_in->roi);
		}
		else {
			frame.img = img;
		}
		// step4:压入一个frame
		param_in->frame_in.push(frame);
		//step5:如果上一帧未处理,则丢帧
		if (param_in->frame_in.size() > param_in->max_in_frame_queue)
		{
			logger->info("图片堆积");
			WaitForSingleObject(param_in->hMutex, INFINITE);
			param_in->frame_in.pop();  // 删除一个原图
			ReleaseMutex(param_in->hMutex);
		}
		//float end = clock();
		//logger->info("get image cost:{}", end - start); 
	}
	return 1;
}



void trace_detect_process()
{
	
	result.state =1;
	logger->info("result.state is {}", result.state);

	if (video_detect)
	{
		char rawPath[128];                 
		char cropPath[128];   
		logger->info("video_time is {}",video_time);
		Sleep(video_time);

		//cv::Mat m = getScaleBitmap(rawPath, cropPath, false);     
		//if (m.empty())//获取图片失败编写日志
		//{
		//	logger->info("cv::imread failed\n");
		//	return;
		//}
		int ret = MatDetect(result.productCodes, result.sessionId);

		//int ret = AutoDetect(result.productCodes, result.sessionId);
		if (ret != 0)
		{
			logger->info("Detect error:{}", ret);          
			return;
		}
	}
	else
	{
		std::strcpy(result.productCodes, "");                  
		std::strcpy(result.sessionId, "");                      
	}
	if (param_in.result_queue.size()< param_in.queue_max) 
	{

		DETECT_RESULT tem = result;   
		param_in.result_queue.push(tem);  
	}
}

void trace_judge_process()
{
	Sleep(100);        
	char path[128] = {};
	char row_path[128] = {};     
	GetScaleBitmap(row_path, path); 
	param_in.detect_image_path = string(path); 
}

void save_in_image()
{
	
	if (rest_in > VIDEOCOINTIN)
	{	                                              
		return;
	}
	
	logger->info("rest_in is{}",rest_in);          
	
	std::string dir = "video_image_in";              
	std::string time = std::to_string(food_get_current_time_millis());          
	dir_in = dir;
	_mkdir(dir.c_str());                                  
	_mkdir((dir + "/" + time).c_str());                 

	int i = 1;
	const char* path = "%s/%s/%s-%d.jpg";          
	char name2[128] = {};                  
	char name3[128] = {};                  
	char name4[128] = {};             
	vector<string>res;
	string pre1;                      
	string pre2;
	string pre3;
	sprintf(name2, path, dir.c_str(), time.c_str(), time.c_str(), i++); 
	//图片命名
	pre1 = pre1 + time.c_str() + "wmAce"+"-" + "1" + ".jpg";
	res.push_back(pre1);
	//储存图片
	cv::imwrite(name2, param_in.back);
	sprintf(name3, path, dir.c_str(), time.c_str(), time.c_str(), i++); 
	pre2 = pre2 + time.c_str() +"wmAce"+ "-" + "2" + ".jpg";
	res.push_back(pre2);
	cv::imwrite(name3, param_in.frame_out[3].img);
	sprintf(name4, path, dir.c_str(), time.c_str(), time.c_str(), i++); 
	pre3 = pre3 + time.c_str() + "wmAce" + "-" + "3" + ".jpg";
	res.push_back(pre3);
	cv::imwrite(name4, param_in.frame_out[4].img);

	char in[10] = "IN";
	res.push_back(in);
	
	
	//上传照片
	Document d;
	string response;
	char uploadUrl[200];
	string tenantStrs;
	string posId_in;
	//获取租户信息
	if (tenantStr.size() != 0&& json_tenant_path)
	{
		tenantStrs = tenantStrs + tenantStr;
		posId_in = posId_in + posId;
	}
	else
	{
		tenantStrs = tenantStrs + '0';
		posId_in = '0';
	}
	sprintf(uploadUrl, upload_file1_url, dev.c_str(), tenantStrs.c_str(), posId_in.c_str());
	logger->info("upload url is {}", uploadUrl);
	res.push_back(tenantStrs);
	res.push_back(posId_in);
	int curlCode = CommonTools::upload_file1(uploadUrl,name2,name3,name4,res,response);
		
	string rrr= UTF_82ASCII(response);
	logger->info("response is {}",rrr);
	if (curlCode != 0) {
		logger->info("上传图片失败,退出");
		return;
	}
	
	rest_in++;
	
	
}

void save_out_image()
{
	

	if (rest_out > VIDEOCOINTIN)
	{
		return;
	}
	std::string dir = "video_image_out";
	std::string time = std::to_string(food_get_current_time_millis());
	dir_out = dir;
	_mkdir(dir.c_str());
	_mkdir((dir + "/" + time).c_str());

	int i = 1;
	const char* path = "%s/%s/%s-%d.jpg";
	char name5[128] = {};
	char name6[128] = {};
	char name7[128] = {};
	vector<string>res_out;
	string buf1, buf2, buf3;
	sprintf(name5, path, dir.c_str(), time.c_str(), time.c_str(), i++);
	buf1 = buf1 + time.c_str() + "wmAce" + "-" + "1" + ".jpg";
	res_out.push_back(buf1);
	cv::imwrite(name5, param_in.back);
	sprintf(name6, path, dir.c_str(), time.c_str(), time.c_str(), i++);
	buf2 = buf2 + time.c_str() + "wmAce" + "-" + "2"+".jpg";
	res_out.push_back(buf2);
	cv::imwrite(name6, param_in.frame_out[3].img);
	sprintf(name7, path, dir.c_str(), time.c_str(), time.c_str(), i++);
	buf3 = buf3 + time.c_str() + "wmAce" + "-" + "3" + ".jpg";
	res_out.push_back(buf3);
	cv::imwrite(name7, param_in.frame_out[4].img);
	string out = "OUT";
	res_out.push_back(out);

	Document d;
	string response;
	char uploadUrl[200];
	string tenantStrs;
	string posId_out;
	if (tenantStr.size() != 0 && json_tenant_path)
	{
		tenantStrs = tenantStrs + tenantStr;
		posId_out = posId_out + posId;
	}
	else
	{
		tenantStrs = tenantStrs + '0';
		posId_out = '0';
	}
	sprintf(uploadUrl, upload_file1_url, dev.c_str(), tenantStrs.c_str(), posId_out.c_str());
	logger->info("upload url is {}", uploadUrl);
	res_out.push_back(tenantStrs);
	res_out.push_back(posId_out);
	int curlCode = CommonTools::upload_file1(uploadUrl, name5, name6, name7,res_out, response);
	string rrr = UTF_82ASCII(response);
	logger->info("response is {}", rrr);
		
	if (curlCode != 0) {
		logger->info("上传图片失败,退出");
		return;
	}

	rest_out++;
	
}

void video_trace_result_callback(const char* data, int len, void* user)
{
	// AI识别
	libvideopipe::MOTION_VIDEOPIPE_OUTPUT* output = (libvideopipe::MOTION_VIDEOPIPE_OUTPUT*)data;

	if (output->status == libvideopipe::MOTION_STATUS_TYPES::MOTION_IN)
	{
		logger->info("检测到物品进入");  
		if (mode == FRESH_DETECT)
		{
			thread t(trace_detect_process);       
			t.detach();                                
			if (rest_count < VIDEOCOUNT)
			{
				thread t_(save_in_image);                     
				t_.detach();                                    
			}
		}
		else if (mode == PREVENT_LOSS)
		{
			thread t(trace_judge_process);                
			t.detach();                                  
			/*thread t_(save_in_image);
			t_.detach();
			logger->info("没有进行存图!");*/
			if (param_in.result_queue.size() < param_in.queue_max)
			{
				DETECT_RESULT tem = result;
				tem.state = 1;
				logger->info("tem.state is {}", tem.state);
				param_in.result_queue.push(tem);
			}
		}
	}
	if (output->status == libvideopipe::MOTION_STATUS_TYPES::MOTION_OUT)
	{
		logger->info("检测到物品离开");
		if (rest_count < VIDEOCOUNT)
		{
			thread t_(save_out_image);
			t_.detach();
		}
		if (param_in.result_queue.size() < param_in.queue_max)
		{
			DETECT_RESULT tem = result;                   
			tem.state = 0;          
			logger->info("tem.state is {}", tem.state);
			param_in.result_queue.push(tem);              
		}
	}
}

DWORD WINAPI FoodDetectTrace(LPVOID params)
{

	auto start = clock();
	FOOD_PARAM_DATA* param_in = (FOOD_PARAM_DATA*)params;
	if (param_in == NULL)
	{
		logger->info("FoodDetectTrace:param_in NULL!\n");
		return 0;
	}
	
	// step0:初始化一个跟踪器
	param_in->mvp->SetROI(param_in->roi);  
	param_in->mvp->SetMarkMat(param_in->back);  
	param_in->mvp->SetResultCallback(video_trace_result_callback, param_in);  
	
	g_video_init = true;
	logger->info("videopipe init end");

	while (!param_in->end_thread)
	{
		
		// step1:判断帧数据是否为空
		if (param_in->frame_in.empty())
		{
			Sleep(30);
			in_image = false;
			continue;
		}
		
		//处理图像开始
		in_image = true;
		float start = clock();

		// step2:获取输入数据
		WaitForSingleObject(param_in->hMutex, INFINITE);
		VIDEO_FRAME_INFO frame;
		frame = param_in->frame_in.front();
		param_in->frame_in.pop();  // 删除一个原图
		ReleaseMutex(param_in->hMutex);

		param_in->frame_out.push_back(frame);
		if (param_in->frame_out.size() > param_in->max_in_frame_queue)
		{
			param_in->frame_out.erase(param_in->frame_out.begin());
		}


		// step3:设置video input
		libvideopipe::MOTION_VIDEOPIPE_INPUT video_input;
		video_input.img = frame.img.clone();
		video_input.pts = frame.timestamp;
		video_input.debug_mode = false;

		/*imshow("frame.img", video_input.img);
		cv::waitKey(1);*/
		 //step4:是否显示调试的视频
		if (param_in->debug_mode == true)
		{
			imshow("frame.img", video_input.img);
			cv::waitKey(1);
		}
		

		// step5:开始Trace;
		//float _start = clock();
		int rt = param_in->mvp->FeedFrame(video_input);
		//float _end = clock();
		//logger->info("FeedFrame costs {} ms \n",_end - _start);
		if (rt != 0)
		{
			logger->info("Trace return error code:{o:x}\n", rt);
		}

		cv::waitKey(3);
		float end = clock();
		solve_cost.push_back(end-start);
		//logger->info("dev solve cost:{}", end - start);

	}
	return 0;
}

void videoPipeDetect() 
{

	logger->info("videopipe init start");
	
	FoodInitParam(&param_in);
	
	// step1: 开线程测试
	const int MAX_THREADS = 2;
	HANDLE hThread[MAX_THREADS];
	hThread[0] = CreateThread(NULL, 0, FoodRealtimeCaptureImage, &param_in, 0, NULL);
	hThread[1] = CreateThread(NULL, 0, FoodDetectTrace, &param_in, 0, NULL);
	param_in.hMutex = CreateMutex(NULL, FALSE, (LPCWSTR)"screen");
	bool getthread = false;
	

	// step2: Wait until all threads have terminated.
	WaitForMultipleObjects(MAX_THREADS, hThread, TRUE, INFINITE);
	for (int i = 0; i < MAX_THREADS; i++)
	{
		CloseHandle(hThread[i]);
	}
	ReleaseMutex(param_in.hMutex);
	delete param_in.mvp;
	g_video_init = false;
	//g_video_init = true;
	logger->info("process end");

}

//识别函数主控
int WMAI_API VideopipeDetect() {
	thread t(videoPipeDetect);
	t.detach();
	return 0;
}

int WMAI_API GetQueueSize()
{
	if (re_ret)
	{
		return 0;
	}
	int ret = param_in.result_queue.size();
	if (ret > 0) {
		re_ret = true;
	}
	return ret;
}

int WMAI_API RealTimeDetect(char* code, char* sessionId, int &state)
{
	if (param_in.result_queue.size() <= 0) {
		logger->info("error get result");
		return -5009;
	}
	logger->info("productCodes:{},sessionId:{},state:{}", param_in.result_queue.front().productCodes, param_in.result_queue.front().sessionId, param_in.result_queue.front().state);
	std::strcpy(code, param_in.result_queue.front().productCodes);
	std::strcpy(sessionId, param_in.result_queue.front().sessionId);
	state = param_in.result_queue.front().state;
	param_in.result_queue.pop();
	re_ret = false;
	return 0;
}

void uploadImage() {
	Document d;
	string response;
	char uploadUrl[200];
	sprintf(uploadUrl, upload_file_url, dev.c_str(), tenantStr.c_str());
	logger->info("upload url is {},imagePath is {}", uploadUrl, param_in.back_mat_path.c_str());
	int curlCode = CommonTools::upload_file(uploadUrl, param_in.back_mat_path.c_str(), response);
	//logger->info("response is {},curl code is {}", response.c_str(), curlCode);
	if (curlCode != 0) {
		logger->info("上传图片失败,退出");
		return;
	}
	d.Parse(response.c_str());
	int resultCode = d["code"].GetInt(); 
	if (resultCode == 0) {
		Value& s1 = d["data"]["url"]; 
		if (s1.IsNull()) {
			logger->info("上传失败");
			return;
		}
		string imageUrl = s1.GetString(); 
		d.Clear(); 

		Pointer("/recommendTime").Set(d, 0); 
		Pointer("/weightingStartTime").Set(d, getNowDate()); 
		Pointer("/weighImageUrl").Set(d, imageUrl); 
		Pointer("/aiType").Set(d, "WINMORE"); 
		Pointer("/identifyCode/code").Set(d, to_string(99999)); 
		Pointer("/identifyCode/accuracy").Set(d, 0.001); 
		StringBuffer buffer; 
		Writer<StringBuffer> writer(buffer); 
		d.Accept(writer); 

		char url[100]; 
		sprintf(url, save_record_url, dev.c_str(), tenantStr.c_str()); 
		logger->info("url:{}",url);

		string response2;
		CommonTools::HttpPost(url, buffer.GetString(), response2, 3);
		//logger->info("response2:{}", response2);
	}
	else {
		logger->info("upload error url is {}, error code is {}", uploadUrl, resultCode);
	}
}

void  Capture_emp_pic()
{
	double empty_save_beign = clock();
	//打开数据库
	CppSQLite3DB db;
	db.open(gszFile);
	int empty_pre;

	CppSQLite3Buffer select_emp;
	select_emp.format("select count(*) from empty_pan_feature");
	CppSQLite3Query select_q = db.execQuery(select_emp);
	while (!select_q.eof())
	{
		empty_pre = select_q.getIntField(0);
		break;
	}
	select_q.finalize();
	_mkdir(empty_pic.c_str());

	int i = 1;
	while (i < 31)
	{
		float feature_em[128];
		//开始获取空盘图
		WaitForSingleObject(param_in.hMutex, INFINITE);
		Sleep(67);
		Mat img_empty = frame_pipe.clone();
		ReleaseMutex(param_in.hMutex);
		//判断当前帧是否为空
		if (img_empty.empty())
		{
			logger->info("图像为空");
			continue;
		}
		img_empty = img_empty(param_in.roi);

		//存图	
		char raw_pic_Name[128] = {};
		char photoPath[MAX_PATH];
		std::strcpy(photoPath, (empty_pic + "\\%d.jpg").c_str());
		sprintf(raw_pic_Name, photoPath, i);
		cv::imwrite(raw_pic_Name, img_empty);

		//图片转向量
		std::vector<float> cls_scores;
		float rhs[FEATURE_COUNT];
		int ret = detect_squeezenet(img_empty, cls_scores, rhs);



		char featureValue[FEATURE_COUNT * 10] = { };
		for (int i = 0; i < FEATURE_COUNT; ++i)
		{
			sprintf(featureValue + strlen(featureValue), "%f,", rhs[i]);
		}
		if (empty_pre < 30)
		{
			CppSQLite3Buffer insert;
			insert.format("insert into empty_pan_feature(feature) values (%Q)", featureValue);
			db.execDML(insert);
		}
		else
		{
			CppSQLite3Buffer update_emp;
			update_emp.format("update empty_pan_feature set feature=%Q where id=%d;", featureValue, i);
			db.execDML(update_emp);
		}
		i++;

	}
	db.close();
	double empty_save_end = clock();

	logger->info("储存空盘图的时间为:{}", empty_save_end - empty_save_beign);
	if (i >= 31)
	{
		return;
	}
}

int WMAI_API ChangeBackPicture() 
{	//step1.获取图像
	logger->info("改变空盘图像开始");
	cv::Mat img = frame_pipe.clone();
	if (img.empty()) {
		logger->info("图片是空的,\n");
		return -2002;
	}

	//step2.裁剪下当前帧照片,保存当前图片
	cv::Rect roi;
	roi.x = ai_x ;
	roi.y = ai_y ;
	roi.width = ai_width;
	roi.height = ai_height;
	if (roi.area() == 0)
	{
		logger->info("裁剪错误");
		return -2002;

	}
	param_in.roi = roi;
	param_in.has_roi = true;
	cv::Mat imgRoi = img(param_in.roi);

	
	////更新空盘图的向量
	//RETRI_INPUT empty_img;
	//empty_img.img = imgRoi.clone();
	//RETRI_OUTPUT clsretri_output;
	//int hr = RETRI_Process(empty_img, &clsretri_output, handle);
	//if (hr != 0)
	//{
	//	logger->info("元芒图形加速库推理失败!error code:{0:x}\n", hr);
	//	return -2008;
	//}

	//for (int i = 0; i < FEATURE_COUNT; i++)
	//{
	//	feature_empty[i] = clsretri_output.feat[i];
	//}

	char pathName[128] = {};
	sprintf(pathName, "%s", param_in.back_mat_path.c_str());
	cv::imwrite(pathName, imgRoi);

	//step3.置换
	param_in.back = imgRoi;
	param_in.mvp->SetMarkMat(imgRoi);
	logger->info("改变空盘图像结束");
	DeleteDirectory(empty_pic.c_str());

	//step4.上传图片
	thread t(uploadImage);
	t.detach();
	//捕获空盘图
	thread t_emp(Capture_emp_pic);
	t_emp.detach();
	//double empty_save_beign = clock();

	//int empty_pre=0;
	//
	////打开数据库
	//CppSQLite3DB db;
	//db.open(gszFile);
	//
	//
	//CppSQLite3Buffer select_emp;
	//select_emp.format("select count(*) from empty_pan_feature");
	//CppSQLite3Query select_q = db.execQuery(select_emp);
	//while (!select_q.eof())
	//{
	//	empty_pre = select_q.getIntField(0);
	//	break;
	//}
	//select_q.finalize();
	//_mkdir(empty_pic.c_str());
	//
	//int i = 1;
	//while (i < 31)
	//{
	//	float feature_em[128];
	//	//开始获取空盘图
	//	WaitForSingleObject(param_in.hMutex, INFINITE);
	//	Sleep(67);
	//	Mat img_empty = frame_pipe.clone(); 
	//	ReleaseMutex(param_in.hMutex);
	//	//判断当前帧是否为空
	//	if (img_empty.empty())
	//	{
	//		logger->info("图像为空");
	//		continue;
	//	}
	//	img_empty = img_empty(roi);

	//	//存图	
	//	char raw_pic_Name[128] = {};
	//	char photoPath[MAX_PATH];
	//	std::strcpy(photoPath, (empty_pic + "\\%d.jpg").c_str());
	//	sprintf(raw_pic_Name, photoPath, i);
	//	cv::imwrite(raw_pic_Name, img_empty);
	//		
	//	//图片转向量
	//	std::vector<float> cls_scores;
	//	float rhs[FEATURE_COUNT];
	//	int ret = detect_squeezenet(img_empty, cls_scores, rhs);



	//	char featureValue[FEATURE_COUNT * 10] = { };
	//	for (int i = 0; i < FEATURE_COUNT; ++i)
	//	{
	//		sprintf(featureValue + strlen(featureValue), "%f,", rhs[i]);
	//	}
	//	if (empty_pre<=30)
	//	{
	//		CppSQLite3Buffer insert;
	//		insert.format("insert into empty_pan_feature(feature) values (%Q)", featureValue);
	//		db.execDML(insert);
	//	}
	//	else
	//	{
	//		CppSQLite3Buffer update_emp;
	//		update_emp.format("update empty_pan_feature set feature=%Q where id=%d;", featureValue,i);
	//		db.execDML(update_emp);
	//	}
	//	i++;
	//		
	//}
	//
	//db.close();
	//double empty_save_end = clock(); 

	//logger->info("储存空盘图的时间为:{}", empty_save_end-empty_save_beign);
	return 0;
}


int WMAI_API Probation()
{
	string mac;
	GetMacByGetAdaptersAddresses(mac);

	Document d;
	StringBuffer buffer;
	Writer<StringBuffer> writer(buffer);
	d.Accept(writer);

	char url[200];
	sprintf(url, wmpos_probation_url, dev.c_str(), mac.c_str());
	logger->info("url is {}", url);

	string response;
	CommonTools::HttpGet(url, response, 3);
	logger->info("reponse is {}", UTF_82ASCII(response));
	d.Parse(response.c_str());
	Value& s = d["code"];
	if (s.GetInt() == 0) {


#if APPOINT_LIBRARY
		string new_dir = string(drive) + dir;
		char wmkey_path[MAX_PATH];
		strcpy(wmkey_path, (new_dir + "pwmkey.wm").c_str());
		const char* wmkey = wmkey_path;
		char prikey_path[MAX_PATH];
		strcpy(prikey_path, (new_dir + "pprikey.pem").c_str());
		const char* prikey = prikey_path;
#else
		string wmkey = "pwmkey.wm";
		string prikey =  "pprikey.pem";
#endif // 0
		string wmKeyUrl = d["data"]["wmKeyUrl"].GetString();
		string privateKeyUrl = d["data"]["privateKeyUrl"].GetString();
		//const char* wmkey = "pwmkey.wm";
		//const char* prikey = "pprikey.pem";
		CommonTools::download_file(wmKeyUrl.c_str(), wmkey.c_str());
		CommonTools::download_file(privateKeyUrl.c_str(), prikey.c_str());
		if (json_tenant_path)
		{
			remove(posId_json_path);
			remove(json_tenant_path);
		}
		return 0;
	}
	else if (s.GetInt() == 65021) {
		logger->info("试用码已过期");
		return -4010;
	}
	else{
		logger->info("probation failed errorCode: {}", s.GetInt());
		return -4011;
	}
}

int WMAI_API WriteLog(const char* txt)
{
	logger->info("node:{}",txt);
	return 0;
}

int WMAI_API SetLogoCoordinate(int x1,int y1,int width1,int height1)
{
	if (!(width1 && height1))
	{
		logger->info("coordinate error");
		return -2008;
	}

	ai_x = x1;
	ai_y = y1;
	ai_width = width1;
	ai_height = height1;

	Document d;
	Pointer("/x").Set(d, x);
	Pointer("/y").Set(d, y);
	Pointer("/width").Set(d, width);
	Pointer("/height").Set(d, height);
	Pointer("/ai_x").Set(d, ai_x);
	Pointer("/ai_y").Set(d, ai_y);
	Pointer("/ai_width").Set(d, ai_width);
	Pointer("/ai_height").Set(d, ai_height);
	Pointer("/cameraNum").Set(d, cameraNum);

	FILE* fp = fopen(json_path, "wb"); // 非 Windows 平台使用 "w"
	char *writeBuffer = (char *)calloc(65536,sizeof(char));
	FileWriteStream os(fp, writeBuffer, sizeof(writeBuffer));
	Writer<FileWriteStream> writer(os);
	d.Accept(writer);
	fclose(fp);
	free(writeBuffer);
	return 0;
}

int WMAI_API InputPluToDetect(char *code,char *sessionId)
{
	if (g_video_init && init_finish) 
	{
		param_in.scan = true;
		char detected_code[100] = {};
		char path[128] = {};
		doSnacksAutoDetect(detected_code, sessionId, path);
		param_in.detect_image_path = string(path);
		if (detected_code[0] == '\0')
		{
			logger->info("没有检测出该商品");;
			return -6001;
		}
		if (strstr(detected_code, code) == nullptr)
		{
			logger->info("没有检测出该商品");
			return -6001;
		}
		return 0;
	}
	else
	{
		return -6002;
	}

}

int WMAI_API GetDetectImage(char* path)
{
	logger->info("image_path:{}", param_in.detect_image_path);
	std::strcpy(path,param_in.detect_image_path.c_str());
	logger->info("path:{}", path);
	return 0;
}

bool WMAI_API JudgeScanTheCode()
{
	bool ret = param_in.scan;
	param_in.scan = false;
	return ret;
}

//bool WMAI_API getVideopipeInit()
//{
//	return g_video_init;
//}



bool WMAI_API GetInitResults()
{
	return g_video_init;
}

bool WMAI_API ReturnCameraState()
{
	return cap.isOpened();
}


//纠错
int WMAI_API FreshChangeError(const char* code)
{


	//检测表单是否存在
	CppSQLite3DB db;                                    
	db.open(gszFile);                                  
	if (!db.tableExists("feature_code")) {
		return 0;                                       
	}

	int r = reassion[code];                      
	int number = r; 
	float feature_a[FEATURE_COUNT]; 

	CppSQLite3Buffer selectFeatureCode; 
	selectFeatureCode.format("select code,feature,id from feature_code where id = %d;", number); 
	CppSQLite3Query q = db.execQuery(selectFeatureCode); 

	//接收数据搜索结果
	int id;                                                                       
	std::string sql_code;                                                         
	while (!q.eof())
	{
		sql_code = q.getStringField(0);                                           
		const char* sql_feature = q.getStringField(1);
		id = q.getIntField(2);                                                   
		char* feature_c = const_cast<char*>(sql_feature); 
		split(feature_c, feature_a); 
		q.nextRow();
	}
	q.finalize();

	//根据返回的plu查询结果
	std::vector<std::pair<float, int>>res = idx->Search(feature_a, 10); 
	for (unsigned int j = 0; j < res.size(); j++)
	{

		//索引结果
		std::pair<float, int> f = res[j];                                   
		logger->info("res[{}]={} {}\n", j, f.first, f.second);              

		//idx的id与数据库id有 1 的偏差
		int number = f.second + 1;
		float feature_f[FEATURE_COUNT];

		CppSQLite3Buffer selectFeatureCode;                                
		selectFeatureCode.format("select code,feature,id from feature_code where id = %d;", number);
		CppSQLite3Query q = db.execQuery(selectFeatureCode);                

		//接收数据搜索结果
		int id; 
		std::string sql_code;
		
		while (!q.eof())
		{
			sql_code = q.getStringField(0);
			const char* sql_feature = q.fieldValue(1);                      
			id = q.getIntField(2);															
			char* feature_c = const_cast<char*>(sql_feature);                
			split(feature_c, feature_f);                 
			q.nextRow();
		}
		q.finalize();

		float similarity = calculSimilar(feature_a, feature_f, FEATURE_COUNT);        
                                                                                                                                                        
		if (sql_code == string(code) && similarity > ERROR_SIMILARITY)
		{
			//删除相似度高的数据
			CppSQLite3Buffer deleted_sql;
			deleted_sql.format("update feature_code set deleted = 1, update_time = datetime('now','localtime') where id = %d", number);
			db.execDML(deleted_sql);
			//将删除的数据加入record_count表中
			CppSQLite3Buffer insertrecord;
			insertrecord.format("insert into record_count(count) values (%d)", number);
			db.execDML(insertrecord);
			idx->Removesample(f.second);
			q.finalize();
		}
	}

	reassion.clear();                                                       
	db.close();                                                              
	return 0;
}



//自动配图
int WMAI_API SelectByName(char* name , char* code, char* image)
{

	if (!authed) {
		authed = authorize();
		if (!authed) {
			logger->info("验证失败");
			return -2001;
		};
	}
	string utf_name(name);
	logger->info("input name{}",UTF_82ASCII(utf_name));

	Document d;
	d.SetObject();
	Document::AllocatorType& a = d.GetAllocator();


	Value array(kArrayType);
	Value o1(kObjectType);


	Value v1(name, a);
	Value v2(code, a);

	o1.AddMember("productName", v1, a);
	o1.AddMember("productNumber", v2, a);
	array.PushBack(o1, a);

	d.AddMember("productList", array, d.GetAllocator());

	StringBuffer buffer;
	Writer<StringBuffer> writer(buffer);
	d.Accept(writer);
	string buffer1 = buffer.GetString();
	string buffer2 = UTF_82ASCII(buffer1);

	char uploadUrl[200];
	string tenant3;

	if (tenantStr.size() != 0)
	{
		tenant3 = tenant3 + tenantStr;
	}
	else if (tenantStr.size() == 0)
	{
		tenant3 = tenant3 + '0';
	}

	sprintf(uploadUrl, pictured_json_url, dev.c_str(), tenant3.c_str());
	string response;
	logger->info("uploadUrl:{}", uploadUrl);
	CommonTools::HttpPost(uploadUrl, buffer.GetString(), response, 3);
	logger->info("reponse is {}", UTF_82ASCII(response));

	d.Parse(response.c_str());
	Value& s = d["code"];
	if (s.GetInt() == 0) {
#if 0
		string out;
		for (int i = 0; i < response.size() - 1; i++)
		{
			if (response[i] == 'h' && response[i + 1] == 't')
			{
				for (int j = i; j < response.size(); j++)
				{
					if (response[j] == '"')
					{
						break;
					}
					out += response[j];
				}
			}
		}

		if (out.size() <= 0)
		{
			logger->info("该名称没有找到图片");
			return -1;
		}

		//string  out1 = UTF_82ASCII(out);
		strcpy(image, out.c_str());
#else
		Value& pro_list = d["data"]["productList"].GetArray();

		if (pro_list.Empty())
		{
			logger->info("该名称没有找到图片");
			return -1;
		}

		string iup = pro_list[0]["matchProductUrl"].GetString();

		strcpy(image, iup.c_str());

#endif
	}
	else
	{
		logger->info("匹配图片失败");
		return -1;
	}
	return 0;
}


int WMAI_API SetVideoDetect(int ch)
{
	video_detect = ch == 0 ? false : true;
	return 0;
}

int WMAI_API LrregularCrop(int x1, int y1, int x2, int y2, int x3, int y3, int x4, int y4)
{
	mask = Mat::zeros(480,640,CV_8UC1);
	vector<vector<Point2i>> contours;
	vector<Point2i>urelpoints;
	// 角点排序
	Point2f verticess[4];
	Point2f tempPoint;
	verticess[0] = Point2f(x1, y1);
	verticess[1] = Point2f(x2, y2);
	verticess[2] = Point2f(x3, y3);
	verticess[3] = Point2f(x4, y4);
	// 角点排序

// 按x轴坐标从小到大排序
	for (int i = 0; i < 3; i++)
	{
		for (int j = i + 1; j < 4; j++)
		{
			if (verticess[i].x > verticess[j].x)
			{
				tempPoint = verticess[i];
				verticess[i] = verticess[j];
				verticess[j] = tempPoint;
			}
		}
	}
	int min_x = verticess[0].x;
	int max_x = verticess[3].x;

	int min_y1 = verticess[0].y;
	int min_y2 = verticess[2].y;
	int max_y1 = verticess[1].y;
	int max_y2 = verticess[3].y;
	// 按纵坐标排序,依次排为(): 0左上角   1左下角   2右上角   3右下角
	if (verticess[0].y > verticess[1].y)
	{
		tempPoint = verticess[0];
		verticess[0] = verticess[1];
		verticess[1] = tempPoint;

		int tem = max_y1;
		max_y1 = min_y1;
		min_y1 = tem;
	}
	if (verticess[2].y > verticess[3].y)
	{
		tempPoint = verticess[2];
		verticess[2] = verticess[3];
		verticess[3] = tempPoint;

		int tem = max_y2;
		max_y2 = min_y2;
		min_y2 = tem;
	}
	int min_y = min_y1 < min_y2 ? min_y1 : min_y2;
	int max_y = max_y1 > max_y2 ? max_y1 : max_y2;
	//cout << min_y << max_y << endl;
	if (verticess[0].y > verticess[3].y)
	{

		urelpoints.push_back(verticess[0]);
		urelpoints.push_back(verticess[3]);
		urelpoints.push_back(verticess[2]);
		urelpoints.push_back(verticess[1]);
	}
	else
	{
		urelpoints.push_back(verticess[1]);
		urelpoints.push_back(verticess[3]);
		urelpoints.push_back(verticess[2]);
		urelpoints.push_back(verticess[0]);
	}

	contours.push_back(urelpoints);


	// 绘图
	cv::drawContours(mask, contours, 0, Scalar::all(255), -1);

	_mkdir("masks");
	imwrite("masks\\mask.jpg", mask);

	x = min_x;
	y = min_y;
	width = max_x - min_x;
	height = max_y - min_y;

	Document d;
	Pointer("/x").Set(d, x);
	Pointer("/y").Set(d, y);
	Pointer("/width").Set(d, width);
	Pointer("/height").Set(d, height);
	Pointer("/ai_x").Set(d, ai_x);
	Pointer("/ai_y").Set(d, ai_y);
	Pointer("/ai_width").Set(d, ai_width);
	Pointer("/ai_height").Set(d, ai_height);
	Pointer("/cameraNum").Set(d, cameraNum);

	FILE* fp = fopen(json_path, "wb"); // 非 Windows 平台使用 "w"
	char writeBuffer[200];
	FileWriteStream os(fp, writeBuffer, sizeof(writeBuffer));
	Writer<FileWriteStream> writer(os);
	d.Accept(writer);
	fclose(fp);
	return 0;
}