#pragma once
#include <cstdio>
#include <cstring>
#include <cassert>
#include <vector>
#include <list>
#include <string>
#include <map>
#include <iterator>
#include <algorithm>
#include <regex>
#include <thread>
#include <mutex>
#ifdef WIN32
#include <io.h>
#include <fcntl.h>
#endif
//Comments:

namespace TASKBUS{
#define TB_SUBJECT_CMD 0xffffffffu
	//数据专题包头结构体，1字节序对齐
	//Datahead structure with pack = 1
#pragma pack(push,1)
	struct subject_package_header{
		//Always be 0x3C,0x5A,0x7E,0x69。
		unsigned char  prefix[4];
		unsigned int subject_id;
		unsigned int path_id;
		unsigned int data_length;
	};
#pragma pack(pop)


	/**
	  *以下部分为具体实现。The following sections are the implementation
	  * -----------------------------------------------------------------------
	  * */
	/*!
	 * \brief init_client A application should call initclient first.
	 */
	inline void init_client()
	{
#ifdef WIN32
#if  _MSC_VER >=1200
		_setmode(_fileno(stdout),O_BINARY);
		_setmode(_fileno(stdin),O_BINARY);
#else
		setmode(fileno(stdout), O_BINARY);
		setmode(fileno(stdin), O_BINARY);
#endif
#endif
	}

	/*!
	 * \brief lendian 返回大小端
	 * \return
	 */
	inline bool lendian()
	{
		static const short testv = 0x0102;
		static auto testp = reinterpret_cast<const unsigned char *>(&testv);
		static const bool littled = testp[0]==0x02;
		return littled;
	}

	template <typename T>
	inline T cvendian(const T & v, bool tolittle)
	{
		if (lendian()==tolittle)
			return v;
		const size_t sz_len = sizeof(T);
		const unsigned char * p = reinterpret_cast<const unsigned char *>(&v);
		T res;
		unsigned char * q = reinterpret_cast<unsigned char *>(&res);
		for (size_t i=0;i<sz_len;++i)
			q[i] = p[sz_len-1-i];
		return res;
	}

	/*!
	 * \brief push_subject 向标准输出stdout写入一个包
	 * \param subject_id	专题ID
	 * \param path_id		通道ID
	 * \param data_length	数据长度
	 * \param dataptr		数据地址
	 * \param pMtx			可选的互斥量，用于多线程
	 */
	inline void push_subject(
			const unsigned int subject_id,
			const unsigned int path_id,
			const unsigned int data_length,
			const unsigned char   *dataptr,
			std::mutex * pMtx = nullptr
			)
	{
		const unsigned char  prefix[4] = { 0x3C,0x5A,0x7E,0x69};
		if (pMtx) pMtx->lock();
		fwrite(prefix,sizeof(char),4,stdout);
		fwrite(&subject_id,sizeof(subject_id),1,stdout);
		fwrite(&path_id,sizeof(path_id),1,stdout);
		fwrite(&data_length,sizeof(data_length),1,stdout);
		fwrite(dataptr,sizeof(unsigned char),data_length,stdout);
		fflush (stdout);
		if (pMtx) pMtx->unlock();
	}
	//推送专题数据
	inline void push_subject(
			const unsigned int subject_id,
			const unsigned int path_id,
			const char   *dataptr,
			std::mutex * pMtx = nullptr
			)
	{
		const unsigned char  prefix[4] = { 0x3C,0x5A,0x7E,0x69};
		if (pMtx) pMtx->lock();
		fwrite(prefix,sizeof(char),4,stdout);
		fwrite(&subject_id,sizeof(subject_id),1,stdout);
		fwrite(&path_id,sizeof(path_id),1,stdout);
		const unsigned int lenstr = static_cast<unsigned int>(strlen(dataptr)+1);
		fwrite(&lenstr,sizeof(lenstr),1,stdout);
		fwrite(dataptr,sizeof(unsigned char),lenstr,stdout);
		fflush (stdout);
		if (pMtx) pMtx->unlock();
	}
	//推送专题数据
	inline void push_subject(
			const subject_package_header header,
			const unsigned char   *dataptr,
			std::mutex * pMtx = nullptr
			)
	{
		assert(header.prefix[0]==0x3C&&header.prefix[1]==0x5A&&header.prefix[2]==0x7E&&header.prefix[3]==0x69);
		if (pMtx) pMtx->lock();
		fwrite(&header,sizeof(header),1,stdout);
		fwrite(dataptr,sizeof(unsigned char),header.data_length,stdout);
		fflush (stdout);
		if (pMtx) pMtx->unlock();


	}
	inline void push_subject(
			const unsigned char   *allptr,
			const unsigned int totalLength,
			std::mutex * pMtx = nullptr
			)
	{
		assert(allptr[0]==0x3C&&allptr[1]==0x5A&&allptr[2]==0x7E&&allptr[3]==0x69);
		if (pMtx) pMtx->lock();
		fwrite(allptr,sizeof(unsigned char),totalLength,stdout);
		fflush (stdout);
		if (pMtx) pMtx->unlock();
	}
	//接收专题数据
	inline std::vector<unsigned char> pull_subject(
			subject_package_header * header
			)
	{
		//读取缓存
		static const size_t batchdeal = 4096;
		std::vector<unsigned char> buf_data;
		//包头
		memset(header,0,sizeof(subject_package_header));
		fread(header,sizeof(subject_package_header),1,stdin);

		if(header->prefix[0]==0x3C&&header->prefix[1]==0x5A&&header->prefix[2]==0x7E&&header->prefix[3]==0x69)
		{
			//数据
			const size_t groups = header->data_length / batchdeal;
			unsigned char buf[batchdeal];
			for (size_t i=0;i<groups;++i)
			{
				fread(buf,1,batchdeal,stdin);
				std::copy(buf,buf+batchdeal, std::back_inserter( buf_data ));
			}
			if (header->data_length % batchdeal)
			{
				fread(buf,1,header->data_length % batchdeal,stdin);
				std::copy(buf,buf+header->data_length % batchdeal, std::back_inserter( buf_data ));
			}
		}
		return buf_data;
	}
	//用于方便操作指令的函数
	//是否为控制指令
	inline bool is_control_subject(const subject_package_header & header)
	{
		if(header.prefix[0]==0x3C&&header.prefix[1]==0x5A&&header.prefix[2]==0x7E&&header.prefix[3]==0x69)
			return (header.subject_id == TB_SUBJECT_CMD)?true:false;
		return false;
	}
	//提取控制命令
	inline std::string control_subject(const subject_package_header & header, const std::vector<unsigned char> & package)
	{
		std::string str;
		if(header.prefix[0]==0x3C&&header.prefix[1]==0x5A&&header.prefix[2]==0x7E&&header.prefix[3]==0x69)
		{
			if (header.data_length==package.size())
			{
				const size_t sz = package.size();
				for (size_t i=0;i<sz;++i)
					str.push_back((char)package[i]);
			}
		}
		return str;
	}

