/* Objective Modula-2 Compiler (objm2c)
 *
 *  @file objm2_filename.c
 *  portable filename function implementation
 *
 *  Compose and decompose pathnames and test file extensions
 *
 *  Author: Benjamin Kowarsch
 *
 *  Copyright (C) 2009 The Objective Modula-2 Project. All rights reserved.
 *
 *  License:
 *
 *  Redistribution  and  use  in source  and  binary forms,  with  or  without
 *  modification, are permitted provided that the following conditions are met
 *
 *  1) This file,  or any part thereof,  may  NOT  be hosted on websites which
 *     contain advertising,  unless specific prior written permission has been
 *     obtained.  The licensor will grant such permission  upon request at its
 *     sole discretion to websites the licensor does  NOT  consider abusive in
 *     their  use  of advertising.  Small notices  in the footer  of a website
 *     naming corporate rights holders,  infrastructure providers  or sponsors
 *     are not considered advertising in the context of this license.
 *
 *  2) Redistributions  of source code must retain the above copyright notice,
 *     this list of conditions and the following disclaimer.
 *
 *  3) Redistributions  in binary form  must  reproduce  the  above  copyright
 *     notice,  this list of conditions  and  the following disclaimer  in the
 *     documentation and other materials provided with the distribution.
 *
 *  4) Neither the author's name nor the names of any contributors may be used
 *     to endorse  or  promote  products  derived  from this software  without
 *     specific prior written permission.
 *
 *  5) Where this list of conditions  or  the following disclaimer, in part or
 *     as a whole is overruled  or  nullified by applicable law, no permission
 *     is granted to use the software.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING,  BUT NOT LIMITED TO,  THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY  AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT  SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE  FOR  ANY  DIRECT,  INDIRECT,  INCIDENTAL,  SPECIAL,  EXEMPLARY,  OR
 * CONSEQUENTIAL  DAMAGES  (INCLUDING,  BUT  NOT  LIMITED  TO,  PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES;  LOSS OF USE,  DATA,  OR PROFITS; OR BUSINESS
 * INTERRUPTION)  HOWEVER  CAUSED  AND ON ANY THEORY OF LIABILITY,  WHETHER IN
 * CONTRACT,  STRICT LIABILITY,  OR TORT  (INCLUDING NEGLIGENCE  OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,  EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 *
 *
 * TO DO : Move function objm2_working_directory() into a separate module
 *         Add minimal perfect hash for supported file extensions
 *         Add code to check well formedness of directory strings
 */

#include "objm2_filename.h"

#include "ASCII.h"
#include "objm2_hash.h"
#include "objm2_alloc.h"
#include <limits.h>
#include <unistd.h>


// ---------------------------------------------------------------------------
// Maximum pathname length
// ---------------------------------------------------------------------------
// On some operating systems,  MAX_PATH may not be defined.

#ifdef MAX_PATH // in limits.h
#define OBJM2_MAX_PATH_LENGTH MAX_PATH
#elif
#warning MAX_PATH is undefined, using default value of 511
#define OBJM2_MAX_PATH_LENGTH 511
#endif


// ---------------------------------------------------------------------------
// Maximum filename length
// ---------------------------------------------------------------------------
// On some operating systems,  MAX_NAME may not be defined.

#ifdef MAX_NAME // in limits.h
#define OBJM2_MAX_FILENAME_LENGTH MAX_NAME
#elif
#warning MAX_NAME is undefined, using default value of 127
#define OBJM2_MAX_FILENAME_LENGTH 127
#endif


// ---------------------------------------------------------------------------
// Check for presence of getcwd() or _getcwd()
// ---------------------------------------------------------------------------
// On some operating systems,  getcwd() may be called _getcwd() instead 

#if !defined(getcwd) && defined(_getcwd)
#define getcwd _getcwd
#elif
#error function getcwd() is undefined, this should have been in unistd.h
#endif


// ---------------------------------------------------------------------------
// Default filenaming scheme
// ---------------------------------------------------------------------------
//
// If any of  ISO9660, POSIX, WINDOWS or WIN, MSDOS or DOS, OPENVMS or VMS are
// defined  in the  compile  time  environment  (usually via CFLAGS)  then the
// default filenaming is set accordingly.  If  none of the symbols are defined
// then the default filenaming is set to POSIX_FILENAMING.

