// ---------------------------------------------------------------------------
// - csys.cxx                                                                -
// - standard system library - c system function implementation              -
// ---------------------------------------------------------------------------
// - This program is free software;  you can redistribute it  and/or  modify -
// - it provided that this copyright notice is kept intact.                  -
// -                                                                         -
// - This program  is  distributed in  the hope  that it will be useful, but -
// - without  any  warranty;  without  even   the   implied    warranty   of -
// - merchantability or fitness for a particular purpose.  In no event shall -
// - the copyright holder be liable for any  direct, indirect, incidental or -
// - special damages arising in any way out of the use of this software.     -
// ---------------------------------------------------------------------------
// - copyright (c) 1999-2008 amaury darsch                                   -
// ---------------------------------------------------------------------------

#include "csys.hpp"
#include "cstr.hpp"
#include "cthr.hpp"
#include "csys.hxx"

namespace afnix {

  // static mutex creation function
  static void* mtx_create (void);
  // mutex for various calls
  static void* mtx = mtx_create ();

  // this function destroy the mutex at exit
  static void mtx_destroy (void) {
    c_mtxdestroy (mtx);
  }

  // this function initialize a mutex statically and register its
  // destruction to be done at exit
  static void* mtx_create (void) {
    void* mtx = c_mtxcreate ();
    c_atexit (mtx_destroy);
    return mtx;
  }

  // the program name
  static char* pgm = nilp;

  // this function destroy the program name
  static void pgm_destroy (void) {
    delete [] pgm;
  }

  // return true if the system is 32 bit

  bool c_is32 (void) {
    return (sizeof (long) == 4);
  }

  // return true if the system is 64 bit

  bool c_is64 (void) {
    return (sizeof (long) == 8);
  }

  // return a unique id

  t_long c_uniqid (void) {
    // the global unique id
    static t_long uniqid = 0;
    // lock the mutex before access
    c_mtxlock (mtx);
    t_long result = ++uniqid;
    c_mtxunlock (mtx);
    return result;
  }

  // set the program name

  void c_setpgm (const char* name) {
    if (pgm == nilp) c_atexit (pgm_destroy);
    if (pgm != nilp) delete [] pgm;
    pgm = c_strdup (name);
  }

  // return the program name
  
  char* c_getpgm (void) {
    return c_strdup (pgm);
  }

  // return a temporary name

  char* c_tempnam (const char* prefix) {
    static long tcnt = 0;
    // lock the mutex
    c_mtxlock (mtx);
    // control the final prefix
    char* base = c_strtrc (prefix, 64);
    char buffer[512];    
    sprintf (buffer, "%s-%ld-%ld", base, c_getpid (), tcnt++);
    delete [] base;
    // unlock the mutex
    c_mtxunlock (mtx);
    return c_strdup (buffer);
  }

  // return the os name
  const char* c_osname (void) {
    return AFNIX_PLATFORM_NAME;
  }

  // return the os type

  const char* c_ostype (void) {
    return AFNIX_PLATFORM_TYPE;
  }

  // return the native option separator

  const char c_optsep (void) {
    return AFNIX_PLATFORM_OSEP;
  }

  // exit without executing the register code

  void c_abort (void) {
    _exit (1);
  }

  // exit unconditionnaly with a status code

  void c_exit (int status) {
    exit (status);
  }

  // register a function to execute at exit

  void c_atexit (void (*func) (void)) {
    atexit (func);
  }

  // return the process id

  long c_getpid (void) {
    return getpid ();
  }

  // return an environment variable value

  const char* c_getenv (const char* name) {
    if (c_strlen (name) == 0) return nilp;
    return getenv (name);
  }
}

#ifdef AFNIX_HAVE_HOSTNAME
namespace afnix {
  // return the host name
  char* c_hostname (void) {
    char name[1024];
    if (gethostname (name,1024) != 0) return nilp;
    return c_strdup (name);
  }
}
#endif

#ifdef AFNIX_HAVE_SYSINFO
namespace afnix {
  // return the host name
  char* c_hostname (void) {
    char name[1024];
    if (sysinfo (SI_HOSTNAME, name,1024) == -1) return nilp;
    return c_strdup (name);
  }
}
#endif

#ifdef AFNIX_HAVE_UID
namespace afnix {
  // return the user name
  char* c_username (void) {
    // get the user id
    uid_t uid = getuid ();
    // get the password line entry
    struct passwd* data = getpwuid (uid);
    if (data == nilp) return c_strdup (c_getenv ("USER"));
    return c_strdup (data->pw_name);
  }
}
#endif

#ifdef AFNIX_HAVE_BACKTRACE
#ifdef  __GNUC__
#define GET_CALLER_FRAME __builtin_frame_address(1);
#else
#define GET_CALLER_FRAME 0
#endif

namespace afnix {
  // the begining of the frame
  struct s_frame {
    s_frame*    p_prev;
    void*       p_pc;
  };

  struct s_finfo {
    void*       p_pc;
    int         d_fidx;
    const char* p_name;
    s_finfo*    p_prev;
    // simple constructor
    s_finfo (void) {
      p_pc   = 0;
      p_name = 0;
      p_prev = 0;
      d_fidx = 0;
    }
    // simple destructor
    ~s_finfo (void) {
      delete p_name;
      delete p_prev;
    } 
  };

  // this function resolve the pc name from its address
  static inline const char* get_pc_name (void* pc) {
    if (pc == 0) return 0;
    Dl_info dlinfo;
    if (dladdr (pc, &dlinfo) == 0) return 0;
    return c_strdup (dlinfo.dli_sname);
  }

  // return the backtrace of the calling function
  void* c_backtrace (void) {
    // get the caller frame
    s_frame* frame = (s_frame*) GET_CALLER_FRAME;
    s_finfo* finfo = 0;
    int      index = 0;

    // loop in the frame
    while (frame != 0) {
      s_finfo* data = new s_finfo;
      data->p_pc   = frame->p_pc;
      data->p_name = get_pc_name (data->p_pc);
      data->p_prev = finfo;
      data->d_fidx = index++;
      finfo = data;
      frame = frame->p_prev;
      if (data->p_name == 0) break;
    }
    return finfo;
  }

  // print the stack trace
  void c_printtrace (void* bptr) {
    if (bptr == 0) return;
    // get to the first frame
    s_finfo* finfo = (s_finfo*) bptr;
    if (finfo->p_prev != 0) c_printtrace (finfo->p_prev);
    // print frame info
    int fidx = finfo->d_fidx;
    if (fidx == 0)
      fprintf (stderr, "\n\t%d: %s\n", fidx, finfo->p_name);
    else
      fprintf (stderr, "\t%d: %s\n", fidx, finfo->p_name);
  }

  // destroy a stack trace
  void c_destroytrace (void* bptr) {
    if (bptr == 0) return;
    s_finfo* finfo = (s_finfo*) bptr;
    delete finfo;
  }
}
#else
namespace afnix {
  // return the stack trace
  void* c_backtrace (void) {
    return 0;
  }

  // print the stack trace
  void c_printtrace (void*) {
    return;
  }

  // destroy a stack trace
  void c_destroytrace (void*) {
    return;
  }
}
#endif
