#include "StdAfx.h"
#include <Aclapi.h>
#include <aplSocketTransport.h>
#include "aplMPC.h"

#define PSS_MPC_PROC _T("Local\\PSS_MPC_PROC_0x%04X_")
const TCHAR* COMMAND_MUTEX_BUFFER_PROCESSED = _T("APL_COMMAND_MUTEX_BUFFER_PROCESSED");
const TCHAR* COMMAND_EVENT_SET = _T("APL_COMMAND_EVENT_SET");
const TCHAR* COMMAND_SHARED_MEMORY_NAME = _T("APL_COMMAND_SHARED_MEMORY_NAME");
#define COMMAND_SHARED_MEMORY_SIZE 1024
#define TIMEOUT_CMD_THREAD_SEND 100
#define TIMEOUT_CMD_THREAD_RECEIVE 0
#define MAX_CYCLOS_SEND 100


CRITICAL_SECTION protect_object;
CaplMPC* active_object=0;
#define ID_TIMER_MPC_CHECK 12345
#define ID_TIMER_MPC_INTERVAL 200

DWORD timer_id = 0;

CaplMPC_ext_function CaplMPC::st_function =0;

void CALLBACK EXPORT TimerProc(HWND hWnd, UINT nMsg, UINT nIDEvent, DWORD dwTime)
{
	// TODO: Add your message handler code here and/or call default
	if(nIDEvent==timer_id)
	{
		EnterCriticalSection(&protect_object);
		if(active_object!=0)
		{
			active_object->ProcessTimer();
		}
		LeaveCriticalSection(&protect_object);
	}
}


bool InitMPCWnd(bool attach)
{
	if(attach)
	{
		active_object = 0;
		InitializeCriticalSection(&protect_object);
	}
	else
	{
		DeleteCriticalSection(&protect_object);
		active_object = 0;
	}
	return true;
}

CaplMPC::CaplMPC(void)
{
	m_hFile=INVALID_HANDLE_VALUE;
	m_hFileCommandMapping=0;
	m_CommandPregion=0;
	m_event_command_set=0;
	m_mutex_command_buffer_processed=0;
	m_p_command_size=0;
	m_p_command_data=0;
	
	m_LastSendError = _T("");
}

#define MY_THROW_EX_SYS(err_code,descr) {CString sbuf;GetDescriptionSystemError(GetLastError(),sbuf);m_LastSendError=descr+CString(_T(": "))+sbuf;throw (int)__LINE__;}


bool  CaplMPC::st_InitializeCriticalSection=false;

