/*
 * SPDX-FileCopyrightText: (c) 2009 RWTH Aachen University <rwth-aachen.de>
 * SPDX-FileCopyrightText: (c) 2009 Gesellschaft für numerische Simulation mbH Braunschweig <gns-mbh.com>
 * SPDX-FileCopyrightText: (c) 2009 Technische Universität Dresden <tu-dresden.de>
 * SPDX-FileCopyrightText: (c) 2009 University of Oregon <uoregon.edu>
 * SPDX-FileCopyrightText: (c) 2009 Forschungszentrum Jülich GmbH <fz-juelich.de>
 * SPDX-FileCopyrightText: (c) 2009 German Research School for Simulation Sciences GmbH
 * SPDX-FileCopyrightText: (c) 2009 Technische Universität München <tum.de>
 *
 * SPDX-License-Identifier: BSD-3-Clause
 *
 */

/**
 * @file
 *
 * Functions for file parsing in C.
 */

#include <config.h>
#include <UTILS_IO.h>

#include <stdarg.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>

#if HAVE( UNISTD_H )
#include <unistd.h>
#endif

#include <utils_package.h>

#if !HAVE( POSIX_GETCWD )
#include <UTILS_CStr.h>
#endif
#if HAVE( PLATFORM_MINGW )
#include <windows.h>
#endif

#define BUFFER_SIZE 1024

PACKAGE_ErrorCode
UTILS_IO_GetLine( char** buffer, size_t* buffer_size, FILE* file )
{
    UTILS_ASSERT( buffer );

    if ( *buffer == NULL || *buffer_size == 0 )
    {
        *buffer = ( char* )realloc( *buffer, BUFFER_SIZE );
        if ( *buffer == NULL )
        {
            *buffer_size = 0;
            UTILS_ERROR_POSIX();
            return PACKAGE_ERROR_MEM_ALLOC_FAILED;
        }
        *buffer_size = BUFFER_SIZE;
    }

    **buffer = '\0';

    if ( !fgets( *buffer, *buffer_size, file ) )
    {
        if ( feof( file ) )
        {
            return PACKAGE_ERROR_END_OF_BUFFER;
        }
        UTILS_ERROR_POSIX( "Error while reading from file" );
        return PACKAGE_ERROR_FILE_INTERACTION;
    }

    while ( strlen( *buffer ) == *buffer_size - 1 )
    {
        *buffer_size += BUFFER_SIZE;
        *buffer       = ( char* )realloc( *buffer, *buffer_size );
        if ( *buffer == NULL )
        {
            *buffer_size = 0;
            UTILS_ERROR_POSIX( "Failed to increase memory for string buffer" );
            return PACKAGE_ERROR_MEM_ALLOC_FAILED;
        }
        if ( !fgets( &( *buffer )[ *buffer_size - BUFFER_SIZE - 1 ],
                     BUFFER_SIZE + 1, file ) )
        {
            if ( feof( file ) )
            {
                return PACKAGE_ERROR_END_OF_BUFFER;
            }
            UTILS_ERROR_POSIX( "Error while reading from file" );
            return PACKAGE_ERROR_FILE_INTERACTION;
        }
    }

    return PACKAGE_SUCCESS;
}

bool
UTILS_IO_HasPath( const char* path )
{
    UTILS_ASSERT( path );
    return strcspn( path, "/" ) < strlen( path );
}

const char*
UTILS_IO_GetWithoutPath( const char* path )
{
    UTILS_ASSERT( path );

    int len = strlen( path );
    int pos;

    for ( pos = len - 1; pos >= 0; pos-- )
    {
        if ( path[ pos ] == '/' )
        {
            return &( path[ pos + 1 ] );
        }
    }
    return path;
}

