/************************************************************************
 *
 * util.cpp - Utility function definitions for the exec utility
 *
 * $Id: util.cpp 590510 2007-10-30 23:30:35Z sebor $
 *
 ************************************************************************
 *
 * Licensed to the Apache Software  Foundation (ASF) under one or more
 * contributor  license agreements.  See  the NOTICE  file distributed
 * with  this  work  for  additional information  regarding  copyright
 * ownership.   The ASF  licenses this  file to  you under  the Apache
 * License, Version  2.0 (the  "License"); you may  not use  this file
 * except in  compliance with the License.   You may obtain  a copy of
 * the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the  License is distributed on an  "AS IS" BASIS,
 * WITHOUT  WARRANTIES OR CONDITIONS  OF ANY  KIND, either  express or
 * implied.   See  the License  for  the  specific language  governing
 * permissions and limitations under the License.
 * 
 **************************************************************************/

#include <assert.h> /* for assert */
#include <errno.h> /* for errno */
#include <signal.h> /* for sigaction(), signal() */
#include <stdio.h> /* for vfprintf */
#include <stdlib.h> /* for exit, malloc */
#include <stdarg.h> /* for va_* */
#include <string.h> /* for strerror */

#include <sys/stat.h> /* for stat() */
#include <sys/types.h> /* for size_t */

#ifndef _WIN32
#  include <unistd.h> /* for sleep() */
#else
#  include <windows.h> /* for Sleep() */
#endif   /* _WIN{32,64} */


#include "cmdopt.h" /* for exe_name, target_name */

#include "util.h"


#ifdef _WIN32
#  define DEV_NULL   "NUL:"
#else
#  define DEV_NULL   "/dev/null"
#endif   // _WIN32


void
warn (const char* const format, ...)
{
    const char* const target_name = get_target ();
    va_list args;

    assert (0 != format);

    if (target_name)
        fprintf (stderr, "%s (%s): ", exe_name, target_name);
    else
        fprintf (stderr, "%s: ", exe_name);

    va_start (args, format);
    vfprintf (stderr, format, args);
    va_end (args);
}

void
terminate (const int state, const char* const format, ...)
{
    const char* const target_name = get_target ();
    va_list args;

    assert (0 != format);
    assert (0 != state);

    if (target_name)
        fprintf (stderr, "%s (%s): ", exe_name, target_name);
    else
        fprintf (stderr, "%s: ", exe_name);

    va_start (args, format);
    vfprintf (stderr, format, args);
    va_end (args);

    exit (state);
}

void*
guarded_malloc (const size_t size, const char* const file, const unsigned line)
{
    void* alloc;

    assert (0 != file);
    assert (0 < size);

    alloc = malloc (size);

    if (0 == alloc)
        terminate (1, "malloc (%lu) at line %u of %s failed: %s\n", 
                 (unsigned long)size, line, file, strerror (errno));

    return alloc;
}

void*
guarded_realloc (void* source, const size_t size, const char* const file, 
                 const unsigned line)
{
    void* alloc;

    assert (0 != file);
    assert (0 < size);

    alloc = realloc (source, size);

    if (0 == alloc)
        terminate (1, "malloc(%lu) at line %u of %s failed: %s\n", 
                 (unsigned long)size, line, file, strerror (errno));

    return alloc;
}

char*
reference_name (const char* data_dir, const char* subdir, const char* mode)
{
    size_t root_len, cmp_len, dir_len, mode_len, net_len;
    char* ref_name;
    char* tail;
    const char* const target_name = get_target ();

    assert (0 != target_name);
    assert (0 != subdir);
    assert (0 != mode);

    root_len = data_dir ? strlen (data_dir) : 0;
    cmp_len = strlen (target_name) - exe_suffix_len;
    dir_len = strlen (subdir);
    mode_len = strlen (mode);
    net_len = root_len + cmp_len + dir_len + mode_len * 2 + 5;
    /* 5 comes from 3 path seperator characters, the suffix seperator 
       character, and the trailing null */
    tail = ref_name = (char*)RW_MALLOC (net_len);

    memcpy (tail, data_dir, root_len);
    tail += root_len;
    *tail++ = default_path_sep;
    memcpy (tail , subdir, dir_len);
    tail += dir_len;
    *tail++ = default_path_sep;
    memcpy (tail , mode, mode_len);
    tail += mode_len;
    *tail++ = default_path_sep;
    memcpy (tail , target_name, cmp_len);
    tail += cmp_len;
    *tail++ = suffix_sep;
    memcpy (tail , mode, mode_len);
    tail += mode_len;
    *tail = '\0';

    return ref_name;
}