bool CaplMPC::InitListen(CaplMPC_ext_function func)
{
	if(!st_InitializeCriticalSection){ InitializeCriticalSection(&protect_object); st_InitializeCriticalSection =true;}

	PSECURITY_ATTRIBUTES psec_attr;
	HANDLE hFile=INVALID_HANDLE_VALUE;

	CString server_id_txt;
	server_id_txt.Format(PSS_MPC_PROC,GetCurrentProcessId());

	CString name_mutex_buffer_processed=server_id_txt+COMMAND_MUTEX_BUFFER_PROCESSED;
	CString name_event_set=server_id_txt+COMMAND_EVENT_SET;
	CString name_CommandPregion=server_id_txt+COMMAND_SHARED_MEMORY_NAME;

	EnterCriticalSection(&protect_object);
	if(active_object!=0)
	{
		LeaveCriticalSection(&protect_object);
		AddReceiveErrorMessage(APL_T("Процедура приема команд уже запущена!"));
		return false;
	}
	
	try
	{
		if(!CreateSecurityAttributes(FILE_MAP_ALL_ACCESS,&psec_attr)){MY_THROW_EX_SYS(0, APL_T("Ошибка создания атрибутов доступа"));}
		m_hFileCommandMapping = CreateFileMapping(hFile, psec_attr, PAGE_READWRITE, 0, COMMAND_SHARED_MEMORY_SIZE+8, name_CommandPregion); 

		if(m_hFileCommandMapping == 0){MY_THROW_EX_SYS(0, APL_T("Ошибка создания hFileCommandMapping"));}
		m_CommandPregion=MapViewOfFile(m_hFileCommandMapping,FILE_MAP_WRITE,0,0,0);

		memset(m_CommandPregion,0,COMMAND_SHARED_MEMORY_SIZE+8);
		m_p_command_size=(DWORD*)((BYTE*)m_CommandPregion);
		m_p_command_data=((BYTE*)m_CommandPregion+4);

		// ну и event-ы и mutex-ы 
		CreateSecurityAttributes(EVENT_ALL_ACCESS,&psec_attr);
		m_event_command_set=CreateEvent(psec_attr,FALSE,FALSE,name_event_set);;
		if(m_event_command_set==0){MY_THROW_EX_SYS(0,_T("event_command_set ==0"));}

		CreateSecurityAttributes(MUTEX_ALL_ACCESS,&psec_attr);
		m_mutex_command_buffer_processed=CreateMutex(psec_attr,FALSE,name_mutex_buffer_processed);
		if(m_mutex_command_buffer_processed==0){MY_THROW_EX_SYS(0,_T("mutex_command_buffer_processed ==0"));}

		active_object = this;
		timer_id = SetTimer(0,ID_TIMER_MPC_CHECK,ID_TIMER_MPC_INTERVAL,TimerProc);
	}
	catch(int err_line)
	{
		int a=err_line; // строчка нужна, чтобы компилятор не выдавал предупреждений
		m_p_command_size=0;
		m_p_command_data=0;

		if(m_CommandPregion!=0){UnmapViewOfFile(m_CommandPregion);}
		if(m_hFileCommandMapping!=0){CloseHandle(m_hFileCommandMapping);}
		if(m_event_command_set!=0){CloseHandle(m_event_command_set);};
		if(m_mutex_command_buffer_processed!=0){CloseHandle(m_mutex_command_buffer_processed);};
		LeaveCriticalSection(&protect_object);
		return false;
	}

	LeaveCriticalSection(&protect_object);

	if(0!=func) st_function=func;
	return true;
}

CaplMPC::~CaplMPC(void)
{
	if(st_InitializeCriticalSection) EnterCriticalSection(&protect_object);
	if(active_object==this)
	{
		KillTimer(0,timer_id);
		active_object=0;
	}
	if(st_InitializeCriticalSection) LeaveCriticalSection(&protect_object);

	m_p_command_size=0;
	m_p_command_data=0;

	if(m_CommandPregion!=0){UnmapViewOfFile(m_CommandPregion);}
	if(m_hFileCommandMapping!=0){CloseHandle(m_hFileCommandMapping);}
	if(m_event_command_set!=0){CloseHandle(m_event_command_set);};
	if(m_mutex_command_buffer_processed!=0){CloseHandle(m_mutex_command_buffer_processed);};

}