void
UTILS_IO_SimplifyPath( char* path )
{
    UTILS_ASSERT( path );

    int  len                = 0;
    int  pos                = 0;
    int  cut_end            = 0;
    int  level              = 0;
    bool has_slashes        = false;
    bool has_starting_slash = ( path[ 0 ] == '/' );
    bool has_trailing_slash = false;

    /* Remove double slashes and '/./' sequences */
    for ( len = 0; path[ len ] != '\0'; len++ )
    {
        path[ len - level ] = path[ len ];
        if ( path[ len ] == '/' )
        {
            has_slashes = true;
        }
        if ( ( path[ len ] == '/' ) && ( path[ len + 1 ] == '/' ) )
        {
            level++;
        }
        if ( ( path[ len ] == '/' ) &&
             ( path[ len + 1 ] == '.' )  &&
             ( path[ len + 2 ] == '/' ) )
        {
            level += 2;
            len++;
        }
    }

    /* determine string length and terminate string */
    len -= level;
    if ( ( len > 0 ) && ( path[ len - 1 ] == '/' ) )
    {
        len--;
        has_trailing_slash = true;
    }
    path[ len ] = '\0';

    /* Remove /.. structures */
    level   = 0;
    cut_end = len;
    for ( pos = len - 1; pos >= 0; pos-- )
    {
        /* Check whether we encountered a /../ or /.. at the end of the path */
        if ( ( pos >= 3 ) &&
             ( ( path[ pos + 1 ] == '/' ) || ( path[ pos + 1 ] == '\0' ) ) &&
             ( path[ pos ] == '.' ) &&
             ( path[ pos - 1 ] == '.' ) &&
             ( path[ pos - 2 ] == '/' ) )
        {
            if ( level == 0 )
            {
                cut_end = pos + 1;
            }
            level++;
            pos = pos - 2;
        }

        /* If we find a slash */
        else if ( path[ pos ] == '/' )
        {
            if ( level > 0 )
            {
                level--;
                /* Cut piece out */
                if ( level == 0 )
                {
                    size_t diff = cut_end - pos;
                    len -= diff;
                    memmove( &path[ pos ], &path[ pos + diff ], len - pos + 1 );
                    cut_end = pos;
                }
            }
            else
            {
                cut_end = pos;
            }
        }
    }

    /* If string does not start with a slash, process first path item */
    if ( ( path[ 0 ] != '/' ) && ( level > 0 ) )
    {
        /* Special handling for path starting with '../' */
        if ( ( path[ 0 ] == '.' ) &&
             ( path[ 1 ] == '.' ) &&
             ( path[ 2 ] == '/' ) )
        {
            level++;
        }
        /* Ignore leading './' */
        else if ( ( path[ 0 ] != '.' ) ||
                  ( path[ 1 ] != '/' ) )
        {
            level--;
        }

        /* start with leading ../ sequence for each remaining level */
        pos = 0;
        while ( pos < level * 3 )
        {
            path[ pos ]     = '.';
            path[ pos + 1 ] = '.';
            path[ pos + 2 ] = '/';
            pos            += 3;
        }

        /* Cut piece out */
        cut_end++;  /* omit leading slash */
        size_t diff = cut_end - pos;
        len -= diff;
        memmove( &path[ pos ], &path[ pos + diff ], len - pos + 1 );
        if ( len < 0 )
        {
            path[ 0 ] = '\0';
        }
    }

    /* Add trailing slash again */
    if ( has_trailing_slash )
    {
        path[ len ]     = '/';
        path[ len + 1 ] = '\0';
    }

    /* Add starting slashed if string empty */
    if ( has_starting_slash && ( path[ 0 ] == '\0' ) )
    {
        path[ 0 ] = '/';
        path[ 1 ] = '\0';
    }

    /* Ensure it still has slashes if it had some before */
    if ( has_slashes && !UTILS_IO_HasPath( path ) && ( path[ 0 ] != '\0' ) )
    {
        memmove( &path[ 2 ], &path[ 0 ], len + 1 );
        path[ 0 ] = '.';
        path[ 1 ] = '/';
    }
}

