#include "pch.h"

CManagerExtension::CManagerExtension()
	: m_manager(nullptr)
	, m_manager_pumping(nullptr)
	, m_run(false)
{
}

CManagerExtension::~CManagerExtension()
{
	Shutdown();
}

bool CManagerExtension::Initialize()
{
	Shutdown();
	//---    
	MTAPIRES retcode = MT_RET_ERROR;
	UINT     version = 0;
	//--- Manage Factory init
	if ((retcode = m_factory.Initialize()) != MT_RET_OK)
	{
		LogOut(tools::logger::LVFATAL, "ManagerExtension: loading manager API  failed [%u]\n", retcode);
		return false;
	}
	//--- check Manager API version
	if ((retcode = m_factory.Version(version)) != MT_RET_OK)
	{
		LogOut(tools::logger::LVFATAL, "ManagerExtension: getting version failed [%u]\n", retcode);
		return false;
	}
	//--- show version  
	wprintf(L"ManagerExtension: Manager API version %u has been loaded\n", version);
	if (version < MTManagerAPIVersion)
	{
		LogOut(tools::logger::LVFATAL, "ManagerExtension: wrong Manager API version, version %u required\n", MTManagerAPIVersion);
		return false;
	}
	//--- create manager interface
	if ((retcode = m_factory.CreateManager(MTManagerAPIVersion, &m_manager)) != MT_RET_OK)
	{
		LogOut(tools::logger::LVFATAL, "ManagerExtension: creating manager interface failed [%u]\n", retcode);
		return false;
	}
	if ((retcode = m_factory.CreateManager(MTManagerAPIVersion, &m_manager_pumping)) != MT_RET_OK)
	{
		LogOut(tools::logger::LVFATAL, "ManagerExtension: creating manager interface failed [%u]\n", retcode);
		return false;
	}
	//if ((retcode = m_factory.CreateAdmin(MTManagerAPIVersion, &m_admin)) != MT_RET_OK)
	//{
	//	wprintf_s(L"AministratorExtension: creating admin interface failed [%u]\n", retcode);
	//}
	if ((retcode = m_manager->Subscribe(this)) != MT_RET_OK)
	{
		LogOut(tools::logger::LVFATAL, "ManagerExtension: manager subscribe failed [%u]\n", retcode);
		return false;
	}
	if ((retcode = m_manager_pumping->Subscribe(this)) != MT_RET_OK)
	{
		LogOut(tools::logger::LVFATAL, "ManagerExtension: manager subscribe failed [%u]\n", retcode);
		return false;
	}

	if ((retcode = m_manager_pumping->OrderSubscribe(this)) != MT_RET_OK)
	{
		LogOut(tools::logger::LVFATAL, "ManagerExtension: order subscribe failed [%u]\n", retcode);
		return false;
	}
	//m_admin->Subscribe(this);
	//--- ok

	// 连接redis
	stop_redis();
	if (start_redis())
	{
		m_run = true;
		if (!m_work_thread.joinable())
		{
			m_work_thread = std::thread(&CManagerExtension::keep_alive, this);
		}
	}
	else
	{
		LogOut(tools::logger::LVFATAL, "failed to connect to redis server");
		return false;
	}

	return true;
}

void CManagerExtension::Shutdown()
{
	//--- disconnect from server
	Disconnect();
	//--- check and release manager interface     
	if (m_manager)
	{
		m_manager->Release();
		m_manager = nullptr;
	}
	if (m_manager_pumping)
	{
		m_manager_pumping->Release();
		m_manager_pumping = nullptr;
	}
	//if (m_admin)
	//{
	//	m_admin->Release();
	//	m_admin = NULL;
	//}
	//--- unload Manager API
	m_factory.Shutdown();

	m_run = false;
	if (m_work_thread.joinable())
		m_work_thread.join();
}

