Program SMarkM;
(***************************************
 * Pascal 1, Assessment 1
 * Student mark management program
 *
 * 12.1994
 * 
 * TAB=2
 *)

{ If using bpw then include WinCrt unit }
{$IfDef WINDOWS}
Uses
	Wincrt;
{$Else}
Uses
	Crt;
{$EndIf}

{ If using Amiga hspc then these are our mem. requirements }
{$IfDef Amiga}
{$M 20,1,10,15}
{$EndIf}

{$F-,I+,R+,S+,V-}

Const
	MAX_STUDENTS = 15;
	MAX_SUBJECTS =  4;
	MAX_NAMELEN  = 15;
	MAX_SUBLEN   =  7;
	DEF_PASS     = 50;
	
Type
	tPercentRange = 0..100;
	
	{ record of marks for each student }
	tStudent = Record
		sdt_Name   : String;                                { student name }
		sdt_Marks  : Array[1..MAX_SUBJECTS] of tPercentRange; 
		                                       { av. mark for each subject }
		sdt_AvMark : tPercentRange;             { av. mark of all subjects }
	End;
	
	{ all the students }
	tClass = Record
		cls_AvAvMark : tPercentRange;                     { av. of AvMarks }
		cls_AvMarks  : Array[1..MAX_SUBJECTS] of tPercentRange; 
		                                       { av. mark for each subject }
		cls_students : Array[1..MAX_STUDENTS] Of tStudent; { student recs. }
	End;
	
	{ The names and data of the subjects }
	tSubject = Record
		sub_Name : String;                               { name of subject }
		sub_PassMark : tPercentrange;               { pass mark of subject }
	End;
	tSubjects = Array[1..MAX_SUBJECTS] of tSubject;
	
	
(***************************************
 * Procedure GetInt;
 * FUNCTION:
 *  Gets a string from Input, verifies that it is a number and within
 *  the specified range.
 * INPUTS:
 *  min - The minimum value accepted;
 *  max - The maximum value accepted;
 *  prompt - The prompt for input;
 * OUTPUTS:
 *  num - The entered number.
 *)
Procedure GetInt(VAR num : Integer; min, max : Integer; prompt : String);

Var
	ts : String;
	err : Integer;
	
Begin
	Repeat
		Write(prompt);
		Readln(ts);
		{ convert to number }
		Val(ts,num,err);
		if err <> 0 then
			num := min-1;
		If NOT (num in [min..max]) then
			Write('Must be in range ',min,'<=x<=',max,' ');
	Until (num in [min..max]);
End;


(***************************************
 * Procedure GetSizes;
 * FUNCTION: 
 *  Prompt user for number of students and number of subjects.
 * OUTPUTS:
 *  classsize - number of students
 *  subjects - number of subjects
 *)
Procedure GetSizes(VAR classsize, subjects : Integer);


Begin
	GetInt(classsize, 1, MAX_STUDENTS,
	       'Enter the number of students in the class : ');
	GetInt(subjects, 1, MAX_SUBJECTS,
	       'Enter the number of subjects : ');
End;


(***************************************
 * Procedure GetString;
 * FUNCTION:
 *  Promts user for a string.
 * INPUTS:
 *  max - maximum length of string
 * OUTPUTS:
 *  s - The entered string
 *)
Procedure GetString(VAR s : String; max : Integer; prompt : String);

Begin
	Write(prompt);
	Readln(s);
	While Length(s) > max do begin
		Write('maximum length is ',max,' ',prompt);
		Readln(s);
	End;
End;


(***************************************
 * Function IntToS;
 * FUNCTION:
 *  Converts an integer to a string, via the Str() procedure
 * INPUTS:
 *  num - number to be converted
 * OUTPUTS:
 *  IntToS - num as an array of assci codes
 *)
Function IntToS(num : Integer) : String;
Var ts : String;
Begin
	Str(num, ts);
	IntToS := ts;
End;


(***************************************
 * Procedure GetSubjectNames;
 * FUNCTION:
 *  Get the names of the subjects.
 * INPUTS:
 *  numsub - number of subjects
 *  prompt - the user prompt
 * OUTPUTS:
 *  subnames - the subject names
 *)
Procedure GetSubjectNames(VAR subnames : tSubjects; numsub : Integer);

Var
	prompt : String;
	n, tmp : Integer;

Begin
	For n := 1 to numsub do begin
	 prompt := 'Enter name of subject '+IntToS(n)+' : ';
	 GetString(subnames[n].sub_Name, MAX_SUBLEN, prompt);
	 prompt := 'Enter percentage passmark of "'+subnames[n].sub_Name+
	   '" : ';
	 GetInt(tmp, 0, 100, prompt);
	 subnames[n].sub_PassMark := tmp; 
	End;
End;


(***************************************
 * Procedure GetInfo;
 * FUNCTION:
 *  Gets the student information.
 * INPUTS:
 *  subjects - the subject details
 *  classsize - number of students
 *  numsub - number of subjects
 * OUTPUTS:
 *  class - all pupil records
 *)
Procedure GetInfo(Var class : tClass; 
                  subjects  : tSubjects; 
                  classsize, 
                  numsub    : Integer);