	inline bool is_valid_header(const subject_package_header & header)
	{
		if(header.prefix[0]==0x3C&&header.prefix[1]==0x5A&&header.prefix[2]==0x7E&&header.prefix[3]==0x69)
			return true;
		return false;
	}

	//返回控制信令专题
	inline unsigned int control_subect_id()
	{
		return TB_SUBJECT_CMD;
	}
	/** Debug the modile.
	*/
	inline std::vector<std::string> debug(const char * logpath, FILE ** old_stdin, FILE ** old_stdout)
	{
		std::vector<std::string> cmdline;
		std::string strpath = std::string(logpath);
		std::string fm_stderr = strpath + "/stderr.txt";
		std::string fm_stdin = strpath + "/stdin.dat";
		std::string fm_stdout = strpath + "/stdout.dat.debug";
#if  _MSC_VER >=1200
		if (old_stdin)
			freopen_s(old_stdin, fm_stdin.c_str(), "rb", stdin);
		else
		{
			FILE * olds = nullptr;
			freopen_s(&olds,fm_stdin.c_str(), "rb", stdin);
		}

		if (old_stdout)
			freopen_s(old_stdout, fm_stdout.c_str(), "wb", stdout);
		else
		{
			FILE * olds = nullptr;
			freopen_s(&olds,fm_stdout.c_str(), "wb", stdout);
		}

		//解析命令行
		FILE * fpErr = nullptr;
		fopen_s(&fpErr,fm_stderr.c_str(), "r");
#else
		if (old_stdin)
			*old_stdin = freopen(fm_stdin.c_str(),"rb",stdin);
		else
			freopen(fm_stdin.c_str(),"rb",stdin);

		if (old_stdout)
			*old_stdout = freopen(fm_stdout.c_str(),"wb",stdout);
		else
			freopen(fm_stdout.c_str(),"wb",stdout);

		//解析命令行
		FILE * fpErr = fopen(fm_stderr.c_str(),"r");

#endif
		if (fpErr)
		{
			char buf[65536];
			if (fgets(buf,65536,fpErr))
			{
				const int argc = atoi(buf);
				for (int i=0;i<argc;++i)
				{
					if (fgets(buf,65536,fpErr))
					{
						while(strlen(buf)>0)
						{
							if ( buf[strlen(buf)-1]<=32&&buf[strlen(buf)-1]>0)
								buf[strlen(buf)-1] = 0;
							else
								break;
						}
						if (strlen(buf)<=0)
							continue;
						char * swim = buf;
						while (*swim<=32 && *swim>0)
							++swim;
						if (strlen(swim)<=0)
							continue;
						cmdline.push_back(std::string(swim));
					}
					else
						break;
				}

			}
			fclose(fpErr);
		}
		return cmdline;

	}

