/*
 * COPYRIGHT:   See COPYING in the top level directory
 * PROJECT:     ReactOS project statistics
 * FILE:        stats.c
 * PURPOSE:     Main program file
 * PROGRAMMERS: Casper S. Hornstrup (chorns@users.sourceforge.net)
 * REVISIONS:
 *   CSH 01/01-2002 Created
 */
#include <windows.h>
#include <tchar.h>
#include <stdio.h>

typedef struct _EXTENSION_INFO
{
  struct _EXTENSION_INFO * Next;
  struct _FILE_INFO * StatInfoList;
  TCHAR ExtName[16];
  TCHAR ExpandedExtName[128];
  DWORD nExtensions;
  TCHAR Description[256];
  DWORD FileCount;
  DWORD LineCount;
  DWORD EmptyLines;
} EXTENSION_INFO, *PEXTENSION_INFO;

typedef struct _FILE_INFO
{
  struct _FILE_INFO * Next;
  struct _FILE_INFO * StatInfoListNext;
  PEXTENSION_INFO ExtInfo;
  TCHAR FileName[MAX_PATH];
  DWORD LineCount;
  DWORD EmptyLines;
  DWORD FunctionCount;
} FILE_INFO, *PFILE_INFO;

HANDLE FileHandle;
PEXTENSION_INFO ExtInfoList;
PFILE_INFO StatInfoList;
BOOLEAN SkipEmptyLines, BeSilent;

#define MAX_OPTIONS	2
TCHAR *Options[MAX_OPTIONS];


VOID
Initialize(VOID)
{
  ExtInfoList = NULL;
  StatInfoList = NULL;
}


VOID
Cleanup(VOID)
{
  PEXTENSION_INFO ExtInfo;
  PEXTENSION_INFO NextExtInfo;

  ExtInfo = ExtInfoList;
  while (ExtInfo != NULL)
  {
    NextExtInfo = ExtInfo->Next;
    HeapFree (GetProcessHeap(), 0, ExtInfo);
    ExtInfo = NextExtInfo;
  }
}


PEXTENSION_INFO
AddExtension(LPTSTR ExtName,
  LPTSTR Description)
{
  PEXTENSION_INFO ExtInfo;
  PEXTENSION_INFO Info;
  TCHAR *t;
  DWORD ln;

  ExtInfo = (PEXTENSION_INFO) HeapAlloc (GetProcessHeap(), 0, sizeof (EXTENSION_INFO));
  if (!ExtInfo)
    return NULL;

  for(t = ExtName; *t != _T('\0'); t += _tcslen(t) + 1);
  ln = (DWORD)t - (DWORD)ExtName;

  ZeroMemory (ExtInfo, sizeof (EXTENSION_INFO));
  memcpy (ExtInfo->ExtName, ExtName, ln);
  _tcscpy (ExtInfo->Description, Description);

  for(t = ExtInfo->ExtName; *t != _T('\0'); t += _tcslen(t) + 1)
  {
    if(ExtInfo->nExtensions++ != 0)
      _tcscat (ExtInfo->ExpandedExtName, _T(";"));
    _tcscat (ExtInfo->ExpandedExtName, _T("*."));
    _tcscat (ExtInfo->ExpandedExtName, t);
  }

  if (ExtInfoList)
  {
    Info = ExtInfoList;
    while (Info->Next != NULL)
    {
      Info = Info->Next;
    }
    Info->Next = ExtInfo;
  }
  else
  {
    ExtInfoList = ExtInfo;
  }

  return ExtInfo;
}


PFILE_INFO
AddFile(LPTSTR FileName,
  PEXTENSION_INFO ExtInfo)
{
  PFILE_INFO StatInfo;
  PFILE_INFO Info;

  StatInfo = (PFILE_INFO) HeapAlloc (GetProcessHeap(), 0, sizeof (FILE_INFO));
  if (!StatInfo)
    return NULL;
  ZeroMemory (StatInfo, sizeof (FILE_INFO));
  _tcscpy (StatInfo->FileName, FileName);
  StatInfo->ExtInfo = ExtInfo;

  if (ExtInfo->StatInfoList)
  {
    Info = ExtInfo->StatInfoList;
    while (Info->StatInfoListNext != NULL)
    {
      Info = Info->StatInfoListNext;
    }
    Info->StatInfoListNext = StatInfo;
  }
  else
  {
    ExtInfo->StatInfoList = StatInfo;
  }

  if (StatInfoList)
  {
    Info = StatInfoList;
    while (Info->Next != NULL)
    {
      Info = Info->Next;
    }
    Info->Next = StatInfo;
  }
  else
  {
    StatInfoList = StatInfo;
  }

  return StatInfo;
}


