/* Bibliotecas necessárias para a compilação */
#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <tchar.h>
#include <string.h>
#include "math.h"

/* Constante que define o intervalo de tempo em que o benchmark deve ser 
executado */
#define RENEW_BENCH 30
/* Constante para a comparação das datas de criação dos arquivos */
#define BASE_YEAR 2005
/* Constante para a definição do tamanho do buffer do pipe */
#define BUFSIZE 1024

BOOL reRunBench(HANDLE hFile);
BOOL CreateChildProcess(INT processorMask); 
LONG ReadFromPipe(INT); 
VOID ErrorExit(LPTSTR);

HANDLE hChildStdoutRd, hChildStdoutWr, hInputFile, hStdout; 

int main(int argc, char** argv) {
	/* Handle do arquivo de saída do benchmark */
	HANDLE hFile;
	/* Variável que determina se o benchmark deve ou não ser executado */
	BOOL execBench = FALSE;
	/* Estrutura dos atributos de segurança do pipe a ser criado */
	SECURITY_ATTRIBUTES saAttr; 
	/* Variável que sinaliza se processo foi criado com sucesso */
    BOOL fSuccess; 
	/* Estrutura com informações do hardware */
	SYSTEM_INFO siSysInfo;
	/* Número de processadores da máquina */
	int numProcs; 
	/* Tempo total de CPU, em milisegundos, dos processos filhos */
	LONG processTime;
	/* Resultado do benchmark em csFlops */
	int result;
	/* Variável que indica o número de bytes escritos em um arquivo */
	DWORD dwWritten;
	/* Buffer de escrita em arquivo */
    char chBuf[BUFSIZE];

	/* Garante que o nome do arquivo de saída foi passado como parâmetro */
	if(argc == 1) {
		printf("Usage: test <hostname>");
		exit(0);
	}

	/* Tenta abrir um arquivo de saída já existente */
	hFile = CreateFile(TEXT(argv[1]),     // file to create
                  GENERIC_WRITE,          // open for writing
                  0,                      // do not share
                  NULL,                   // default security
                  OPEN_EXISTING,          // overwrite existing
                  FILE_ATTRIBUTE_NORMAL,  // normal file
                  NULL);                  // no attr. template

	/* Caso não exista um arquivo, é preciso executar o benchmark */
	if(hFile == INVALID_HANDLE_VALUE)
		execBench = TRUE;

	/* Verifica se é necessário executar o benchmark novamente de acordo com 
	a idade do arquivo existente */
	if(execBench == FALSE)
		execBench = reRunBench(hFile);

	/* Executa o benchmark */
	if(execBench) 
	{
		/* Copia as informações do hardware */
		GetSystemInfo(&siSysInfo); 
		/* Coleta o número de CPUs da máquina local */
		numProcs = siSysInfo.dwNumberOfProcessors;

		/* Seta bInheritHandle para que os handles dos pipes sejam herdados */
 		saAttr.nLength = sizeof(SECURITY_ATTRIBUTES); 
		saAttr.bInheritHandle = TRUE; 
	    saAttr.lpSecurityDescriptor = NULL; 

		/* Obtém o handle para o STDOUT corrente */
 		hStdout = GetStdHandle(STD_OUTPUT_HANDLE); 
 
		/* Cria um pipe para o STDOUT do processo filho */
		if (! CreatePipe(&hChildStdoutRd, &hChildStdoutWr, &saAttr, 0)) 
			ErrorExit("Falha na criação do Stdout do pipe\n"); 
	  
		/* Garante que o handle de leitura do STDOUT do pipe não é herdado */
		SetHandleInformation( hChildStdoutRd, HANDLE_FLAG_INHERIT, 0);
 
		/* Cria um processo para cada processador da máquina */
		for(int i = 0; i < numProcs; i++) 
		{
			fSuccess = CreateChildProcess(i);
			if (! fSuccess) 
				ErrorExit("Falha na criação do processo"); 
		}

		/* Coleta os resultados dos processos */
		processTime = ReadFromPipe(numProcs);
		
		/* Calcula a capacidade de processamento da CPU */
		processTime /= numProcs;
   		
        result = numProcs * 10000 * 100 / processTime;
	
		if(hFile != INVALID_HANDLE_VALUE)
			CloseHandle(hFile);

		/* Abre o arquivo de saída para escrita */
		hFile = CreateFile(argv[1],     // file to create
			GENERIC_WRITE,              // open for writing
            0,                          // do not share
            NULL,                       // default security
            CREATE_ALWAYS,              // create new file
            FILE_ATTRIBUTE_NORMAL,      // normal file
            NULL);   
		
		/* Escreve resultado no arquivo de saída */
		itoa(result, chBuf, 10);
		fSuccess = WriteFile(hFile, chBuf, (DWORD)strlen(chBuf)*sizeof(char), &dwWritten, NULL); 
		if (! fSuccess) 
			printf("Erro no WriteFile! %d", GetLastError());
	}

	CloseHandle(hFile);

	return 1;
}