bool CManagerExtension::Connect(const CMTStr & server, const UINT64 login, const CMTStr & password)
{
	//--- Disconnect if reconnect
	Disconnect();
	//---    
	MTAPIRES retcode = MT_RET_ERROR;
	//--- check input params
	if (server.Empty() || login == 0 || password.Empty())
	{
		LogOut(tools::logger::LVFATAL, "ManagerExtension: connection failed with bad arguments\n");
		return false;
	}
	//--- initialize Manger API
	if (!m_manager)
		return false;
	if ((retcode = m_manager->Connect
		(
			server.Str(),
			login,
			password.Str(),
			L"",
			0,
			CONNECTION_TIMEOUT
		)) != MT_RET_OK)
	{
		LogOut(tools::logger::LVFATAL, "Connection failed [%u]\n", retcode);
		return false;
	}

	// 根据组配置加载login
	// 在回调可能产生前调用此函数

	if (!m_manager_pumping)
		return false;
	if ((retcode = m_manager_pumping->Connect
		(
			server.Str(),
			login,
			password.Str(),
			L"",
			IMTManagerAPI::PUMP_MODE_ORDERS
			| IMTManagerAPI::PUMP_MODE_GROUPS
			| IMTManagerAPI::PUMP_MODE_USERS
			| IMTManagerAPI::PUMP_MODE_POSITIONS,
			CONNECTION_TIMEOUT
		)) != MT_RET_OK)
	{
		LogOut(tools::logger::LVFATAL, "Connection failed [%u]\n", retcode);
		return false;
	}
	//if ((retcode = m_admin->Connect(server.Str(), login, password.Str(), L"", 0, CONNECTION_TIMEOUT)) != MT_RET_OK)
	//{
	//	LogOut(tools::logger::LVFATAL, "Connection failed [%u]\n", retcode);
	//	return false;
	//}
	//--- check and create in/out streams   
	//if (m_in_stream == NULL)  m_in_stream = m_manager->CustomCreateStream();
	//if (m_out_stream == NULL) m_out_stream = m_manager->CustomCreateStream();
	//if (m_in_stream == NULL || m_out_stream == NULL)
	//{
	//	wprintf_s(L"ManagerExtension: CustomCreateStream failed\n");
	//	return(false);
	//}

	m_run = true;

	if(!m_work_thread.joinable())
		m_work_thread = std::thread(&CManagerExtension::keep_alive, this);

	m_dealer_login = login;

	retcode = LoadLogins();
	if (retcode != MT_RET_OK)
	{
		return false;
	}

	//--- ok
	return true;
}

void CManagerExtension::Disconnect()
{
	//--- if connection sets
	if (m_manager)
	{
		m_manager->Disconnect();
	}
	if (m_manager_pumping)
	{
		m_manager_pumping->Disconnect();
	}
	//if (m_admin)
	//{
	//	m_admin->Disconnect();
	//}
}

//void CManagerExtension::OnOrderAdd(const IMTOrder * order)
//{
//	if (order == nullptr)	return;
//	if (order->Login() != m_trader)	return;
//
//	LogOut(tools::logger::LVTRACK, "OnOrderAdd");
//}