VOID
CleanupAfterFile(VOID)
{
  if(FileHandle != INVALID_HANDLE_VALUE)
    CloseHandle (FileHandle);
}


BOOL
LoadFile(LPTSTR FileName)
{
  LONG FileSize;

  FileHandle = CreateFile (FileName, // Create this file
    GENERIC_READ,                    // Open for reading
    0,                               // No sharing
    NULL,                            // No security
    OPEN_EXISTING,                   // Open the file
    FILE_ATTRIBUTE_NORMAL,           // Normal file
    NULL);                           // No attribute template
  if (FileHandle == INVALID_HANDLE_VALUE)
    return FALSE;

  FileSize = GetFileSize (FileHandle, NULL);
  if (FileSize <= 0)
  {
    CloseHandle (FileHandle);
    return FALSE;
  }

  return TRUE;
}


VOID
ReadLines(PFILE_INFO StatInfo)
{
  DWORD ReadBytes, LineLen;
  static char FileBuffer[1024];
  char LastChar = '\0';
  char *Current;

  LineLen = 0;
  while(ReadFile (FileHandle, FileBuffer, sizeof(FileBuffer), &ReadBytes, NULL) && ReadBytes >= sizeof(char))
  {
    for(Current = FileBuffer; ReadBytes > 0; ReadBytes -= sizeof(char), Current++)
    {
      if(*Current == '\n' && LastChar == '\r')
      {
        LastChar = '\0';
        if(!SkipEmptyLines || (LineLen > 0))
          StatInfo->LineCount++;
        if(LineLen == 0)
          StatInfo->EmptyLines++;
        LineLen = 0;
        continue;
      }
      LastChar = *Current;
      if(SkipEmptyLines && (*Current == ' ' || *Current == '\t'))
      {
        continue;
      }
      if(*Current != '\r')
        LineLen++;
    }
  }

  StatInfo->LineCount += (LineLen > 0);
  StatInfo->EmptyLines += ((LastChar != '\0') && (LineLen == 0));
}


VOID
PrintStatistics(VOID)
{
  PEXTENSION_INFO Info;
  DWORD TotalFileCount;
  DWORD TotalLineCount;
  DWORD TotalAvgLF;
  DWORD TotalEmptyLines;

  TotalFileCount = 0;
  TotalLineCount = 0;
  TotalEmptyLines = 0;
  Info = ExtInfoList;

  for (Info = ExtInfoList; Info != NULL; Info = Info->Next)
  {
    TotalFileCount += Info->FileCount;
    TotalLineCount += Info->LineCount;
    TotalEmptyLines += Info->EmptyLines;
  }

  TotalAvgLF = (TotalFileCount ? TotalLineCount / TotalFileCount : 0);

  for (Info = ExtInfoList; Info != NULL; Info = Info->Next)
  {
    DWORD AvgLF;

    if (Info->FileCount != 0)
    {
      AvgLF = (Info->FileCount ? Info->LineCount / Info->FileCount : 0);
    }
    else
    {
      AvgLF = 0;
    }

    _tprintf (_T("\n"));
    _tprintf (_T("File extension%c             : %s\n"), ((Info->nExtensions > 1) ? _T('s') : _T(' ')), Info->ExpandedExtName);
    _tprintf (_T("File ext. description       : %s\n"), Info->Description);
    _tprintf (_T("Number of files             : %lu\n"), Info->FileCount);
    _tprintf (_T("Number of lines             : %lu\n"), Info->LineCount);
    if(SkipEmptyLines)
      _tprintf (_T("Number of empty lines       : %lu\n"), Info->EmptyLines);
    _tprintf (_T("Proportion of lines         : %.2f %%\n"), (float)(TotalLineCount ? (((float)Info->LineCount * 100) / (float)TotalLineCount) : 0));
    _tprintf (_T("Average no. lines/file      : %lu\n"), AvgLF);
  }

  _tprintf (_T("\n"));
  _tprintf (_T("Total number of files       : %lu\n"), TotalFileCount);
  _tprintf (_T("Total number of lines       : %lu\n"), TotalLineCount);
  if(SkipEmptyLines)
    _tprintf (_T("Total number of empty lines : %lu\n"), TotalEmptyLines);
  _tprintf (_T("Average no. lines/file      : %lu\n"), TotalAvgLF);
}