bool CaplMPC::SendCommand(DWORD process_id,const TCHAR* command)
{
	if(!st_InitializeCriticalSection){ InitializeCriticalSection(&protect_object); st_InitializeCriticalSection =true;}

	HANDLE hFile=INVALID_HANDLE_VALUE;
	HANDLE hFileCommandMapping=0;
	PVOID CommandPregion=0;
	PVOID CommandPregionData=0;
	HANDLE event_command_set=0;
	HANDLE mutex_command_buffer_processed=0;

	DWORD *p_command_size;
	BYTE *p_command_data;

	DWORD res;

	bool retval = true;

	HANDLE process_handle;

	CString server_id_txt;
	server_id_txt.Format(PSS_MPC_PROC,process_id);

	CString name_mutex_buffer_processed=server_id_txt+COMMAND_MUTEX_BUFFER_PROCESSED;
	CString name_event_set=server_id_txt+COMMAND_EVENT_SET;
	CString name_CommandPregion=server_id_txt+COMMAND_SHARED_MEMORY_NAME;

	int num_cycles = 0;
	int command_sent = false;

	if( _strlen(command) > COMMAND_SHARED_MEMORY_SIZE)
	{
		m_LastSendError.Format(APL_T("Команда слишком большая! Максимальный размер команды %i байт"),COMMAND_SHARED_MEMORY_SIZE);
		return false;
	}

	try
	{
		// дескриптор для слежения за состоянием сервера
		process_handle=OpenProcess(SYNCHRONIZE | PROCESS_QUERY_INFORMATION | PROCESS_TERMINATE,TRUE,process_id);
		if(process_handle==0){MY_THROW_EX_SYS(APL_NET_SRV_MISSED_DATA, APL_T("Не удается присоединиться к процессу ")+server_id_txt);}

		// Переменные для посылки команд
		// event-ы и mutex-ы 
		event_command_set=OpenEvent(EVENT_ALL_ACCESS,FALSE,name_event_set);
		if(event_command_set==0){MY_THROW_EX_SYS(APL_NET_SRV_MISSED_DATA, APL_T("Не удается получить управляющий объект процесса")+server_id_txt);}

		mutex_command_buffer_processed=OpenMutex(MUTEX_ALL_ACCESS,FALSE,name_mutex_buffer_processed);
		if(mutex_command_buffer_processed==0){MY_THROW_EX_SYS(APL_NET_SRV_MISSED_DATA, APL_T("Не удается получить управляющий объект процесса ")+server_id_txt);}

		HANDLE hFile=INVALID_HANDLE_VALUE;

		// создаем объект проекция файла - его последним.
		hFileCommandMapping = OpenFileMapping( FILE_MAP_ALL_ACCESS, FALSE, name_CommandPregion); 
		if(hFileCommandMapping == 0) {MY_THROW_EX_SYS(APL_NET_SRV_MISSED_DATA, APL_T("Ошибка при открытии области команд процесса ")+server_id_txt);}
		CommandPregion=MapViewOfFile(hFileCommandMapping,FILE_MAP_WRITE,0,0,0);

		p_command_size=(DWORD*)((BYTE*)CommandPregion);
		p_command_data=((BYTE*)CommandPregion+4);

		do{
			// ждем и захватываем мутекс операции 
			res=WaitForSingleObject(mutex_command_buffer_processed,TIMEOUT_CMD_THREAD_SEND);
			switch(res)
			{
			case WAIT_FAILED:
				MY_THROW_EX_SYS(APL_NET_SRV_MISSED_DATA, APL_T("Ошибка при передаче команды процессу ")+server_id_txt);
				break;
			case WAIT_TIMEOUT:
				// ограничиваем количество циклов с записью в лог
// 				RESTRICT_NUM_TIMEOUT_REPEAT( APL_T("Ждем Мкоманду сервера "));
				break;
			case WAIT_ABANDONED:
				MY_THROW_EX_SYS(APL_NET_SRV_MISSED_DATA, APL_T("Процесс ")+server_id_txt+APL_T(" не может принять команду из-за аварийного завершения"));
				// break; мутекс захвачен нами, будем обрабатывать 
			case WAIT_OBJECT_0:
				ResetEvent(event_command_set);
				
				// пишем команду
				int size = _strlen(command);
				*p_command_size = size;
				memcpy(((BYTE*)p_command_data),command,*p_command_size);

				// ставим флаг что команда записана
				SetEvent(event_command_set);
				
				// освобождаем мутекс
				ReleaseMutex(mutex_command_buffer_processed);
				command_sent = true;
				break;
			}
			if(command_sent)break;
			num_cycles++;
		}while(num_cycles < MAX_CYCLOS_SEND);

		if(!command_sent)
		{
			m_LastSendError.Format(APL_T("Не удалось передать команду в течение %i секунд"),MAX_CYCLOS_SEND*TIMEOUT_CMD_THREAD_SEND);
			retval = false;
		}

	}
	catch(int err_line)
	{
		int a=err_line; // строчка нужна, чтобы компилятор не выдавал предупреждений
		retval = false;
	}

	if(CommandPregion!=0){UnmapViewOfFile(CommandPregion);}
	if(hFileCommandMapping!=0){CloseHandle(hFileCommandMapping);}
	if(event_command_set!=0){CloseHandle(event_command_set);};
	if(mutex_command_buffer_processed!=0){CloseHandle(mutex_command_buffer_processed);};

	return retval;
}