	/*!
	 * \brief trim_elems trim all spaces of  a string.
	 * \param text string to be trimmed.
	 * \return trimmed result
	 */
	inline std::string trim_elems(const std::string &  text)
	{
		std::string result(text);
		if(!result.empty())
		{
			result.erase(0, result.find_first_not_of(" \n\r\t"));
			result.erase(result.find_last_not_of(" \n\r\t") + 1);
		}
		return result;
	}

	/*!
	 * \brief string_to_map convert string cmd to map
	 * \param s string cmd, key=value paires splitted by ';'
	 * \return map
	 */
	inline std::map<std::string, std::string> string_to_map(const std::string & s)
	{
		using namespace std;
		std::map<std::string, std::string> res;
		regex regex_set{"([;])"};
		regex regex_sep{"([=])"};
		sregex_token_iterator it{s.begin(), s.end(), regex_set, -1};
		list<string> words{it, {}};

		for_each(words.begin(),words.end(),[&regex_sep,&res](string v){
			sregex_token_iterator itp {v.begin(), v.end(), regex_sep, -1};
			list<string> paras{itp, {}};
			string key , value;
			if (paras.size())
				key = trim_elems(*paras.begin());
			else
				return;
			paras.pop_front();
			if (paras.size())
				value = trim_elems(*paras.begin());
			res[key] = value;
		});
		return res;
	}

	inline std::map<std::string, std::string> ctrlpackage_to_map(const std::vector<unsigned char> & s)
	{
		using namespace std;
		std::string str;
		copy(s.begin(),s.end(),back_inserter(str));
		return string_to_map(str);
	}


	/*!
	 * \brief map_to_string convert map to key=value paires
	 * \param s map
	 * \return string cmd, key=value paires splitted by ';'
	 */
	inline std::string map_to_string(const std::map<std::string, std::string> & s)
	{
		using namespace std;
		string strv;
		for (auto p = s.begin(); p != s.end(); ++p)
		{
			strv+=(*p).first;
			strv+="=";
			strv+=(*p).second;
			strv+=";";
		}
		return strv;
	}

	inline std::vector<unsigned char> map_to_ctrlpackage(const std::map<std::string, std::string> & s)
	{
		std::string str = map_to_string(s);
		std::vector<unsigned char> v;
		copy(str.begin(),str.end(),std::back_inserter(v));
		v.push_back(0);
		return v;
	}
	/*!
	 * \brief annouce_handle 通过广播，公告某个参数可被全局配置
	 * \param source	本公告的来源。可以自定义，只要确保与配置指令的一致性即可。
	 * \param destin	一般填“0”，表示向所有模块实例广播
	 * \param handle	参数的名称（句柄名称），如频率、增益
	 * \param value		当前取值
	 * \param label		显示在界面上的友好信息。如果不写，handle会被显示
	 * \param type		类型，可以为空，则表示字符串。
	 * \param range		取值范围：用于指定spinbox的范围
	 * \param step		步进：用于指定spinbox的步进
	 * \param decimal	小数位数。
	 * \param pMtx		用于多线程stdout时的互斥量
	 */
	inline void annouce_handle(
		const std::string & source,
		const std::string & destin,
		const std::string & handle,
		const std::string & value,
		const std::string & label = "",
		const std::string & type = "",
		const std::string & range = "",
		const std::string & step = "",
		const std::string & decimal = "",
		std::mutex * pMtx = nullptr
			)
	{
		std::map<std::string, std::string> mp;
		mp["source"] = source;
		mp["function"] = "handle_annouce";
		mp["destin"] = destin;
		mp["handle"] = handle;
		mp["value"] = value;
		if (label.size())	mp["label"] = label;
		if (type.size())	mp["type"] = type;
		if (range.size())	mp["range"] = range;
		if (range.size())	mp["step"] = step;
		if (range.size())	mp["decimal"] = decimal;
		std::vector<unsigned char> v = map_to_ctrlpackage(mp);
		push_subject(TB_SUBJECT_CMD,0,v.size(),v.data(),pMtx);
	}
	/*!
	 * \brief set_handle	设置刷新功能参数的值
	 * \param source
	 * \param destin	如果destin是0，则是用于刷新界面显示。如果destin是annouce_handle的source，则是配置参数。
	 * \param handle	参数的名称（句柄名称），如频率、增益
	 * \param value		当前取值
	 * \param pMtx		用于多线程stdout时的互斥量
	 */
	inline void set_handle(
		const std::string & source,		//发起参数刷新的来源。注意要和annouce_handle的source一致
		const std::string & destin,		//配置目的。这个目的
		const std::string & handle,
		const std::string & value,
		std::mutex * pMtx = nullptr
			)
	{
		std::map<std::string, std::string> mp;
		mp["source"] = source;
		mp["function"] = "handle_set";
		mp["destin"] = destin;
		mp["handle"] = handle;
		mp["value"] = value;
		std::vector<unsigned char> v = map_to_ctrlpackage(mp);
		push_subject(TB_SUBJECT_CMD,0,v.size(),v.data(),pMtx);
	}

}