static const objm2_filenaming_t _default_filenaming =
#if defined(ISO9660)
    ISO9660_FILENAMING ;
#elif defined(POSIX)
    POSIX_FILENAMING ;
#elif defined(WINDOWS) || defined(WIN)
    MSDOS_FILENAMING ;
#elif defined(MSDOS) || defined(DOS)
    MSDOS_FILENAMING ;
#elif defined(OPENVMS) || defined(VMS)
    OPENVMS_FILENAMING ;
#else
#warning OS environment undefined, default filenaming set to POSIX_FILENAMING
    POSIX_FILENAMING ;
#endif


// ---------------------------------------------------------------------------
// Hash values of filename extensions for input files
// ---------------------------------------------------------------------------

#define OBJM2_EXT_DEF_UPPER  0x21C12F85 /* DEF */
#define OBJM2_EXT_DEF_CAMEL  0x21E13785 /* Def */
#define OBJM2_EXT_DEF_LOWER  0x31A327A5 /* def */
#define OBJM2_EXT_MOD_UPPER  0x2639BD82 /* MOD */
#define OBJM2_EXT_MOD_CAMEL  0x2659C582 /* Mod */
#define OBJM2_EXT_MOD_LOWER  0x361BB5A2 /* mod */
#define OBJM2_EXT_PROT_UPPER 0x32D146C7 /* PROT */
#define OBJM2_EXT_PROT_CAMEL 0x42B33EE7 /* Prot */
#define OBJM2_EXT_PROT_LOWER 0x138D56C7 /* prot */


// ---------------------------------------------------------------------------
// File extension strings
// ---------------------------------------------------------------------------

static const char *_ext_string[] = {
    // dummy entry
    "\0",
    // source file extensions
    "def\0", "mod\0", "prot\0",
    // output file extensions
    "ast\0", "sym\0", "h\0", "c\0", "mm\0", "llvm\0"
}; /* _ext_string */

#define _MAX_EXTSTR_SIZE 5


// ---------------------------------------------------------------------------
// Delimiter set type
// ---------------------------------------------------------------------------

typedef struct /* delimiter_set */ {
    char for_filename;
    char for_file_extension;
    char for_file_version;
} delimiter_set;


// ---------------------------------------------------------------------------
// Delimiter sets
// ---------------------------------------------------------------------------

static const delimiter_set _delimiter[] = {
    // dummy entry
    { CSTRING_TERMINATOR, CSTRING_TERMINATOR, CSTRING_TERMINATOR },
    // delimiters for POSIX
    { '/', '.', CSTRING_TERMINATOR },
    // delimiters for ISO9660
    { '/', '.', ';' },
    // delimiters for MSDOS
    { '\\', '.', CSTRING_TERMINATOR },
    // delimiters for OPENVMS
    { ']', '.', ';' }
}; /* _delimiter */


// ---------------------------------------------------------------------------
// Filename descriptor
// ---------------------------------------------------------------------------

typedef struct /* objm2_filename_s */ {
    char *directory;
    char *filename;
    char *extension;
    cardinal path_length;
    objm2_file_type_t file_type;
    objm2_filenaming_t filenaming;
} objm2_filename_s;


// ---------------------------------------------------------------------------
// Buffer for pathname of working directory
// ---------------------------------------------------------------------------

static char *_working_directory[OBJM2_MAX_PATH_LENGTH + 1] = EMPTY_STRING;


// ---------------------------------------------------------------------------
// function:  objm2_working_directory()
// ---------------------------------------------------------------------------
//
// Obtains the  current working directory pathname  of the current process and
// returns an  immutable pointer  to the pathname string.  Subsequent calls to
// this function will always return the  same string  as the working directory
// is not supposed to change during the process' lifetime.

const char *objm2_working_directory() {
    if (_working_directory[0] == CSTRING_TERMINATOR)
        return (const char *)
            getcwd(_working_directory, OBJM2_MAX_PATH_LENGTH + 1);
    else
        return _working_directory
} // end objm2_working_directory


