/* $Id: shell.c,v 1.12 2002/09/08 10:23:47 chorns Exp $
 *
 * PROJECT    : ReactOS Operating System
 * DESCRIPTION: ReactOS' Native Shell
 * LICENSE    : See top level directory
 *
 */
#define NTOS_MODE_USER
#include <ntos.h>
#include <windows.h>
#include <stdarg.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>


HANDLE InputHandle, OutputHandle;
BOOL   bCanExit = TRUE, bCanLinespace = TRUE;


void debug_printf(char* fmt, ...)
{
   va_list args;
   char buffer[512];
   DWORD nbChar;

   va_start(args,fmt);
   _vsnprintf(buffer,512,fmt,args);
   va_end(args);
   WriteConsoleA(OutputHandle, buffer, strlen(buffer), &nbChar, NULL);
}

void ExecuteCls(void)
{
   DWORD dwWritten;
   CONSOLE_SCREEN_BUFFER_INFO csbi;
   COORD coPos;

   GetConsoleScreenBufferInfo (OutputHandle, &csbi);

   coPos.X = 0;
   coPos.Y = 0;
   FillConsoleOutputAttribute (OutputHandle, csbi.wAttributes, (csbi.dwSize.X)*(csbi.dwSize.Y), coPos, &dwWritten);
   FillConsoleOutputCharacter (OutputHandle, _T(' '), (csbi.dwSize.X)*(csbi.dwSize.Y), coPos, &dwWritten);
   SetConsoleCursorPosition (OutputHandle, coPos);
}

void ExecuteVer(void)
{
    debug_printf(
	"Reactos Simple Shell\n(compiled on %s, at %s)\n",
	__DATE__,
	__TIME__
	);
}

void ExecuteCd(char* cmdline)
{
   if (!SetCurrentDirectoryA(cmdline))
     {
	debug_printf("Invalid directory\n");
     }
}

void ExecuteMd (char *cmdline)
{
   if (!CreateDirectoryA (cmdline, NULL))
     {
	debug_printf("Create Directory failed!\n");
     }
}

void ExecuteDir(char* cmdline)
{
   HANDLE shandle;
   WIN32_FIND_DATA FindData;
   int nFile=0, nRep=0;
   FILETIME fTime;
   SYSTEMTIME sTime;

   shandle = FindFirstFile("*",&FindData);

   if (shandle==INVALID_HANDLE_VALUE)
     {
	debug_printf("File not found\n");
	return;
     }
   do
     {
	debug_printf("%-15.15s",FindData.cAlternateFileName);
	if(FindData.dwFileAttributes &FILE_ATTRIBUTE_DIRECTORY)
	  debug_printf("<DIR>       "),nRep++;
	else
	  debug_printf(" %10d ",FindData.nFileSizeLow),nFile++;

        FileTimeToLocalFileTime(&FindData.ftLastWriteTime ,&fTime);
        FileTimeToSystemTime(&fTime, &sTime);
        debug_printf("%02d/%02d/%04d %02d:%02d:%02d "
            ,sTime.wMonth,sTime.wDay,sTime.wYear
            ,sTime.wHour,sTime.wMinute,sTime.wSecond);

	debug_printf("%s\n",FindData.cFileName);
     } while(FindNextFile(shandle,&FindData));
   debug_printf("\n    %d files\n    %d directories\n",nFile,nRep);
   FindClose(shandle);
}


void ExecuteReboot(char* cmdline)
{
   NtShutdownSystem (ShutdownReboot);
}


void ExecuteShutdown(char* cmdline)
{
   NtShutdownSystem (ShutdownNoReboot);
}


void ExecuteType(char* cmdline)
{
   HANDLE FileHandle;
   char c;
   DWORD Result;
   BOOL Success;

   FileHandle = CreateFile(cmdline,
			   FILE_GENERIC_READ,
			   0,
			   NULL,
			   OPEN_EXISTING,
			   0,
			   NULL);
   if (FileHandle == NULL)
     {
	debug_printf("Unknown file\n");
	return;
     }
   do
     {
       Success = ReadFile(FileHandle,
			  &c,
			  1,
			  &Result,
			  NULL);
       if (Success)
	 {
	   debug_printf("%c",c);
	   c = 0;
	 }
     } while (Success && Result > 0);
   CloseHandle(FileHandle);
}