BOOL
ProcessFiles(LPTSTR Path)
{
  WIN32_FIND_DATA FindFile;
  PEXTENSION_INFO Info;
  TCHAR SearchPath[256];
  TCHAR FileName[256];
  TCHAR *Ext;
  HANDLE SearchHandle;
  BOOL More;

  Info = ExtInfoList;
  while (Info != NULL)
  {
   Ext = Info->ExtName;
   do
   {
    ZeroMemory (&FindFile, sizeof (FindFile));
    _tcscpy (SearchPath, Path);
    _tcscat (SearchPath, _T("\\*."));
    _tcscat (SearchPath, Ext);
    _tcscpy (FindFile.cFileName, SearchPath);
    SearchHandle = FindFirstFile (SearchPath, &FindFile);
    if (SearchHandle != INVALID_HANDLE_VALUE)
    {
      More = TRUE;
      while (More)
      {
	      if (!(FindFile.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
  	    {
          _tcscpy (FileName, Path);
          _tcscat (FileName, _T("\\"));
          _tcscat (FileName, FindFile.cFileName);

				  if (LoadFile (FileName))
				  {
	          PFILE_INFO StatInfo;

	          StatInfo = AddFile (FindFile.cFileName, Info);
	          if (!StatInfo)
						{
	  			    _tprintf (_T("Not enough free memory.\n"));
	            return FALSE;
						}

				    ReadLines (StatInfo);

            Info->FileCount++;
	          Info->LineCount += StatInfo->LineCount;
	          Info->EmptyLines += StatInfo->EmptyLines;

	          CleanupAfterFile();
				  }
        }
        More = FindNextFile (SearchHandle, &FindFile);
      }
      FindClose (SearchHandle);
    }
    Ext += _tcslen(Ext) + 1;
   } while(*Ext != _T('\0'));
    Info = Info->Next;
  }
  return TRUE;
}


BOOL
ProcessDirectories(LPTSTR Path)
{
  WIN32_FIND_DATA FindFile;
  TCHAR SearchPath[MAX_PATH];
  HANDLE SearchHandle;
  BOOL More;

  if(!BeSilent)
  {
    _tprintf (_T("Processing %s ...\n"), Path);
  }

  _tcscpy (SearchPath, Path);
  _tcscat (SearchPath, _T("\\*.*"));

  SearchHandle = FindFirstFileEx (SearchPath,
    FindExInfoStandard,
    &FindFile,
    FindExSearchLimitToDirectories,
    NULL,
    0);
  if (SearchHandle != INVALID_HANDLE_VALUE)
  {
    More = TRUE;
    while (More)
    {
	    if ((FindFile.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
      && (_tcscmp (FindFile.cFileName, _T(".")) != 0)
      && (_tcscmp (FindFile.cFileName, _T("..")) != 0)
      && (_tcscmp (FindFile.cFileName, _T("CVS")) != 0)
      && (_tcscmp (FindFile.cFileName, _T(".svn")) != 0))
			{
			  _tcscpy (SearchPath, Path);
			  _tcscat (SearchPath, _T("\\"));
			  _tcscat (SearchPath, FindFile.cFileName);
	      if (!ProcessDirectories (SearchPath))
          return FALSE;
	      if (!ProcessFiles (SearchPath))
          return FALSE;
			}
      More = FindNextFile (SearchHandle, &FindFile);
    }
    FindClose (SearchHandle);
  }
  return TRUE;
}


VOID
Execute(LPTSTR Path)
{
  if (!ExtInfoList)
  {
	  _tprintf (_T("No extensions specified.\n"));
    return;
  }

  if (!ProcessDirectories (Path))
  {
	  _tprintf (_T("Failed to process directories.\n"));
    return;
  }

  if (!ProcessFiles (Path))
  {
	  _tprintf (_T("Failed to process files.\n"));
    return;
  }

  PrintStatistics();
}

BOOLEAN
IsOptionSet(TCHAR *Option)
{
  int i;
  for(i = 0; i < MAX_OPTIONS; i++)
  {
    if(!Options[i])
      continue;

    if(!_tcscmp(Options[i], Option))
      return TRUE;
  }
  return FALSE;
}


int _tmain(int argc, _TCHAR * argv[])
{
  int a;

  _tprintf (_T("ReactOS Project Statistics\n"));
  _tprintf (_T("==========================\n\n"));

  if (argc < 2 || argc > 2 + MAX_OPTIONS)
  {
    _tprintf(_T("Usage: stats [-e] [-s] directory\n"));
    _tprintf(_T("  -e: don't count empty lines\n"));
    _tprintf(_T("  -s: be silent, don't print directories while processing\n"));
    return 1;
  }

  Initialize();
  AddExtension (_T("c\0\0"), _T("Ansi C Source files"));
  AddExtension (_T("cpp\0cxx\0\0"), _T("C++ Source files"));
  AddExtension (_T("h\0\0"), _T("Header files"));

  for(a = 1; a < argc - 1; a++)
  {
    Options[a - 1] = argv[a];
  }

  SkipEmptyLines = IsOptionSet(_T("-e"));
  BeSilent = IsOptionSet(_T("-s"));

  Execute (argv[argc - 1]);
  Cleanup();

  return 0;
}