// ---------------------------------------------------------------------------
// function:  objm2_is_valid_filename_string( filename )
// ---------------------------------------------------------------------------
//
// Returns true if <filename> is a valid Objective Modula-2 filename,  returns
// false otherwise.  Characters  allowed  in  filenames are  letters,  digits,
// underscore and dollar.  However, filenames must not start with a digit. The
// syntax for filenames is the same as for Objective Modula-2 identifiers:
//
// filename := ( letter | "_" | "$" ) ( letter | digit | "_" | "$" )*

bool objm2_is_valid_filename_string(const char *filename) {
    int index;

    if ((IS_NOT_LETTER(filename[0])) &&
        (filename[0] != UNDERSCORE) && (filename[0] != DOLLAR))
        return false;
    
    index = 1;
    while (filename[index] != CSTRING_TERMINATOR) {
        if ((IS_NOT_ALPHANUM(filename[index])) &&
            (filename[0] != UNDERSCORE) && (filename[0] != DOLLAR))
            return false;
        index++;
    } // end while
        
    return true;
} // end objm2_is_valid_filename_string


// --------------------------------------------------------------------------
// function:  objm2_outfile_type_for( target, sourcefile_type )
// --------------------------------------------------------------------------
//
// Returns  the  output  file  type  associated  with  <sourcefile_type>  for
// <target>.  Returns FILE_TYPE_UNKNOWN if no associated file type exists.

objm2_file_type_t objm2_outfile_type_for(objm2_target_t target,
                                         objm2_file_type_t sourcefile_type) {
    switch (target) {
            
            // when using Objective-C source code generator
        case OBJM2_TARGET_OBJC :
            switch(sourcefile_type) {
                case FILE_TYPE_MOD :
                    return FILE_TYPE_MM;
                case FILE_TYPE_DEF :
                case FILE_TYPE_PROT :
                    return FILE_TYPE_H;
                default :
                    return FILE_TYPE_UNKNOWN;
            } // end switch
            
            // when using LLVM source code generator
        case OBJM2_TARGET_LLVM :
            switch(sourcefile_type) {
                case FILE_TYPE_MOD :
                    return FILE_TYPE_LLVM;
                case FILE_TYPE_DEF :
                case FILE_TYPE_PROT :
                    return FILE_TYPE_SYM;
                default :
                    return FILE_TYPE_UNKNOWN;
            } // end switch
            
            // no other targets are available
        default :
            return FILE_TYPE_UNKNOWN;
    } // end switch
} // end objm2_outfile_type_for


// ---------------------------------------------------------------------------
// function:  objm2_new_filename( dir, name, file_type, filenaming, status )
// ---------------------------------------------------------------------------
//
// Returns a newly allocated filename descriptor,  initialised with the passed
// in parameters.  The caller is responsible  for deallocating the descriptor.
// Returns NULL if  any  of <filename> and <file_type> is invalid.
//
// The status of the operation  is passed back in <status>,  unless  NULL  was
// passed in for <status>.