/* Determina se o benchmark deve ser executado. Existem dois casos em que isso 
ocorre: quando não há nenhum arquivo de saída de uma execução anterior, e 
quando há esse arquivo, mas ele é mais velho que 30 dias. Esse segundo caso é 
tratado neste método */
BOOL reRunBench(HANDLE hFile)
{
    FILETIME ftCreate, ftAccess, ftWrite;
    SYSTEMTIME stUTCFile, stUTCActual, stFile, stActual;
	long actualDiff, timeDiff, diffTime;
	
    /* Coleta o último acesso para escrita do arquivo encontrado */
    if (!GetFileTime(hFile, &ftCreate, &ftAccess, &ftWrite))
        return TRUE;

	/* Convert a data de última escrita em uma data local */
    FileTimeToSystemTime(&ftWrite, &stUTCFile);
    SystemTimeToTzSpecificLocalTime(NULL, &stUTCFile, &stFile);

	/* Obtém a hora atual e a transforma em data local */
	GetSystemTime(&stUTCActual);
	SystemTimeToTzSpecificLocalTime(NULL, &stUTCActual, &stActual);

	/* Calcula a diferença entre os tempos */
	actualDiff = ((stActual.wYear - BASE_YEAR) * 360) + 
		((stActual.wMonth - 1) * 30) + stActual.wDay;

	timeDiff = ((stFile.wYear - BASE_YEAR) * 360) + 
		((stFile.wMonth - 1) * 30) + stFile.wDay;

	diffTime = actualDiff - timeDiff;

	/* Se conteúdo do arquivo mais antigo que RENEW_BENCH, executa o benchmark 
	novamente */
	if(diffTime > RENEW_BENCH)
		return TRUE;
	else
		return FALSE; 
}

/* Cria um processo filho */
BOOL CreateChildProcess(INT processorMask) 
{ 
	TCHAR szCmdline[]=TEXT("whet");
	PROCESS_INFORMATION piProcInfo; 
	STARTUPINFO siStartInfo;
	BOOL bFuncRetn = FALSE; 
 
	/* Configura os membros da estrutura PROCESS_INFORMATION */
	ZeroMemory( &piProcInfo, sizeof(PROCESS_INFORMATION) );
 
	/* Configura os membros da estrutura STARTUPINFO */
	ZeroMemory( &siStartInfo, sizeof(STARTUPINFO) );
	siStartInfo.cb = sizeof(STARTUPINFO); 
	siStartInfo.hStdError = hChildStdoutWr;
	siStartInfo.hStdOutput = hChildStdoutWr;
	siStartInfo.dwFlags |= STARTF_USESTDHANDLES;
 
    /* Cria, de fato, o processo filho */
	bFuncRetn = CreateProcess(NULL, 
		szCmdline,     // command line 
		NULL,          // process security attributes 
		NULL,          // primary thread security attributes 
		TRUE,          // handles are inherited 
		0,             // creation flags 
		NULL,          // use parent's environment 
		NULL,          // use parent's current directory 
		&siStartInfo,  // STARTUPINFO pointer 
		&piProcInfo);  // receives PROCESS_INFORMATION 
   
    if (bFuncRetn == 0) 
		ErrorExit("Falha na criação do processo filho\n");
    else 
    {
		SetProcessAffinityMask(piProcInfo.hProcess, (int)pow(2,processorMask));
		CloseHandle(piProcInfo.hProcess);
		CloseHandle(piProcInfo.hThread);
		return bFuncRetn;
	}
}
 
/* Metódo que realiza leitura de dados enviados pelos processos filhos para 
o pipe */
LONG ReadFromPipe(INT numProcs) 
{ 
   DWORD dwRead; 
   CHAR chBuf[BUFSIZE]; 
   LONG processTime = 0;

   /* Fecha a opção de escrita para ler o canal */
   if (!CloseHandle(hChildStdoutWr)) 
      ErrorExit("Closing handle failed"); 
 
   /* Captura dados do canal */
   for(int i = 0; i < numProcs; i++) 
   {
		ReadFile( hChildStdoutRd, chBuf, BUFSIZE, &dwRead, NULL);
		processTime += atol(chBuf);
   }
   
   return processTime;
} 
 
/* Método para a exposição de erros */
VOID ErrorExit (LPTSTR lpszMessage) 
{ 
   fprintf(stderr, "%s\n", lpszMessage); 
   ExitProcess(0); 
} 
