/**************************************************************************
 *{@C
 *	Copyright:	1988-2020 Paul Obermeier (obermeier@poSoft.de)
 *
 *	Module:		Utilities
 *	Filename:	UT_FileIO.c
 *
 *	Author:		Paul Obermeier
 *
 *	Description: 	Safe file IO wrapper class of fopen/fclose.
 *
 *	Additional documentation:
 *
 *	Exported functions:
 *			UT_FileOpen
 *			UT_FileClose
 *			UT_FileGetMaxOpenFiles
 *			UT_FileGetNumOpenFiles
 *			UT_FileCheck
 *
 **************************************************************************/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "UT_Compat.h"
#include "UT_Error.h"
#include "UT_Memory.h"
#include "UT_FileIO.h"

#define str_fopen          "Cannot open file \"%s\" in mode \"%s\""
#define str_fclose         "Cannot close file \"%s\""
#define str_max_open_files "Max. number of open files (%d) reached"
#define str_invalid_fp     "Trying to close an invalid filepointer"

#define MAX_OPEN_FILES FOPEN_MAX

typedef struct fileMgr {
    Int32 count;
    Int32 current;
    FILE *fPointer[MAX_OPEN_FILES];
    char *fName[MAX_OPEN_FILES];
} FileMgr;

static FileMgr this;

static Int32 findNextSlot (void)
{
    Int32 ind = this.current;
    while (this.fPointer[ind]) {
	ind = (ind +1) % MAX_OPEN_FILES;
	if ( ind == this.current) {
	    return -1;
	}
    }
    return ind;
}

static Int32 findFilePointer (FILE *fp)
{
    Int32 ind = this.current;
    while (this.fPointer[ind] != fp) {
	ind = (ind-1) < 0? MAX_OPEN_FILES-1: (ind-1);
	if ( ind == this.current) {
	    return -1;
	}
    }
    return ind;
}

static void moduleInit (void)
{
    this.count   = 0;
    this.current = 0;
    memset (this.fPointer, 0, MAX_OPEN_FILES * sizeof (FILE *));
    memset (this.fName,    0, MAX_OPEN_FILES * sizeof (char *));
    return;
}

/***************************************************************************
 *[@e
 *      Name:           UT_FileGetMaxOpenFiles
 *
 *      Usage:          Get the maximum number of concurrently open files
 *			supported by the operating system.
 *
 *      Synopsis:       Int32 UT_FileGetMaxOpenFiles (void)
 *
 *      Description:    
 *
 *      Return value:   The maximum number of open files.
 *
 *      See also:
 *
 ***************************************************************************/

Int32 UT_FileGetMaxOpenFiles (void)
{
    return MAX_OPEN_FILES;
}

/***************************************************************************
 *[@e
 *      Name:           UT_FileGetNumOpenFiles
 *
 *      Usage:          Get the number of currently opened files.
 *
 *      Synopsis:       Int32 UT_FileGetNumOpenFiles (void)
 *
 *      Description:    
 *
 *      Return value:   The number of currently opened files.
 *
 *      See also:
 *
 ***************************************************************************/

Int32 UT_FileGetNumOpenFiles (void)
{
    return this.count;
}

/***************************************************************************
 *[@e
 *      Name:           UT_FileCheck
 *
 *      Usage:          Check if files are still open.
 *
 *      Synopsis:       UT_Bool UT_FileCheck (void)
 *
 *      Description:    
 *
 *      Return value:   UT_True, if all files opened with this module have been
 *			closed. If there are still files open, UT_False is 
 *			returned and a list of the open file names is printed
 *			to stdout.
 *
 *      See also:
 *
 ***************************************************************************/

UT_Bool UT_FileCheck (void)
{
    Int32 i;

    if (this.count) {
        printf ("The following files are currently open\n");
	for (i=0; i<MAX_OPEN_FILES; i++) {
	    if (this.fPointer[i]) {
		printf ("\t%s\n", this.fName[i]);
	    }
	}
	return UT_False;
    }
    return UT_True;
}

/***************************************************************************
 *[@e
 *      Name:           UT_FileOpen
 *
 *      Usage:          Open a file.
 *
 *      Synopsis:       FILE *UT_FileOpen (const char *name, const char *mode)
 *
 *      Description:    The functionality is the same as "fopen" from the C
 *			standard library.
 *
 *      Return value:   The file pointer, if the file could be opened according
 *			to the supplied mode. NULL otherwise. 
 *
 *      See also:
 *
 ***************************************************************************/

FILE *UT_FileOpen (const char *name, const char *mode)
{
    Int32 current;
    FILE *fp;
    static UT_Bool init = UT_False;

    if (!init) {
	moduleInit ();
	init = UT_True;
    }
    current = this.current;
    if (this.count == MAX_OPEN_FILES) {
	UT_ErrSetNum (UT_ErrParamRange, str_max_open_files, MAX_OPEN_FILES);
	return NULL;
    }
    if (!(fp = fopen (name, mode))) {
	UT_ErrSetNum (UT_ErrFromOs (), str_fopen, name, mode);
	return NULL;
    }
    if ((current = findNextSlot ()) < 0) {
	/* This should never happen, because we already checked if there are
	 * free slots available: this.count == MAX_OPEN_FILES. */
	UT_ErrFatal ("UT_FileOpen", "Cannot find free slot");
    }
    if (!(this.fName[current] = UT_MemPermArray (strlen(name)+1, char *))) {
	fclose (fp);
	return NULL;
    }

    strcpy (this.fName[current], name);
    this.fPointer[current] = fp;
    this.current = current;
    this.count++;
    return fp;
}

/***************************************************************************
 *[@e
 *      Name:           UT_FileClose
 *
 *      Usage:          Close a file.
 *
 *      Synopsis:	UT_Bool UT_FileClose (FILE *fp)
 *
 *      Description:    The functionality is the same as "fclose" from the C
 *			standard library.
 *
 *      Return value:   UT_True, if the file could be closed and was opened
 *			with a call of UT_FileOpen. Otherwise UT_False.
 *
 *      See also:
 *
 ***************************************************************************/

UT_Bool UT_FileClose (FILE *fp)
{
    Int32 ind;

    if (!fp) {
	UT_ErrSetNum (UT_ErrParamInvalid, str_invalid_fp);
	return UT_False;
    }
 
    ind = findFilePointer (fp);
    if (ind < 0) {
	UT_ErrSetNum (UT_ErrParamInvalid, str_invalid_fp);
	return UT_False;
    }
    if (fclose (fp) != 0) {
	UT_ErrSetNum (UT_ErrFromOs (), str_fclose, this.fName[ind]);
	return UT_False;
    }
    this.fPointer[ind] = NULL;
    UT_MemFree (this.fName[ind]);
    this.fName[ind] = NULL;
    this.count--;
    return UT_True;
}