objm2_filename_t *objm2_new_filename(const char *directory,
                                     const char *filename,
                                     objm2_file_type_t file_type,
                                     objm2_filenaming_t filenaming,
                                     objm2_filename_status_t *status) {
    objm2_filename_s *new_filename;
    int size, index = 0, total_length = 0;
    char *fn_delimiter, *ext_delimiter, *ver_delimiter;
    
    // bail out if filename is invalid
    if ((filename == NULL) ||
        (objm2_is_valid_filename_string(filename) == false)) {
        ASSIGN_BY_REF(status, OBJM2_FILENAME_STATUS_INVALID_FILENAME);
        return NULL;
    } // end if
    
    // bail out if file type is invalid
    if ((file_type == FILE_TYPE_UNKNOWN) ||
        (file_type >= INVALID_FILENAME)) {
        ASSIGN_BY_REF(status, OBJM2_FILENAME_STATUS_INVALID_FILETYPE);
        return NULL;
    } // end if
    
    // use default if filenaming value is not specific or invalid
    if ((filenaming == DEFAULT_FILENAMING) ||
        (filenaming >= NUMBER_OF_FILENAMING_SCHEMES))
        filenaming = _default_filenaming;
    
    // determine delimiters
    fn_delimiter = _delimiter[filenaming].for_filename;
    ver_delimiter = _delimiter[filenaming].for_file_version;
    ext_delimiter = _delimiter[filenaming].for_file_extension;
    
    
    // allocate memory for filename descriptor
    new_filename =
        (objm2_filename_t *) OBJM2_ALLOCATE(sizeof(objm2_filename_t));
    
    // bail out if allocation failed
    if (new_filename == NULL) {
        ASSIGN_BY_REF(status, OBJM2_FILENAME_STATUS_ALLOCATION_FAILED);
        return NULL;
    } // end if
    
    // determine the allocation size of the directory string
    size = 0;
    while (directory[size] != CSTRING_TERMINATOR)
        size++;
    
    // allocate memory for directory string
    new_filename->directory = (char *) OBJM2_ALLOCATE(size);
    
    // bail out if allocation failed
    if (new_filename->directory == NULL) {
        OBJM2_DEALLOCATE(new_filename);
        ASSIGN_BY_REF(status, OBJM2_FILENAME_STATUS_ALLOCATION_FAILED);
        return NULL;
    } // end if
    
    // copy the directory string
    index = 0;
    while (index < size) {
        new_filename->directory[index] = directory[index];
        index++;
    } // end while
        
    // accumulated length
    total_length = index;
    
    // terminate the directory string
    new_filename->directory[index] = CSTRING_TERMINATOR;
    
    
    // determine the allocation size of the filename string
    size = 0;
    while (filename[size] != CSTRING_TERMINATOR)
        size++;
    
    // allocate memory for the filename string
    new_filename->filename = (char *) OBJM2_ALLOCATE(size);
    
    // bail out if allocation failed
    if (new_filename->filename == NULL) {
        OBJM2_DEALLOCATE(new_filename->directory);
        OBJM2_DEALLOCATE(new_filename);
        ASSIGN_BY_REF(status, OBJM2_FILENAME_STATUS_ALLOCATION_FAILED);
        return NULL;
    } // end if
    
    // copy the filename string
    index = 0;
    while (index < size) {
        new_filename->filename[index] = filename[index];
        index++;
    } // end while
    
    // accumulated length
    total_length = total_length + index;
    
    // terminate the filename string
    new_filename->filename[index] = CSTRING_TERMINATOR;

    
    // allocate memory for the file extension string
    new_filename->extension = (char *) OBJM2_ALLOCATE(_MAX_EXTSTR_SIZE);
    
    // bail out if allocation failed
    if (new_filename->extension == NULL) {
        OBJM2_DEALLOCATE(new_filename->directory);
        OBJM2_DEALLOCATE(new_filename->filename);
        OBJM2_DEALLOCATE(new_filename);
        ASSIGN_BY_REF(status, OBJM2_FILENAME_STATUS_ALLOCATION_FAILED);
        return NULL;
    } // end if
    
    // copy the file extension string
    index = 0;
    while (_ext_string[file_type][index] != CSTRING_TERMINATOR) {
        new_filename->extension[index] = _ext_string[file_type][index];
        index++;
    } // end while
    
    // accumulated length
    total_length = total_length + index;

    // terminate the file extension string
    new_filename->extension[index] = CSTRING_TERMINATOR;
    
    // calculate and set the path name length
    new_filename->path_length = total_length + 1;
    
    // set the file type and filenaming scheme
    new_filename->file_type = file_type;
    new_filename->filenaming = filenaming;
    
    ASSIGN_BY_REF(status, OBJM2_FILENAME_STATUS_SUCCESS);
    return (objm2_filename_t) new_filename;
} // end objm2_new_filename


// ---------------------------------------------------------------------------
// function:  objm2_new_filename_from_path( path, filenaming, status )
// ---------------------------------------------------------------------------
//
// Returns a newly allocated filename descriptor,  initialised from path using
// filenaming scheme <filenaming>.  The caller is responsible for deallocating
// the descriptor.  The function tests the path for the presence of input file
// extensions and sets the filename descriptor's file_type field as follows:
//
// o  if  the  filename  consists  of nothing but the file extension  then the
//    descriptor's file_type field is set to INVALID_FILENAME.
//
// o  if the file extension is "DEF",  "Def"  or "def"  then  the descriptor's
//    file_type field is set to FILE_TYPE_DEF.
//
// o  if the file extension is "MOD",  "Mod"  or "mod"  then  the descriptor's
//    file_type field is set to FILE_TYPE_MOD.
//
// o  if the file extension is "PROT", "Prot" or "prot"  then the descriptor's
//    file_type field is set to FILE_TYPE_PROT.
//
// o  if the file extension matches none of the above file extensions then the
//    descriptor's file_type field is set to FILE_TYPE_UNKNOWN.
//
// For  any missing component  in <path>  an empty string  is  assigned to the
// string field representing  the missing component in the descriptor.  If all
// components are missing in <path>,  no descriptor will be created  and  NULL
// will be returned.
//
// The status of the operation  is passed back in <status>,  unless  NULL  was
// passed in for <status>.