int ExecuteProcess(char* name, char* cmdline, BOOL detached)
{
   PROCESS_INFORMATION	ProcessInformation;
   STARTUPINFO		StartupInfo;
   BOOL			ret;
   CHAR			fullname[260];
   PCHAR		p;
   
   /* append '.exe' if needed */
   strcpy (fullname, name);
   p = strrchr (fullname, '.');
   if ((p == NULL) ||
       (_stricmp (p, ".exe") != 0))
     {
	strcat (fullname, ".exe");
     }

   memset(&StartupInfo, 0, sizeof(StartupInfo));
   StartupInfo.cb = sizeof (STARTUPINFO);
   StartupInfo.lpTitle = name;
   if( cmdline && *cmdline  )
     *(cmdline-1) = ' ';  // fix command line so it is the FULL command, including exe name
   ret = CreateProcessA(fullname,
			name,
			NULL,
			NULL,
			FALSE,
			((TRUE == detached)
			 ? CREATE_NEW_CONSOLE
			: 0
			),
			NULL,
			NULL,
			& StartupInfo,
			& ProcessInformation
			);
   if (TRUE == detached)
     {
	if (ret)
	  {
	     debug_printf("\"%s\" detached:\n"
			  "\thProcess = %08X\n"
			  "\thThread  = %08X\n"
			  "\tPID      = %d\n"
			  "\tTID      = %d\n\n",
			  fullname,
			  ProcessInformation.hProcess,
			  ProcessInformation.hThread,
			  ProcessInformation.dwProcessId,
			  ProcessInformation.dwThreadId);
	     CloseHandle(ProcessInformation.hProcess);
	     CloseHandle(ProcessInformation.hThread);
	  }
	else
	  {
	     debug_printf("Could not detach %s\n", name);
	  }
     }
   else
     {
	if (ret)
	  {
//	     	debug_printf("ProcessInformation.hThread %x\n",
//			     ProcessInformation.hThread);
	     WaitForSingleObject(ProcessInformation.hProcess, INFINITE);
	     CloseHandle(ProcessInformation.hProcess);
//	     debug_printf("Thandle %x\n", ProcessInformation.hThread);
	     CloseHandle(ProcessInformation.hThread);
	  }
     }
   return(ret);
}

void ExecuteStart(char* CommandLine)
{
	   char *ImageName = CommandLine;

	   for (	;
			(	(*CommandLine)
				&& (*CommandLine != ' ')
				&& (*CommandLine != '\t')
				);
			CommandLine++
		);
	   *CommandLine++ = '\0';
	   while (	(*CommandLine)
			&& (	(*CommandLine == ' ')
				|| (*CommandLine == '\t')
				)
			);
	   ExecuteProcess(
		ImageName,
		CommandLine,
		TRUE
		);
	   return;
}

void
ExecuteKill(char * lpPid)
{
	HANDLE	hProcess;
	DWORD	dwProcessId;

	dwProcessId = (DWORD) atol(lpPid);
	debug_printf("Killing PID %d...\n",dwProcessId);
	hProcess = OpenProcess(
			PROCESS_TERMINATE,
			FALSE,
			dwProcessId
			);
	if (NULL == hProcess)
	{
		debug_printf(
			"Could not open the process with PID = %d\n",
			dwProcessId
			);
		return;
	}
	if (FALSE == TerminateProcess(
			hProcess,
			0
			)
	) {
		debug_printf(
			"Could not terminate the process with PID = %d\n",
			dwProcessId
			);
	}
	CloseHandle(hProcess);
	return;
}

void ExecuteHelp (void * dummy)
{
	debug_printf (
		"A:\t\t\tCurrent drive is A:\n"
		"C:\t\t\tCurrent drive is C:\n"
		"cd [directory]\t\tChange current directory\n"
		"dir [directory]\t\tList directory\n"
		"exit\t\t\tTerminate the shell\n"
		"help\t\t\tPrint this help message\n"
		"kill [pid]\t\tKill process which PID is pid\n"
		"md [directory]\t\tCreate a new directory\n"
		"reboot\t\t\tRestart the system\n"
		"start [program.exe]\tDetach program.exe\n"
		"type [file]\t\tPrint the file on console\n"
		"validate\t\tValidate the process' heap\n"
		"ver\t\t\tPrint version information\n"
		"[program.exe]\t\tStart synchronously program.exe\n"
		);
}

