
#ifdef _WIN32

# include <windows.h>
static char tempSpace[2048];

#else

#define __USE_GNU 1
#define _GNU_SOURCE
#include <dlfcn.h>

#endif

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

#ifdef _WIN32
#define strcasecmp stricmp
#else
#include <strings.h>
#endif

#define CG_EXPLICIT
#include <Cg/cg.h>

static void HandleOptions(int argc, char *argv[]);
static void PrintUsage(void);
static void ScanVersionString(const char* pVersionString);
static void DumpProfiles(void);

typedef const char * (*PFNCGGETSTRINGPROC)(CGenum);
typedef int (*PFNCGGETNUMSUPPORTEDPROFILESPROC)(void);
typedef CGprofile (*PFNCGGETSUPPORTEDPROFILEPROC)(int);
typedef const char * (*PFNCGGETPROFILESTRINGPROC)(CGprofile);

PFNCGGETSTRINGPROC cgGetString = (PFNCGGETSTRINGPROC)NULL;
PFNCGGETNUMSUPPORTEDPROFILESPROC cgGetNumSupportedProfiles;
PFNCGGETSUPPORTEDPROFILEPROC cgGetSupportedProfile;
PFNCGGETPROFILESTRINGPROC cgGetProfileString;

#ifdef _WIN32
static HMODULE g_pLibraryHandle = NULL;
#else
static void* g_pLibraryHandle = NULL;
#endif
static const char* g_pLibrary = "";
static const char* g_pVersion = NULL;
static const char* g_pLibraryName = NULL;
static const char* g_pExeName = NULL;
static int g_VersionMajor = 0;
static int g_VersionMinor = 0;
static int g_VersionRelease = 0;
static int g_DumpProfiles = 0;
static int g_MissingSymbol = 0;

#ifdef GET_PROCADDR
# undef GET_PROCADDR
#endif

#ifdef _WIN32

#define GET_PROCADDR(name, typename) \
   if ((name = (typename) GetProcAddress(g_pLibraryHandle, #name)) == NULL) \
   { \
     printf( "\nFailed to get entry point address:\n" \
             " Library = '%s'\n" \
             " Entry Point = '%s'\n", \
             g_pLibraryName, #name ); \
     g_MissingSymbol++; \
   }

#else

#define GET_PROCADDR(name, typename) \
   if ((name = (typename) dlsym(g_pLibraryHandle, #name)) == NULL) \
   { \
     printf( "\nFailed to get entry point address:\n" \
             " Library = '%s'\n" \
             " Entry Point = '%s'\n", \
             g_pLibraryName, #name ); \
     g_MissingSymbol++; \
   }

#endif

int main( int argc, char *argv[] )
{
  // library name is platform specific

#ifdef _WIN32
  g_pLibraryName = "cg.dll";
#else
#ifdef __APPLE__
  g_pLibraryName = "Cg.framework/Cg";
#else
  g_pLibraryName = "libCg.so";
#endif
#endif

  // if any arguments were passed, assume that the first
  // contains the path to the library of interest

  HandleOptions(argc, argv);

  // load the library

#ifdef _WIN32
  g_pLibraryHandle = LoadLibrary( g_pLibraryName );
  if ( g_pLibraryHandle == NULL ) {
    printf( "  failed to load library: %s\n", g_pLibraryName );
    FormatMessage( FORMAT_MESSAGE_FROM_SYSTEM, NULL,
                   GetLastError(), 0, tempSpace, 2048, NULL );
    printf( "  LoadLibrary error: %s", tempSpace );
    return( 1 );
  }
#else
  g_pLibraryHandle = dlopen( g_pLibraryName, RTLD_LAZY | RTLD_LOCAL );
  if ( g_pLibraryHandle == NULL ) {
    printf( "  failed to load library: %s\n", g_pLibraryName );
    printf( "  dlopen error: %s\n", dlerror() );
    return( 1 );
  }
#endif

  // get the address for cgGetString()

  GET_PROCADDR(cgGetString, PFNCGGETSTRINGPROC)

  if ( g_MissingSymbol ) {
    return( 1 );
  }

  // get the name of the library which satisfied this symbol.  this
  // is useful if the dynamic loader located the library by searching
  // along LD_LIBRARY_PATH, DYLD_LIBRARY_PATH, or etc.

#ifdef _WIN32
  {
    DWORD rVal = GetModuleFileName( g_pLibraryHandle, tempSpace, 2047 );
    if ( rVal ) {
      tempSpace[2047] = 0;
      g_pLibrary = tempSpace;
    }
  }
#else
  {
    Dl_info info;
    if ( dladdr( (void *) cgGetString, &info ) ) {
      g_pLibrary = info.dli_fname;
    }
  }
#endif

  // get version from library

  g_pVersion = cgGetString( CG_VERSION );

  ScanVersionString(g_pVersion);

  // print results

  printf( "  Library     = %s\n", g_pLibrary );
  printf( "  Version     = %s\n", ( g_pVersion ? g_pVersion : "" ) );
  printf( "  cgGetString = %p\n", cgGetString );

  DumpProfiles();

  return( 0 );
}

static void PrintUsage(void)
{
  printf( "Usage: %s [-profiles] [/path/to/library]\n", g_pExeName );
}

static void HandleOptions(int argc, char *argv[])
{
  int ii;

  // first argv is program name

  g_pExeName = argv[0];

  // last argv (if it doesn't begin with '-') is library name

  if ( argc > 1 && *(argv[argc-1]) != '-') {
    g_pLibraryName = argv[argc-1];
    argc--;
  }

  // Parse remaining arguments

  for (ii = 1; ii < argc; ii++) {
    if (!strcasecmp(argv[ii], "-profiles")) {
      g_DumpProfiles = 1;
    } else {
      PrintUsage();
      exit( 1 );
    }
  }
}

static void ScanVersionString(const char* pVersionString)
{
  char* pNext = NULL;
  g_VersionMajor = strtol( pVersionString, &pNext, 10 );
  pNext++;
  g_VersionMinor = strtol( pNext, &pNext, 10 );
  pNext++;
  g_VersionRelease = strtol( pNext, &pNext, 10 );
}

static void DumpProfiles(void)
{
  int ii;
  int nProfiles;
  CGprofile profile;

  // dump profiles when:
  //   option -profiles passed in command line, and
  //   library version is 2.2 or greater

  if ( !g_DumpProfiles ||
       (g_VersionMajor < 2) ||
       (g_VersionMajor == 2 && g_VersionMinor < 2) ) {
    return;
  }

  // get the addresses needed to dump profiles

  GET_PROCADDR(cgGetNumSupportedProfiles, PFNCGGETNUMSUPPORTEDPROFILESPROC)
  GET_PROCADDR(cgGetSupportedProfile, PFNCGGETSUPPORTEDPROFILEPROC)
  GET_PROCADDR(cgGetProfileString, PFNCGGETPROFILESTRINGPROC)

  if ( g_MissingSymbol ) {
    return;
  }

  nProfiles = cgGetNumSupportedProfiles();

  printf("\n  Supported profiles:\n\n");

  for (ii=0; ii<nProfiles; ++ii) {
    profile = cgGetSupportedProfile(ii);
    printf("     %i = %s\n", profile, cgGetProfileString(profile));
  }
}