objm2_filename_t objm2_new_filename_from_path(const char *path,
                                              objm2_filenaming_t filenaming,
                                              objm2_filename_status_t *status) {
    objm2_filename_s *new_filename;
    int index, path_index, fn_index, ext_index;
    int end_of_path, length, total_length;
    cardinal ext_hash = OBJM2_HASH_INITIAL;
    char *fn_delimiter, *ext_delimiter, *ver_delimiter;
    
    // bail out if there is no path string
    if ((path == NULL) || (path[0] == CSTRING_TERMINATOR)) {
        ASSIGN_BY_REF(status, OBJM2_FILENAME_STATUS_INVALID_PATHNAME);
        return NULL;
    } // end if

    // use default if filenaming value is not specific or invalid
    if ((filenaming == DEFAULT_FILENAMING) ||
        (filenaming >= NUMBER_OF_FILENAMING_SCHEMES))
        filenaming = _default_filenaming;
    
    // determine delimiters
    fn_delimiter = _delimiter[filenaming].for_filename;
    ver_delimiter = _delimiter[filenaming].for_file_version;
    ext_delimiter = _delimiter[filenaming].for_file_extension;
        
    // find end of path
    index = 0;
    fn_index = -1;
    ext_index = -1;
    while ((path[index] != CSTRING_TERMINATOR) &&
           (path[index] != ver_delimiter)) {
        if (path[index] == fn_delimiter)
            fn_index = index;
        if (path[index] == ext_delimiter)
            ext_index = index;
        index++;
    } // end while
    index--;
    end_of_path = index;
    
    if (end_of_path >= OBJM2_MAX_PATH_LENGTH) {
        ASSIGN_BY_REF(status, OBJM2_FILENAME_STATUS_PATHNAME_TOO_LONG);
        return NULL;
    } // end if
    
    // disregard ext delimiters in directory
    if (ext_index < fn_index)
        ext_index = -1;
    
    
    // length of directory string
    length = fn_index + 1;
    total_length = length;
    
    // allocate memory for directory string
    new_filename->directory = (char *) OBJM2_ALLOCATE(length + 1);
    
    // bail out if allocation failed
    if (new_filename->directory == NULL) {
        OBJM2_DEALLOCATE(new_filename);
        ASSIGN_BY_REF(status, OBJM2_FILENAME_STATUS_ALLOCATION_FAILED);
        return NULL;
    } // end if
        
    index = 0;
    if (length > 0) {
        // copy the directory string
        while (index < size) {
            new_filename->directory[index] = path[index];
            index++;
        } // end while
    } // end if
    
    // terminate the directory string
    new_filename->directory[index] = CSTRING_TERMINATOR;
    
    
    // length of filename string
    if (ext_index < 0)
        length = end_of_path - fn_index;
    else
        length = ext_index - fn_index - 1;
    total_length = total_length + length;

    
    // allocate memory for filename string
    new_filename->filename = (char *) OBJM2_ALLOCATE(length + 1);
    
    // bail out if allocation failed
    if (new_filename->filename == NULL) {
        OBJM2_DEALLOCATE(new_filename->directory);
        OBJM2_DEALLOCATE(new_filename);
        ASSIGN_BY_REF(status, OBJM2_FILENAME_STATUS_ALLOCATION_FAILED);
        return NULL;
    } // end if
    
    index = 0;
    path_index = fn_index + 1;
    if (length > 0) {
        // copy the filename string
        while (index < size) {
            new_filename->filename[index] = path[path_index];
            index++;
            path_index++;
        } // end while
    } // end if
    
    // terminate the filename string
    new_filename->filename[index] = CSTRING_TERMINATOR;
    
    
    // length of file extension string
    if (ext_index < 0)
        length = 0;
    else
        length = end_of_path - ext_index - 1;
    total_length = total_length + length;
    
    // allocate memory for file extension string
    new_filename->extension = (char *) OBJM2_ALLOCATE(length + 1);
    
    // bail out if allocation failed
    if (new_filename->filename == NULL) {
        OBJM2_DEALLOCATE(new_filename->directory);
        OBJM2_DEALLOCATE(new_filename->filename);
        OBJM2_DEALLOCATE(new_filename);
        ASSIGN_BY_REF(status, OBJM2_FILENAME_STATUS_ALLOCATION_FAILED);
        return NULL;
    } // end if
    
    index = 0;
    path_index = ext_index + 1;
    if (length > 0) {
        // copy the file extension string
        while (index < length) {
            new_filename->extension[index] = path[path_index];
            ext_hash = OBJM2_HASH_NEXT_CHAR(ext_hash, path[path_index]);
            index++;
            path_index++;
        } // end while
        ext_hash = OBJM2_HASH_FINAL(ext_hash);
    } // end if
    
    // terminate the file extension string
    new_filename->extension[index] = CSTRING_TERMINATOR;
    
    // calculate and set the path name length
    new_filename->path_length = total_length + 1;
    
    // determine and set the file type
    if ((new_filename->filename[0] == CSTRING_TERMINATOR) ||
        (new_filename->extension[0] == CSTRING_TERMINATOR))
        new_filename->file_type = INVALID_FILENAME;
    else if ((ext_hash == OBJM2_EXT_DEF_UPPER) ||
             (ext_hash == OBJM2_EXT_DEF_CAMEL) ||
             (ext_hash == OBJM2_EXT_DEF_LOWER))
        new_filename->file_type = FILE_TYPE_DEF;
    else if ((ext_hash == OBJM2_EXT_MOD_UPPER) ||
             (ext_hash == OBJM2_EXT_MOD_CAMEL) ||
             (ext_hash == OBJM2_EXT_MOD_LOWER))
        new_filename->file_type = FILE_TYPE_MOD;
    else if ((ext_hash == OBJM2_EXT_PROT_UPPER) ||
             (ext_hash == OBJM2_EXT_PROT_CAMEL) ||
             (ext_hash == OBJM2_EXT_PROT_LOWER))
        new_filename->file_type = FILE_TYPE_PROT;
    else
        new_filename->file_type = FILE_TYPE_UNKNOWN;
    
    // set the filenaming scheme
    new_filename->filenaming = filenaming;
    
    ASSIGN_BY_REF(status, OBJM2_FILENAME_STATUS_SUCCESS);
    return (objm2_filename_t) new_filename;
} // end objm2_new_filename_from_path