void ExecuteCommand(char* line)
{
   char* cmd;
   char* tail;

   if (isalpha(line[0]) && line[1] == ':' && line[2] == 0)
     {
	char szPath[MAX_PATH];
	char szVar[5];

	/* save curent directory in environment variable */
	GetCurrentDirectory (MAX_PATH, szPath);

	strcpy (szVar, "=A:");
	szVar[1] = toupper (szPath[0]);
	SetEnvironmentVariable (szVar, szPath);

	/* check for current directory of new drive */
	strcpy (szVar, "=A:");
	szVar[1] = toupper (line[0]);
	if (GetEnvironmentVariable (szVar, szPath, MAX_PATH) == 0)
	  {
	     /* no environment variable found */
	     strcpy (szPath, "A:\\");
	     szPath[0] = toupper (line[0]);
	  }

	/* set new current directory */
	SetCurrentDirectory (szPath);
	GetCurrentDirectory (MAX_PATH, szPath);
	if (szPath[0] != (char)toupper (line[0]))
	     debug_printf("Invalid drive\n");

	return;
     }

   tail = line;
   while ((*tail)!=' ' && (*tail)!=0)
     {
	tail++;	
     }
   if ((*tail)==' ')
     {
	*tail = 0;
	tail++;
     }
   cmd = line;


   if (cmd==NULL || *cmd == '\0' )
     {
	return;
     }
   if (strcmp(cmd,"cd")==0)
     {
	ExecuteCd(tail);
	return;
     }
   if (strcmp(cmd,"dir")==0)
     {
	ExecuteDir(tail);
	return;
     }
   if (strcmp(cmd,"kill")==0)
     {
	ExecuteKill(tail);
	return;
     }
   if (strcmp(cmd,"md")==0)
     {
	ExecuteMd(tail);
	return;
     }
   if (strcmp(cmd,"reboot")==0)
     {
	ExecuteReboot(tail);
	return;
     }
   if (strcmp(cmd,"shutdown")==0)
     {
	ExecuteShutdown(tail);
	return;
     }
   if (strcmp(cmd,"type")==0)
     {
	ExecuteType(tail);
	return;
     }
   if (strcmp(cmd,"ver")==0)
     {
	ExecuteVer();
	return;
     }
   if (strcmp(cmd,"validate")==0)
     {
	debug_printf("Validating heap...");
	if (HeapValidate(GetProcessHeap(),0,NULL))
	  {
	     debug_printf("succeeded\n");
	  }
	else
	  {
	     debug_printf("failed\n");
	  }
	return;
     }
   if (strcmp(cmd,"start") == 0)
     {
	ExecuteStart(tail);
	return;
     }
   if ((strcmp(cmd,"help") * strcmp(cmd,"?")) == 0)
     {
	ExecuteHelp(tail);
	return;
     }
   if (strcmp(cmd,"exit")==0)
     {
        if (bCanExit == TRUE)
          {
             ExitProcess(0);
          }
	return;
     }
   if (strcmp(cmd,"cls") == 0)
     {
	ExecuteCls();
        bCanLinespace = FALSE;
	return;
     }
   if (ExecuteProcess(cmd,tail,FALSE))
     {
	return;
     }
   debug_printf("Unknown command '%s'\n", cmd);
}

void ReadLine(char* line)
{
   DWORD Result;
   UCHAR CurrentDir[255];

   GetCurrentDirectoryA(255,CurrentDir);
   debug_printf("%s>", CurrentDir);
   if ( !ReadConsoleA(InputHandle, line, 252, &Result, NULL) )
      {
	 debug_printf("Failed to read from console\n");
	 for(;;);
      }
   // If the \n is in there, so is the \r so null it, otherwise, null the last char
   if( line[Result-1] == '\n' )
      line[Result-2] = 0;
   else {
     line[Result] = 0;
   }
}

void ParseCommandLine (void)
{
   char *pszCommandLine;

   pszCommandLine = GetCommandLineA ();
   _strupr (pszCommandLine);
   if (strstr (pszCommandLine, "/P") != NULL)
     {
        debug_printf("Permanent shell\n");
        bCanExit = FALSE;
     }
}

int main(void)
{
   static char line[256];
   
   AllocConsole();
   InputHandle = GetStdHandle(STD_INPUT_HANDLE);
   OutputHandle = GetStdHandle(STD_OUTPUT_HANDLE);

   ParseCommandLine ();

   for(;;)
     {
	ReadLine(line);
	ExecuteCommand(line);
        if(bCanLinespace)
        {
           printf("\n");
        } else
           bCanLinespace = TRUE;
     }

   return 0;
}

/* EOF */