char*
UTILS_IO_JoinPath( int nPaths, ... )
{
    char*       result_path;
    size_t      total_length  = 0;
    const char* sep           = "";
    int         last_absolute = 0;

    va_list paths;

    va_start( paths, nPaths );
    for ( int i = 0; i < nPaths; i++ )
    {
        const char* path = va_arg( paths, const char* );
        if ( !path )
        {
            va_end( paths );
            return NULL;
        }

        /* ignore empty path elements */
        size_t path_len = strlen( path );
        if ( 0 == path_len )
        {
            continue;
        }

        /* check if path is absolute */
        if ( path[ 0 ] == '/' )
        {
            /* Yes, reset counters, and remember the position */
            total_length  = 0;
            sep           = "";
            last_absolute = i;
        }

        total_length += strlen( sep ) + path_len;
        sep           = "/";
    }
    va_end( paths );

    /* include the terminating \0 */
    total_length += 1;

    result_path = malloc( total_length * sizeof( char ) );
    if ( !result_path )
    {
        return NULL;
    }

    va_start( paths, nPaths );
    total_length = 0;
    sep          = "";
    for ( int i = 0; i < nPaths; i++ )
    {
        const char* path = va_arg( paths, const char* );

        /* ignore path element, if an absolute path comes after */
        if ( i < last_absolute )
        {
            continue;
        }

        size_t path_len = strlen( path );
        if ( 0 == path_len )
        {
            continue;
        }

        strcpy( result_path + total_length, sep );
        total_length += strlen( sep );
        sep           = "/";

        strcpy( result_path + total_length, path );
        total_length += path_len;
    }
    va_end( paths );

    /* always terminate result string */
    result_path[ total_length ] = 0;

    return result_path;
}

/* Declare gethostname if we can link against it but it is not declared */
#if ( !HAVE( C_DECL_POSIX_GETHOSTNAME ) ) && HAVE( POSIX_GETHOSTNAME )
int
gethostname( char*  name,
             size_t namelen );

#endif

PACKAGE_ErrorCode
UTILS_IO_GetHostname( char* name, size_t namelen )
{
#if HAVE( POSIX_GETHOSTNAME )
    if ( gethostname( name, namelen ) )
    {
        return PACKAGE_ABORT;
    }

    return PACKAGE_SUCCESS;
#elif HAVE( PLATFORM_MINGW )
    TCHAR computer_name[ MAX_COMPUTERNAME_LENGTH + 1 ];
    DWORD computer_name_len = sizeof( computer_name ) / sizeof( computer_name[ 0 ] );
    if ( !GetComputerName( computer_name, &computer_name_len ) )
    {
        return PACKAGE_ABORT;
    }

    strncpy( name, computer_name, namelen );
    return PACKAGE_SUCCESS;
#else
    char* hostname = getenv( "HOST" );
    if ( ( hostname == NULL ) || ( *hostname == '\0' ) )
    {
        return PACKAGE_ABORT;
    }

    strncpy( name, hostname, namelen );
    return PACKAGE_SUCCESS;
#endif
}

/* Declare getcwd if we can link against it but it is not declared */
#if ( !HAVE( C_DECL_POSIX_GETCWD ) ) && HAVE( POSIX_GETCWD )
char*
getcwd( char*  buf,
        size_t size );

#endif


char*
UTILS_IO_GetCwd( char* buf, size_t size )
{
#if HAVE( POSIX_GETCWD )
    return getcwd( buf, size );
#else
    char* cwd = getenv( "PWD" );
    if ( cwd == NULL )
    {
        errno = EACCES;
        return NULL;
    }
    if ( buf == NULL )
    {
        return UTILS_CStr_dup( cwd );
    }

    if ( size == 0 )
    {
        errno = EINVAL;
        return NULL;
    }

    size_t len = strlen( cwd );
    if ( len + 1 > size )
    {
        errno = ERANGE;
        return NULL;
    }

    strcpy( buf, cwd );
    return buf;
#endif
}

PACKAGE_ErrorCode
UTILS_IO_FileCopy( const char* sourceFileName, const char* destFileName )
{
    char   buf[ BUFSIZ ];
    size_t size;

    FILE* source = fopen( sourceFileName, "rb" );
    FILE* dest   = fopen( destFileName, "wb" );
    if ( !source || !dest )
    {
        UTILS_ERROR_POSIX( "Error opening file" );
        return PACKAGE_ERROR_FILE_CAN_NOT_OPEN;
    }

    while ( ( size = fread( buf, 1, BUFSIZ, source ) ) )
    {
        fwrite( buf, 1, size, dest );
    }

    fclose( source );
    fclose( dest );

    return PACKAGE_SUCCESS;
}