void CaplMPC::ProcessTimer()
{
	CString sbuf;
	DWORD res;
	bool command_processed = false;
	int count_mutex = 0;

	res=WaitForSingleObject(m_event_command_set,TIMEOUT_CMD_THREAD_RECEIVE);
	switch(res)
	{
	case WAIT_FAILED:
		GetDescriptionSystemError(GetLastError(),sbuf);
		AddReceiveErrorMessage(sbuf);
		/*LogErrorFC1(APL_T("Ошибка при ожидании массива HANDLEов в диспетчере команд: ")+sbuf+APL_T("\r\n!!!!! Прерываем работу сервера!"));
		theApp.SetGlobalExit();*/
		break;
	case WAIT_TIMEOUT:
		break;
	case WAIT_OBJECT_0:

		// какой-то сервер поставил флаг что он приготовил команду
		// ждем пока он ее сформирует и освободит буфер
		do{
			res=WaitForSingleObject(m_mutex_command_buffer_processed,TIMEOUT_CMD_THREAD_SEND);
			switch(res)
			{
			case WAIT_FAILED:
				GetDescriptionSystemError(GetLastError(),sbuf);
				AddReceiveErrorMessage(sbuf);
				break;
			case WAIT_TIMEOUT:
				break;
			case WAIT_ABANDONED:
				GetDescriptionSystemError(GetLastError(),sbuf);
				sbuf +=  APL_T("WAIT_ABANDONED! Вызывающий процесс упал в процессе записи команды!");
				AddReceiveErrorMessage(sbuf);
				// break; мутекс захвачен нами, будем обрабатывать 
			case WAIT_OBJECT_0:
				// вызывающий сервер дописал команду и освободил мутекс
				// сбрасываем евент воизбежание повторных срабатываний
				ResetEvent(m_event_command_set);

				// проверяем размерчик на "влезание"
				if(*m_p_command_size>COMMAND_SHARED_MEMORY_SIZE)
				{
					sbuf +=  APL_T("Попытка принять слишком большую команду!");
					AddReceiveErrorMessage(sbuf);
				}

				m_p_command_data[*m_p_command_size] = 0;
				sbuf = m_p_command_data;

				ProcessingCommand(sbuf);
				command_processed = true;

				ReleaseMutex(m_mutex_command_buffer_processed);
				break;
			}
			if(command_processed)break;
			count_mutex++;
		}while(count_mutex < 10);
		if(!command_processed)
		{
			sbuf +=  APL_T("Не удалось за отведенное время заблокировать буфер для приема команды!");
			AddReceiveErrorMessage(sbuf);
		}
	}
}

bool CaplMPC::GetLastReceiveErrors(CStringArray &ReceiveErrors)
{
	int i;
	bool retval = false;
	ReceiveErrors.RemoveAll();
	EnterCriticalSection(&protect_object);
	retval = m_ReceveErrors.GetCount() > 0;
	for(i=0;i<m_ReceveErrors.GetCount();i++)
	{
		ReceiveErrors.Add(m_ReceveErrors.GetAt(i));
	}
	m_ReceveErrors.RemoveAll();
	LeaveCriticalSection(&protect_object);
	return retval;
}

