MODULE filesystem INTERFACE;

{*
	Este módulo proporciona los tipos, prodedimientos y funciones
	relacionados con los datos de la aplicación (Persistencia en
	disco de los datos y tratamiento de los mismos,
	carga de configuraciones y tipos personalizados).
*}

export filesystem = (	beingEdited, config, data, disposeDump, NAME_CAPACITY, 
						SURNAME_CAPACITY, newDump, PtrToDump, PtrToStudent,
						searchStudent, studentExists);
	
CONST
	
	NAME_CAPACITY		= 20;
	SURNAME_CAPACITY	= 40;
	
TYPE
	
	BooleanStream = bindable file of boolean;
	PtrToBooleanStream = ^BooleanStream;
	
	{*
		Tipo registro para las notas teórica y práctica,
		la global se calcula al mostrarla.
	*}
	CallMarks = record
		
		theory:real;
		practice:real;
		
	end;
	
	{*
		Tipo del alumno.
	*}
	Student = record
		
		name:string(NAME_CAPACITY);
		surname:string(SURNAME_CAPACITY);
		login:packed array[1..8] of char;
		marks:array[0..2] of CallMarks;
		
	end;
	PtrToStudent = ^Student;
	
	{*
		Secuencia de alumnos. Se utiliza de 0 a MAXINT puesto que al
		parecer no existe ninguna diferencia en cuanto a memoria utilizada
		por las secuencias de acceso secuencial y las de acceso aleatorio.
	*}
	StudentStream = bindable file[0..MAXINT] of Student;
	PtrToStudentStream = ^StudentStream;
	
	{*
		Variable para el volcado de la secuencia de alumnos
		a un array de puntero-a-alumno. Se utiliza un
		array pues no es recomendable hacer la ordenación
		en disco, y se utilizan punteros puesto que en
		la ordenación lo que se moverá serán los punteros y no
		la información a la que apuntan, moviendo así menos datos
		en memoria.
	*}
	Dump(eod:integer) = array[0..eod] of PtrToStudent;
	PtrToDump = ^Dump;
	
VAR
	
	{El último alumno encontrado por searchStudent.}
	beingEdited:integer;
	{Almacena la configuración de la aplicación.}
	config:array[0..1] of boolean;
	{Puntero a la secuencia que actúa como base de datos.}
	data:PtrToStudentStream;
	
{*
	Crea una secuencia de booleans y la liga con el fichero name
	(en disco, si el fichero no existe lo crea). La función devuelve
	un puntero a la secuencia creada o un puntero nulo si ha ocurrido
	algún error.
*}
function booleanStreamOpen(name:string):PtrToBooleanStream;

{*
	Desliga la secuencia f^ de su fichero en disco y libera
	el espacio de memoria asignado a la misma.
*}
procedure booleanStreamClose(f:PtrToBooleanStream);

{*
	Libera el espacio de memoria ocupado por el volcado link^.
*}
procedure disposeDump(link:PtrToDump);

{*
	Carga la configuración de la aplicación o la crea si no existe.
*}
procedure loadConfig;

{*
	Crea un volcado de la secuencia data^ (data es global) en link^.
*}
procedure newDump(var link:PtrToDump);

{*
	Busca un alumno cuyo login coincida con login y si existe devuelve 
	un puntero al mismo y modifica la variable beingEdited (global) con
	la posición de ese alumno en la base de datos. Si no existe devuelve
	un puntero nulo.
*}
function searchStudent(login:string):PtrToStudent;

{*
	Busca un alumno cuyo login coincida con login y si existe devuelve
	true y modifica la variable aux (global solo en la implementación
	del módulo) con la posición de ese alumno en la base de datos.
	Si no existe devuelve false.
*}
function studentExists(login:string):boolean;

{*
	Crea una secuencia de alumnos (Student) y la liga con el fichero
	name (en disco, si el fichero no existe lo crea). La función devuelve
	un puntero a la secuencia creada o un puntero nulo si ha ocurrido
	algún error.
*}
function studentStreamOpen(name:string):PtrToStudentStream;

{*
	Desliga la secuencia f^ de su fichero en disco y libera
	el espacio de memoria asignado a la misma.
*}
procedure studentStreamClose(f:PtrToStudentStream);

END.


MODULE filesystem IMPLEMENTATION;

IMPORT
	
	filteredinput;
	standardoutput;
	
CONST
	
	DATABASE_FILE_NAME	= './data';
	
VAR
	
	{*
		Contendrá el último alumno encontrado por studentExists
		(o el último alumno de la base de datos si la última
		llamada a studentExists devolvió false).
	*}
	aux:integer;
	
function booleanStreamOpen;
var
	
	b:BindingType;
	f:PtrToBooleanStream;
	