Var
	n, w, tmp,
	stot : Integer;
	ts        : String;
	avMarks   : Array[1..MAX_SUBJECTS] of Integer;

	
Begin
	Writeln;
	{ initilise }
	For n := 1 to numsub do
		AvMarks[n] := 0;
		
	For n := 1 to classsize do begin
		stot := 0;
		GetString(class.cls_Students[n].sdt_Name, MAX_NAMELEN, 
		          'Enter name of student '+IntToS(n)+' : ');
		For w := 1 to numsub do begin
			GetInt(tmp, 0, 100, 
			      'Enter percentage mark of "'+class.cls_Students[n].sdt_Name+
			      '" for "'+subjects[w].sub_Name+'" : ');	
			class.cls_Students[n].sdt_Marks[w] := tmp;
			inc(stot, tmp);
			inc(AvMarks[w], tmp);
		End;
		class.cls_Students[n].sdt_AvMark := Round(stot / numsub);
		Writeln;
	End;
	
	stot := 0;
	For n := 1 to numsub do begin
		class.cls_AvMarks[n] := Round(AvMarks[n] / classsize);
		stot := stot + class.cls_AvMarks[n];
	End;
	class.cls_AvAvMark := Round(stot / numsub); 
End;
		
(***************************************
 * Procedure DisplayData;
 * FUNCTION:
 *  Displays all student records.
 * INPUTS:
 *  class - All the student data
 *  subject - All subject data
 *  classsize - number of students in the class
 *  numsub - number of subjects
 *)
Procedure DisplayData(class   : tClass; 
                      subject : tSubjects; 
                      classsize,
                      numsub  : Integer);

Procedure DrawVLine;
Var
	n : Integer;
	
Begin
	Write(' ');
	For n := 1 to (MAX_NAMELEN+((MAX_SUBLEN+3)*(numsub+1))+4) do
		Write('=');
	Writeln;
End;

Procedure DrawHeader;
Var
	n  : Integer;
                      
Begin
	{ Display header }
	DrawVLine;
		Write(' | ','Student Name':MAX_NAMELEN,' | ');
	For n := 1 to numsub do
		Write(subject[n].sub_Name:MAX_SUBLEN,' | ');
	Write('Average':MAX_SUBLEN,' |');
	Writeln;
	Write(' | ','':MAX_NAMELEN,' | ');
	For n := 1 to numsub do
		Write('PM ',IntToS(subject[n].sub_PassMark):3,'% | ');
	Write('Mark':MAX_SUBLEN,' |');
	Writeln;
	DrawVLine;
End;

Procedure DrawStudent(n : Integer);

Var
	w : Integer;
	
Begin
	Write(' | ',class.cls_Students[n].sdt_Name:MAX_NAMELEN,' | ');
	For w := 1 to numsub do begin
		Write(class.cls_Students[n].sdt_Marks[w]:MAX_SUBLEN-1);
		If class.cls_Students[n].sdt_Marks[w] >= subject[w].sub_PassMark then
			Write(' ')
		else
			Write('*');
		Write(' | ');
	End;
	Write(class.cls_Students[n].sdt_AvMark:MAX_SUBLEN-1);
	If class.cls_Students[n].sdt_AvMark >= DEF_PASS then
		Write(' ')
	else
		Write('*');
	Writeln(' |');
End;


Procedure DrawAvMarks;

Var
	n : Integer;
	
Begin
	Write(' | ','Average Mark':MAX_NAMELEN,' | ');
	For n := 1 to numsub do begin
		Write(class.cls_AvMarks[n]:MAX_SUBLEN-1);
		If class.cls_AvMarks[n] >= DEF_PASS then
			Write(' ')
		else
			Write('*');
		Write(' | ');
	End;
	Write(class.cls_AvAvMark:MAX_SUBLEN-1);
	If class.cls_AvAvMark >= DEF_PASS then
		Write(' ')
	else
		Write('*');
	Writeln(' |');
End;
	

Var
	n : Integer;
                      
Begin
	{ print the header }
	DrawHeader;
	{ print each student record }
	For n := 1 to classsize do
		DrawStudent(n);
	DrawVLine;
	{ print the average class marks }
	DrawAvMarks;
	DrawVLine;
End;
	


(***************************************
 * Function Main
 * FUNCTION:
 *  The main entry point to the program. Avoids the use of glob vars. 
 * OUTPUTS:
 *  Main - Error code, 0 = no error.
 *)
Function Main : Integer;

Var
	classsize, 
	numsub   : Integer;
	class    : tClass;
	subjects : tSubjects;
	ch       : Char;
	
Begin
	Repeat
		ClrScr;
		GetSizes(classsize, numsub);
		GetSubjectNames(subjects, numsub);
		GetInfo(class, subjects, classsize, numsub);
		DisplayData(class, subjects, classsize, numsub);
		
		Writeln;
		Writeln;
		Write('Do you wish to run again? (y/n) : ');
		Readln(ch);
	Until NOT (ch in ['y','Y']);
	{ Always return 0... }
	Main := 0;
End;
	
	
(***************************************
 * call the main procedure and return error code.
 *)
Begin 
	Halt(Main); 
End.