// ---------------------------------------------------------------------------
// function:  objm2_directory_string( filename )
// ---------------------------------------------------------------------------
//
// Returns  an  immutable pointer  to the  directory string  field of filename
// descriptor <filename>.  Returns NULL if the descriptor is NULL.

const char *objm2_directory_string(objm2_filename_t filename) {
    objm2_filename_s *this_filename = (objm2_filename_s *) filename;
    
    if (filename == NULL)
        return NULL;
    
    return (const char *) this_filename->directory;
} // end objm2_directory_string


// ---------------------------------------------------------------------------
// function:  objm2_filename_string( filename )
// ---------------------------------------------------------------------------
//
// Returns  an  immutable pointer  to the  filename string  field  of filename
// descriptor <filename>.  Returns NULL if the descriptor is NULL.

const char *objm2_filename_string(objm2_filename_t filename) {
    objm2_filename_s *this_filename = (objm2_filename_s *) filename;
    
    if (filename == NULL)
        return NULL;
    
    return (const char *) this_filename->filename;
} // end objm2_filename_string


// ---------------------------------------------------------------------------
// function:  objm2_file_ext_string( filename )
// ---------------------------------------------------------------------------
//
// Returns  an  immutable pointer  to the  file_ext string  field  of filename
// descriptor <filename>.  Returns NULL if the descriptor is NULL.