begin
	
	if length(name) = 0 then booleanStreamOpen := NIL
	else
	begin
		
		new(f);
		
		unbind(f^);
		b := binding(f^);
		b.name := name;
		bind(f^, b);
		b := binding(f^);
		
		if b.bound then
		begin
			
			if not b.existing then rewrite(f^);
			
			booleanStreamOpen := f
			
		end
		else
		begin
			
			dispose(f);
			booleanStreamOpen := NIL
			
		end;
		
	end;
	
end;

procedure booleanStreamClose;
begin
	
	unbind(f^);
	dispose(f);
	
end;

procedure disposeDump;
var
	
	i:integer;
	
begin
	
	for i := 0 to link^.eod do dispose(link^[i]);
	
	dispose(link);
	
end;

procedure loadConfig;
var
	
	i:integer value 0;
	f:PtrToBooleanStream;
	c:char;
	
begin
	
	f := booleanStreamOpen('./config');
	
	if f = NIL then
	begin
		
		write('Error desconocido al intentar acceder al archivo ./config, ');
		writeln('no es posible cargar la configuración de la aplicación.');
		write('Pulse intro para continuar.');
		readChar(c);
		
	end
	else
	begin
		
		reset(f^);
		
		if eof(f^) then
		begin
			
			writeln;
			writeln('Configuración inicial del programa:');
			writeln;
			writeln('¿Debe mantenerse la nota de teoría entre convocatorias? [s/n] ');
			
			readChar(c);
			
			while not (c in YES_NO) do
			begin
				
				writeln('Solo se admiten los valores "s" o "n", para responder "Sí" o "No" a la pregunta, respectivamente.');
				writeln('¿Debe mantenerse la nota de teoría entre convocatorias? [s/n] ');
				readChar(c);
				
			end;
			
			if c in YES then config[0] := true
			else config[0] := false;
			
			writeln('¿Debe mantenerse la nota de la práctica entre convocatorias? [s/n] ');
			
			readChar(c);
			
			while not (c in YES_NO) do
			begin
				
				writeln('Solo se admiten los valores "s" o "n", para responder "Sí" o "No" a la pregunta, respectivamente.');
				writeln('¿Debe mantenerse la nota de la práctica entre convocatorias? [s/n] ');
				readChar(c);
				
			end;
			
			if c in YES then config[1] := true
			else config[1] := false;
			
			write(f^, config[0]);
			write(f^, config[1]);
			
		end
		else
		begin
			
			while (i < 2) and (not eof(f^)) do
			begin
				
				read(f^, config[i]);
				i := i + 1;
				
			end;
			
		end;
		
		booleanStreamClose(f);
		
	end;
	
end;

procedure newDump;
var
	
	i, l:integer;
	s:PtrToStudent;
	
begin
	
	reset(data^);
	
	l := lastPosition(data^);
	
	if l = -1 then link := NIL
	else
	begin
		
		new(link, l);
		
		for i := 0 to l do
		begin
			
			new(s);
			
			read(data^, s^);
			
			link^[i] := s;
			
		end;
		
	end;
	
end;

function searchStudent;
var
	
	exists:boolean;
	student:PtrToStudent;
	
begin
	
	exists := studentExists(login);
	
	if exists then
	begin
		
		new(student);
		
		beingEdited := aux;
		student^ := data^^;
		searchStudent := student;
		
	end
	else searchStudent := NIL;
	
end;

function studentExists;
var
	
	found:boolean value false;
	
begin
	
	reset(data^);
	aux := 0;
	
	while not found and not eof(data^) do
	begin
		
		if data^^.login = login then found := true
		else
		begin
			
			get(data^);
			aux := aux + 1;
			
		end;
		
	end;
	
	if found then studentExists := true
	else studentExists := false;
	
end;

function studentStreamOpen;
var
	
	b:BindingType;
	f:PtrToStudentStream;
	
begin
	
	if length(name) = 0 then studentStreamOpen := NIL
	else
	begin
		
		new(f);
		
		unbind(f^);
		b := binding(f^);
		b.name := name;
		bind(f^, b);
		b := binding(f^);
		
		if b.bound then
		begin
			
			if not b.existing then rewrite(f^);
			
			studentStreamOpen := f;
			
		end
		else
		begin
			
			dispose(f);
			studentStreamOpen := NIL
			
		end;
		
	end;
	
end;

procedure studentStreamClose;
begin
	
	unbind(f^);
	dispose(f);
	
end;

{*
	Al cargar por primera vez este módulo se carga la configuración
	de la aplicación y se crea un puntero global para acceder a la
	base de datos.
*}
to begin do
begin
	
	loadConfig;
	
	data := studentStreamOpen(DATABASE_FILE_NAME);
	
	if data = NIL then
	begin
		
		writeln('Error: No es posible acceder al archivo "', DATABASE_FILE_NAME, '", el programa se detendrá ahora.');
		halt;
		
	end;
	
end;

{*
	Al terminar el programa se "cierra" la base de datos.
*}
to end do studentStreamClose(data);

END.