void CaplMPC::AddReceiveErrorMessage(const TCHAR *LastReсeiveMessage)
{
	EnterCriticalSection(&protect_object);
	m_ReceveErrors.Add(LastReсeiveMessage);
	if(m_ReceveErrors.GetSize()>20)
	{
		m_ReceveErrors.RemoveAt(0);
	}
	LeaveCriticalSection(&protect_object);
}


bool CaplMPC::ProcessingCommand(const TCHAR* command)
{
	if(0!=st_function)
	{
		(st_function)(command);
		return true;
	}
	CString mess = APL_T("Получена команда:");
	mess += command ;
	AfxMessageBox(mess);
	return false;
}


DWORD CaplMPC::StartProcess(LPCTSTR exe_name, LPCTSTR cmdline)
{
	if(0==exe_name) return 0;

	CString sExeName=exe_name;
	if(sExeName.Find(_T('\\'))<0)
	{
		CWinApp *app=AfxGetApp(); // При вызове из ActiveX возвращает 0
		if(0!=app)
		{
			CString hlppath=app->m_pszHelpFilePath;
			int i=hlppath.ReverseFind(_T('\\'));
			sExeName=hlppath.Left(i+1);
			sExeName+=exe_name;
		}
	}
	CString buf=sExeName; buf+=_T(" "); buf+=cmdline;


	PROCESS_INFORMATION proc_inf;
	STARTUPINFO startin; memset(&startin,0,sizeof(STARTUPINFO));
	startin.cb=sizeof(STARTUPINFO);
	startin.lpReserved=NULL;
	startin.lpDesktop=NULL;
	startin.lpTitle=NULL;
	startin.dwFlags=STARTF_USESHOWWINDOW;
	startin.cbReserved2=0;
	startin.lpReserved2=NULL;
	startin.wShowWindow=SW_SHOWDEFAULT;


	//if(minimized)startin.wShowWindow=SW_SHOWMINIMIZED;
	//else startin.wShowWindow=SW_SHOWDEFAULT;

	if(0==CreateProcess(0,(LPTSTR)((LPCTSTR)buf),NULL,NULL,TRUE,
		CREATE_NEW_CONSOLE,NULL,NULL,&startin,&proc_inf))
		return -1;

	HANDLE hProcess=proc_inf.hProcess;
	if(0==hProcess) return 0;
	DWORD pid=proc_inf.dwProcessId;
	/*if(wait)
	{
		int ExitCode=0;
		_cwait(&ExitCode,(int)m_hProcess,0);
		return ExitCode;
	}*/
	//return (DWORD)hProcess;
	return pid;
}

bool CaplMPC::GetParam(LPCTSTR cmd_line, LPCTSTR param, CString &value) 
{
	value=_T("");
	if(0==cmd_line || 0==param) return false;
	if(_T('\0')==cmd_line[0] || _T('\0')==param[0]) return false;

	//int k=strlen(prefix);
	//if(k<=0) return;

	int i=0;
	while(true)
	{
		if(cmd_line[i]==param[0])
		{
			int j=1;
			bool bfound=false;
			while(param[j]!=_T('\0') && cmd_line[i+j]!=_T('\0') && param[j]==cmd_line[i+j]) j++;
			if(param[j]==_T('\0'))
			{
				// совпало
				TCHAR c_end=_T(' ');
				if(cmd_line[i-1]==_T('"')) c_end=_T('"');

				i=i+j;
				while(cmd_line[i]!=_T('\0') && cmd_line[i]!=c_end) 
				{
					value+=cmd_line[i];
					i++;
				}
				return true;
			}
		}
		i++;
		if(cmd_line[i]==_T('\0')) return false;
	}
}

bool CaplMPC::IsPpocessActive(DWORD pid)
{
	if(0==pid) return false;
	HANDLE h = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, pid); 
	if(0==h) return false;

	DWORD code;
	bool bReturn =false;
	if (GetExitCodeProcess(h, &code))
	{ 
		if (code == STILL_ACTIVE)  bReturn =true;
	}
	CloseHandle(h); 
	return bReturn;
}