const char *objm2_file_ext_string(objm2_filename_t filename) {
    objm2_filename_s *this_filename = (objm2_filename_s *) filename;
    
    if (filename == NULL)
        return NULL;
    
    return (const char *) this_filename->extension;
} // end objm2_file_ext_string


// ---------------------------------------------------------------------------
// function:  objm2_file_type( filename )
// ---------------------------------------------------------------------------
//
// Returns the value of the file_type field of filename descriptor <filename>.
// Returns FILE_TYPE_UNKNOWN if the descriptor is NULL.

objm2_file_type_t objm2_file_type(objm2_filename_t filename) {
    objm2_filename_s *this_filename = (objm2_filename_s *) filename;
    
    if (filename == NULL)
        return FILE_TYPE_UNKNOWN;
    
    return this_filename->file_type;
} // end objm2_file_type


// ---------------------------------------------------------------------------
// function:  objm2_filenaming( filename )
// ---------------------------------------------------------------------------
//
// Returns the  filenaming scheme  of filename descriptor <filename>.  Returns
// DEFAULT_FILENAMING if the descriptor is NULL.

objm2_filenaming_t objm2_filenaming(objm2_filename_t filename) {
    objm2_filename_s *this_filename = (objm2_filename_s *) filename;
    
    if (filename == NULL)
        return DEFAULT_FILENAMING;
    
    return this_filename->filenaming;
} // end objm2_filenaming


// ---------------------------------------------------------------------------
// function:  objm2_path_from_filename( filename, status )
// ---------------------------------------------------------------------------
//
// Returns a newly allocated C string with a pathname composed from the fields
// in   filename   descriptor   <filename>.  The  caller  is  responsible  for
// deallocating the returned C string.
//
// The status of the operation  is passed back in <status>,  unless  NULL  was
// passed in for <status>.

const char objm2_path_from_filename(objm2_filename_t filename,
                                    objm2_filename_status_t *status) {
    
    objm2_filename_s *this_filename = (objm2_filename_s *) filename;
    int index, path_index = 0;
    char *path;
    
    // bail out if filename descriptor is NULL
    if (filename == NULL) {
        ASSIGN_BY_REF(status, OBJM2_FILENAME_STATUS_INVALID_REFERENCE);
        return NULL;
    } // end if
    
    // allocate memory for path string
    path = (char *) OBJM2_ALLOCATE(this_filename->path_length + 1);
    
    // bail out if allocation failed
    if (path = NULL) {
        ASSIGN_BY_REF(status, OBJM2_FILENAME_STATUS_ALLOCATION_FAILED);
        return NULL;
    } // end if
    
    // copy the directory string
    index = 0;
    while (this_filename->directory[index] != CSTRING_TERMINATOR) {
        path[path_index] = this_filename->directory[index];
        path_index++;
        index++;
    } // end while;

    // append the filename string
    index = 0;
    while (this_filename->filename[index] != CSTRING_TERMINATOR) {
        path[path_index] = this_filename->filename[index];
        path_index++;
        index++;
    } // end while;
    
    // append the file extension delimiter
    path[path_index] = _delimiter[this_filename->filenaming].for_file_extension;
    path_index++;
    
    // append the file extension string
    index = 0;
    while (this_filename->extension[index] != CSTRING_TERMINATOR) {
        path[path_index] = this_filename->extension[index];
        path_index++;
        index++;
    } // end while;
    
    // terminate the path string
    path[path_index] = CSTRING_TERMINATOR;
    
    ASSIGN_BY_REF(status, OBJM2_FILENAME_STATUS_SUCCESS);
    return (const char *) path;
} // end objm2_path_from_filename


// ---------------------------------------------------------------------------
// function:  objm2_dispose_filename( filename )
// ---------------------------------------------------------------------------
//
// Deallocates filename descriptor <filename> and all of its components.

void objm2_dispose_filename(objm2_filename_t filename) {
    objm2_filename_s *this_filename = (objm2_filename_s *) filename;
    
    if (filename == NULL)
        return;

    OBJM2_DEALLOCATE(this_filename->directory);
    OBJM2_DEALLOCATE(this_filename->filename);
    OBJM2_DEALLOCATE(this_filename->extension);
    OBJM2_DEALLOCATE(this_filename);
} // end objm2_dispose_filename


// END OF FILE