void CManagerExtension::OnOrderDelete(const IMTOrder * order)
{
	if (order == nullptr)	return;
	if (order->Login() != m_trader)	return;

	//LogOut(tools::logger::LVTRACK, "OnOrderDelete");
	LogOut(tools::logger::LVTRACK, "OnOrderDelete, login: %lld, ord: %lld, pos: %lld, state: %d, vol_init: %lld, vol_cur: %lld",
		order->Login(), order->Order(), order->PositionID(), order->State(), order->VolumeInitial(), order->VolumeCurrent());

	IMTAccount* account = m_manager->UserCreateAccount();
	IMTRequest* request = m_manager->RequestCreate();
	ScopeGuard guard([request, account]()
	{
		if (request)
			request->Release();
		if (account)
			account->Release();
	});

	UINT64 orig_position = order->PositionID();
	char position_buf[128];
	sprintf(position_buf, "%lld", order->PositionID());

	char request_buf[64] = { 0 };
	char login_buf[128] = { 0 };
	if (order->Order() == order->PositionID())
	{

		for (auto login : m_logins)
		{
			MTAPIRES res = MT_RET_OK;

			if (m_manager_pumping->UserAccountGet(login, account) != MT_RET_OK)
			{
				// TODO: 这里失败直接跳过没有做其他处理，下一次进来可能依然会遇到一样的问题
				LogOut(tools::logger::LVTRACK, "failed to get account: %lld", login);
				continue;
			}

			UINT level = (UINT)(account->Balance() / m_step);
			UINT64 volume = round((double)level * order->VolumeInitial() / 10000) * 100;

			LogOut(tools::logger::LVTRACK, "start position, login: %lld, level: %d, vol: %lld", login, level, volume);
			if (volume <= 0)	continue;

			request->Clear();
			request->Login(login);
			request->SourceLogin(m_dealer_login);
			request->Action(IMTRequest::TA_DEALER_POS_EXECUTE);
			request->Type(order->Type());
			request->Symbol(order->Symbol());
			request->PriceOrder(order->PriceOrder());
			request->Volume(volume);

			UINT request_id = 0;
			res = m_manager->DealerSend(request, this, request_id);

			LogOut(tools::logger::LVTRACK, "start position, login: %lld, req id: %d", login, request_id);

			// 存储下这个请求的cache，给插件使用
			// TODO：这里有可能插件的OnTradeRequestAdd回调比写入缓存更早调用
			request_cache cache;
			cache.request = request_id;
			cache.orig_position = orig_position;
			cache.login = login;

			sprintf(request_buf, "reqid_%d_%lld", request_id, login);
			m_redis_client->set(request_buf, std::string((char*)&cache, sizeof(request_cache)), [this](cpp_redis::reply& r)
			{
				if (r.ko())
				{
					LogOut(tools::logger::LVERROR, "redis: %s", r.error().c_str());
				}
			});

			m_redis_client->commit();

			position_context context;
			context.request_id = request_id;
			context.level = level;
			//int direction = 1;
			//if (order->Type() == IMTOrder::OP_SELL)
			//	direction = -1;

			sprintf(login_buf, "%lld", login);
			m_redis_client->hset(position_buf, login_buf, std::string((char*)&context, sizeof(position_context)), [this](cpp_redis::reply& r)
			{
				if (r.ko())
				{
					LogOut(tools::logger::LVERROR, "redis: %s", r.error().c_str());
				}
			});

			m_redis_client->commit();
		}
	}
	else
	{
		for (auto login : m_logins)
		{
			// 获取跟单持仓的上下文
			sprintf(login_buf, "%lld", login);
			auto fut = m_redis_client->hget(position_buf, login_buf);
			m_redis_client->sync_commit();
			auto reply = fut.get();

			LogOut(tools::logger::LVTRACK, "request context, key %s, field %s", position_buf, login_buf);

			// 如果不存在，忽略
			if (reply.ko())	continue;
			if (reply.is_null())	continue;
			MTAPIRES res = MT_RET_OK;

			// 读取跟单持仓
			position_context context;
			memcpy(&context, reply.as_string().c_str(), sizeof(position_context));

			LogOut(tools::logger::LVTRACK, "orig position: %s, login: %s, volume: %lld, dest position: %lld", position_buf, login_buf, context.volume, context.position_id);

			if (m_manager_pumping->UserAccountGet(login, account) != MT_RET_OK)
			{
				// TODO: 这里失败直接跳过没有做其他处理，下一次进来可能依然会遇到一样的问题
				LogOut(tools::logger::LVTRACK, "failed to get account: %lld", login);
				continue;
			}

			UINT level = context.level;
			UINT volume = round((double)level * order->VolumeInitial() / 10000) * 100;

			LogOut(tools::logger::LVTRACK, "add order, login: %lld, level: %d, vol: %lld", login, level, volume);
			if (volume <= 0)	continue;

			request->Clear();
			request->Login(login);
			request->SourceLogin(m_dealer_login);
			request->Action(IMTRequest::TA_DEALER_POS_EXECUTE);
			request->Type(order->Type());
			request->Symbol(order->Symbol());
			request->PriceOrder(order->PriceOrder());
			request->Volume(volume);
			// 加入position id
			request->Position(context.position_id);

			UINT request_id = 0;
			res = m_manager->DealerSend(request, this, request_id);

			LogOut(tools::logger::LVTRACK, "add order, login: %lld, req id: %d", login, request_id);

			request_cache cache;
			cache.request = request_id;
			cache.orig_position = orig_position;
			//cache.dest_position = context.position_id;
			cache.login = login;
			int direction = 1;
			if (order->Type() == IMTOrder::OP_SELL)
				direction = -1;

			sprintf(request_buf, "reqid_%d", request_id);
			m_redis_client->hset(request_buf, login_buf, std::string((char*)&cache, sizeof(position_context)), [this](cpp_redis::reply& r)
			{
				if (r.ko())
				{
					LogOut(tools::logger::LVERROR, "redis: %s", r.error().c_str());
				}
			});

			m_redis_client->commit();
		}
	}
}