/**
   Composes the name of an input file, based on target

   Takes a data directory and an executable name, and tries to open an input 
   file based on these variables.  If a file is found in neither of two 
   locattions derived from these variables, this method tries to fall back on 
   /dev/null.

   Source file locations:
     - [data_dir]/manual/in/[target].in
     - [data_dir]/tutorial/in/[target].in
     - /dev/null

   @param data_dir the path of the reference data directory
   @param target the name of executable being run
   @returns the name of the file
*/
char*
input_name (const char* data_dir, const char* target)
{
    char* fname = 0;
    int stat_result = 0;
    struct stat sb;

    assert (0 != target);

    if (data_dir && *data_dir) {

        /* Try data_dir/manual/in/target.in */
        fname       = reference_name (data_dir, "manual", "in");
        stat_result = stat (fname, &sb);
    
        if (0 == stat_result)
            return fname;

        free (fname);

        /* Try data_dir/tutorial/in/target.in */
        fname       = reference_name (data_dir, "tutorial", "in");
        stat_result = stat (fname, &sb);

        if (0 == stat_result)
            return fname;

        free (fname);
    }

    /* If we didn't find a source file, use /dev/null */
    fname = (char*)RW_MALLOC (sizeof DEV_NULL);
    strcpy (fname, DEV_NULL);
    return fname;
}


char*
output_name (const char* target)
{
    const char* suffix = "out";
    const size_t sfx_len = strlen (suffix);
    const size_t exe_len = strlen (target) - exe_suffix_len;
    char* const tmp_name = (char*)RW_MALLOC (exe_len + sfx_len + 2);
    
    memcpy (tmp_name, target, exe_len);
    *(tmp_name + exe_len) = suffix_sep;
    memcpy (tmp_name + exe_len + 1, suffix, sfx_len + 1);
    return tmp_name;
}


#ifndef _WIN32

void
rw_sleep (int seconds)
{
    sleep (seconds);
}


#  ifndef _RWSTD_EDG_ECCP

#    ifdef __cplusplus

extern "C" {

#    endif   /* __cplusplus */

int
rw_signal (int signo, void (*func)(int))
{
    struct sigaction act;
    memset (&act, 0, sizeof act);

    /* avoid extern "C"/"C++" mismatch due to an HP aCC 6 bug
       (see STDCXX-291) */
    if (func)
        memcpy (&act.sa_handler, &func, sizeof func);
    else
        act.sa_handler = 0;

    return 0 > sigaction (signo, &act, 0);
}

#    ifdef __cplusplus

}   /* extern "C" */

#    endif   /* __cplusplus */

#  else   /* if defined (_RWSTD_EDG_ECCP) */

#    ifdef __cplusplus

extern "C" {

#    endif   /* __cplusplus */

int
rw_signal (int signo, void (*func)(int))
{
    return SIG_ERR == signal (signo, func);
}

#    ifdef __cplusplus

}   /* extern "C" */

#    endif   /* __cplusplus */
#  endif   /* _RWSTD_EDG_ECCP */
#else   /* if defined (_WIN32) || defined (_WIN64) */

void
rw_sleep (int seconds)
{
    Sleep (seconds * 1000);
}


int
rw_signal (int signo, void (*func)(int))
{
    return SIG_ERR == signal (signo, func);
}

#endif   /* _WIN32 */