void CManagerExtension::OnConnect()
{
	LogOut(tools::logger::LVLOG, "Server connected");
}

void CManagerExtension::OnDisconnect()
{
	LogOut(tools::logger::LVLOG, "Server lost connection");
}

void CManagerExtension::OnDealPerform(const IMTDeal * deal, IMTAccount * account, IMTPosition * position)
{
	// deal perform会在order delete之后调用
	// 也许把order delete中的内容移动到这里来更好？
	// 如果持仓被平，则遍历记录，删除所有记录
	if (deal == nullptr
		|| account == nullptr
		|| position == nullptr)
		return;

	if (account->Login() != m_trader)	return;

	LogOut(tools::logger::LVTRACK, "on deal perform, volume: %lld", position->Volume());

	if (position->Volume() != 0)		return;

	char position_buf[128] = { 0 };
	sprintf(position_buf, "%lld", position->Position());

	std::vector<std::string> fields;
	for (auto login : m_logins)
	{
		fields.push_back(std::to_string(login));
	}

	m_redis_client->hdel(position_buf, fields, [this](cpp_redis::reply& r)
	{
		if (r.ko())
		{
			// TODO 错误处理
			try
			{
				LogOut(tools::logger::LVERROR, "redis: %s", r.error().c_str());
			}
			catch (...)
			{
			}
		}
	});

	m_redis_client->commit();
}

void CManagerExtension::SetTrader(const UINT64 trader)
{
	m_trader = trader;
}

void CManagerExtension::SetGroups(const std::string & groups)
{
	m_groups = groups;
}

void CManagerExtension::SetStep(const int step)
{
	m_step = step;
}

void CManagerExtension::SetTolerance(const int tolerance)
{
	m_tolerance = tolerance;
}

void CManagerExtension::SetRedisServer(const std::string & server)
{
	m_redis_server = server;
}

void CManagerExtension::SetRedisPort(const int port)
{
	m_redis_port = port;
}

void CManagerExtension::SetRedisPassword(const std::string & password)
{
	m_redis_password = password;
}

void CManagerExtension::SetLogger(const std::shared_ptr<tools::logger>& logger)
{
	m_logger = logger;
}

void CManagerExtension::LogOut(const tools::logger::log_level level, const char * msg, ...)
{
	if (m_logger == nullptr)	return;

	char buffer[1024];
	//--- format string
	va_list arg_ptr;
	va_start(arg_ptr, msg);
	_vsnprintf(buffer, sizeof(buffer) - 1, msg, arg_ptr);
	va_end(arg_ptr);
	//--- out to server log
	m_logger->write(level, buffer);
}

bool CManagerExtension::start_redis()
{
	try
	{
		m_redis_error_notified = false;


		if (m_redis_client)
		{
			if (m_redis_client->is_connected())
				return true;
		}

		m_redis_client.reset(new cpp_redis::client);

		m_redis_client->connect
		(
			m_redis_server,
			m_redis_port,
			[this](const std::string& host, std::size_t port, cpp_redis::connect_state status)
			{
				if (status == cpp_redis::connect_state::ok)
				{
					LogOut(tools::logger::LVTRACK, "redis server connected");
				}
			}
		);

		if (m_redis_password != "")
		{
			auto fut = m_redis_client->auth(m_redis_password);
			m_redis_client->sync_commit();
			auto reply = fut.get();
			if (reply.is_error())
			{
				LogOut(tools::logger::LVERROR, "connect: authentication failed");
				return false;
			}
		}
	}
	catch (tacopie::tacopie_error& e)
	{
		LogOut(tools::logger::LVERROR, "redis conn: %s", e.what());
		return false;
	}
	catch (std::exception& e)
	{
		LogOut(tools::logger::LVERROR, "redis conn: %s", e.what());
		return false;
	}
	catch (...)
	{
		LogOut(tools::logger::LVERROR, "failed to connect to server");
		return false;
	}

	return true;
}

void CManagerExtension::stop_redis()
{
	try
	{
		if (m_redis_client)
		{
			// 使用sync commit保证操作全部提交
			m_redis_client->sync_commit();
			m_redis_client->disconnect();
			m_redis_client.reset();
		}
	}
	catch (tacopie::tacopie_error& e)
	{
		LogOut(tools::logger::LVERROR, "stop redis: %s", e.what());
	}
	catch (std::exception& e)
	{
		LogOut(tools::logger::LVERROR, "stop redis: %s", e.what());
	}
	catch (...)
	{
		LogOut(tools::logger::LVERROR, "stop redis: unknown error");
	}
}

void CManagerExtension::keep_alive()
{
	using namespace std::chrono_literals;
	int counter = 500;

	while (m_run)
	{
		if (counter-- <= 0)
		{
			counter = 500;

			std::lock_guard<decltype(m_lock)> lk(m_lock);
			if (m_redis_client)
			{
				if (m_redis_client->is_connected())
				{
					m_redis_client->ping([this](cpp_redis::reply& r)
					{
						if (r.ko())
						{
							m_redis_error_notified = true;
							if (!m_redis_error_notified)
							{
								LogOut(tools::logger::LVERROR, "redis: %s", r.error().c_str());
							}
						}
					});
				}
				else
				{
					m_redis_error_notified = true;
					if (!m_redis_error_notified)
					{
						LogOut(tools::logger::LVERROR, "redis server not connected");
					}

					stop_redis();
					start_redis();
				}
			}
		}

		std::this_thread::sleep_for(16ms);
	}
}

MTAPIRES CManagerExtension::LoadLogins()
{
	m_logins.clear();

	IMTConGroup* group = m_manager_pumping->GroupCreate();
	ScopeGuard group_guard([&]()
	{
		group->Release();
	});

	if (group == nullptr)	return MT_RET_ERR_MEM;
	UINT total = m_manager_pumping->GroupTotal();

	LogOut(tools::logger::LVTRACK, "total group(s): %d", total);

	MTAPIRES ret = MT_RET_OK;
	std::vector<std::wstring> groups;

	char groups_buf[256] = { 0 };
	sprintf(groups_buf, "%s", m_groups);
	for (int i = 0; i < total; i++)
	{
		group->Clear();
		ret = m_manager_pumping->GroupNext(i, group);

		if (ret != MT_RET_OK)	continue;

		if (CheckGroup(groups_buf, ws2s(group->Group()).c_str()) == FALSE)	continue;

		LogOut(tools::logger::LVTRACK, "group %s matched config", ws2s(group->Group()).c_str());
		groups.push_back(group->Group());
	}

	for (auto& group : groups)
	{
		UINT64* logins = nullptr;
		UINT total_users = 0;

		ret = m_manager_pumping->UserLogins(group.c_str(), logins, total_users);
		LogOut(tools::logger::LVTRACK, "add group %s, total users: %d", ws2s(group).c_str(), total_users);
		if (ret == MT_RET_OK && logins != nullptr)
		{
			for (int i = 0; i < total_users; ++i)
			{
				LogOut(tools::logger::LVTRACK, "add %d to list", logins[i]);
				m_logins.push_back(logins[i]);
			}
		}

		if (logins)
		{
			m_manager_pumping->Free(logins);
		}
	}

	return ret;
}
