/*
   Copyright (C) 1998 T. Scott Dattalo

This file is part of gpsim.

gpsim is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2, or (at your option)
any later version.

gpsim 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.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with gpsim; see the file COPYING.  If not, write to
the Free Software Foundation, 59 Temple Place - Suite 330,
Boston, MA 02111-1307, USA.  */



/* os_dependent.cc  -                   */
/* version 0.1                          */

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

#include <iostream>
#include <iomanip>
#include <vector>
#include <algorithm>

#include "../config.h"
#include "exports.h"
#include "modules.h"

#ifdef _WIN32
#define G_PLATFORM_WIN32
#define G_OS_WIN32
#include <glib/gmem.h>
#include <glib/gwin32.h>
#include <direct.h>
#include <windows.h>

#define STRICMP stricmp

#else

#include <dlfcn.h>
#include <string.h>
#define STRICMP strcasecmp

#endif	// _WIN32

using namespace std;

#ifdef _WIN32
#define MODULE_EXT ".dll"
#define FOLDERDELIMITER '\\'
#define FOLDERDELIMITERALTERNATIVE '/'
#define PATHDELIMITER ";"
#else
#define MODULE_EXT ".so"
#define FOLDERDELIMITER '/'
#define FOLDERDELIMITERALTERNATIVE '\\'
#define PATHDELIMITER ":"
#endif

#ifdef _WIN32
#define OS_E_FILENOTFOUND 0x0000007E
#define OS_E_MEMORYACCESS 0x000003E6
#else
// JRH - just made a guess
#define OS_E_FILENOTFOUND ENOENT
#define OS_E_MEMORYACCESS EADDRNOTAVAIL
#include <errno.h>
#endif

//------------------------------------------------------------------------
// Convert forward slashes and backslashes into the os-dependent
// slash
void translatePath(string &sPath)
{
  string::size_type nPos;
  while ( (nPos = sPath.find(FOLDERDELIMITERALTERNATIVE)) != string::npos)
    sPath[nPos] = FOLDERDELIMITER;
}

//------------------------------------------------------------------------
// EnsureTrailingFolderDelimiter -- append a directory delimeter (slash)
// to the string if one is not present already.

void EnsureTrailingFolderDelimiter(string &sPath)
{
  string::reference rLast = sPath.at(sPath.size() - 1);
  if(rLast == FOLDERDELIMITERALTERNATIVE)
    rLast = FOLDERDELIMITER;
  else if(rLast != FOLDERDELIMITER)
    sPath.push_back(FOLDERDELIMITER);
}

//------------------------------------------------------------------------
bool LIBGPSIM_EXPORT IsFileExtension(const char *pszFile, const char *pFileExt)
{
  string s(pszFile);

  int i = s.rfind(pFileExt) ;
  
  return i>=0;

}

//------------------------------------------------------------------------
/// SplitPathAndFile()
/// Note this function does not verify whether the trailing
/// is actually a file component.
void SplitPathAndFile(string &sSource, string &sFolder, string &sFile) {
  translatePath(sSource);
  string::size_type LastDelimiter = sSource.find_last_of(FOLDERDELIMITER);
  if (LastDelimiter == string::npos) {
//    sFolder.erase();
    // JRH - I'm not sure this is a good assumption.
    // It will do for the one place it is currently being used.
    static char sCurrentFolder[] = { '.', FOLDERDELIMITER };
    sFolder.append(sCurrentFolder);
    sFile = sSource;
  }
  else {
    string sNewFolder;
    sFolder = sSource.substr(0, LastDelimiter + 1);
    sFile = sSource.substr(LastDelimiter + 1);
  }
}

const char * CFileSearchPath::Find(string &path) {
  const_iterator it = find(begin(), end(), path);
  if (it != end()) {
    return (*it).c_str();
  }
  return NULL;
}

static CFileSearchPath asDllSearchPath;
#if defined(_DEBUG)
static bool bAltPaths = false;
#endif
void AddModulePathFromFilePath(string &sFolder) {
  string sFile;
  asDllSearchPath.AddPathFromFilePath(sFolder, sFile);
  char * pszGpsimModulePath;
  if((pszGpsimModulePath = getenv("GPSIM_MODULE_PATH")) != NULL) {
    char * pLast = pszGpsimModulePath;
    char * pChar = strchr(pszGpsimModulePath, PATHDELIMITER[0]);
    string sFolder;
    while(true) {
      if(pChar != NULL) {
        *pChar = '\0';
      }
      if(*pLast != '\0') {
        // only add non empty folders
        sFolder = pLast;
        translatePath(sFolder);
        if(sFolder[sFolder.size() - 1] != FOLDERDELIMITER) {
          sFolder.push_back(FOLDERDELIMITER);
        }
        asDllSearchPath.push_back(sFolder);
      }
      if(pChar == NULL) {
        break;
      }
      pChar++;
      pLast = pChar;
      pChar = strchr(pChar, PATHDELIMITER[0]);
    }
  }
#if defined(_DEBUG)
  if(!bAltPaths) {
    bAltPaths = true;
    string sPath;
    int iPos = sFolder.find_last_of(FOLDERDELIMITER);
    if(iPos != string::npos) {
      char szLine[1024];
      sPath = sFolder.substr(0, iPos + 1);
      sPath.append("altpaths.txt");
      FILE *pFile = fopen(sPath.c_str(), "r");
      if(pFile) {
        while(fgets(szLine, 1024, pFile) != NULL) {
          char *pChar = &szLine[strlen(szLine) - 1];
          while((*pChar == '\n' || *pChar == '\n') && pChar != szLine)
            *pChar-- = 0;
          if(*pChar != FOLDERDELIMITER) {
            pChar++;
            *pChar = FOLDERDELIMITER;
            pChar++;
            *pChar = 0;
          }
          asDllSearchPath.push_back(string(szLine));
        }
        fclose(pFile);
      }
    }
  }
#endif
}

void CFileSearchPath::AddPathFromFilePath(string &sFolder, string &sFile) {
  string::size_type LastDelimiter = sFolder.find_last_of(FOLDERDELIMITER);
  if (LastDelimiter == string::npos) {
    sFile = sFolder;
  }
  else {
    string sNewFolder;
    sNewFolder = sFolder.substr(0, LastDelimiter + 1);
    sFile = sFolder.substr(LastDelimiter + 1);
    iterator it = find(asDllSearchPath.begin(),
      asDllSearchPath.end(), sNewFolder);
    if (it == asDllSearchPath.end()) {
      asDllSearchPath.insert(asDllSearchPath.begin(), sNewFolder);
    }
  }
}
//------------------------------------------------------------------------
bool bHasAbsolutePath(string &fname)
{
  return fname[0] == FOLDERDELIMITER;
}
//---------------------------
//OS agnostic library loader

static void * sLoad(const char *library_name)
{
  if(!library_name)
    return 0;

  void *handle;
#ifdef _WIN32
  handle = (void *)LoadLibrary((LPCSTR)library_name);
#else
  // According to the man page for dlopen, the RTLD_GLOBAL flag can
  // be or'd with the second pararmeter of the function call. However,
  // under Linux at least, this apparently cause *all* symbols to
  // disappear.

  handle = dlopen (library_name, RTLD_NOW); // | RTLD_GLOBAL);
#endif
  return handle;
}

void FixupLibraryName(string &sPath) {
  translatePath(sPath);
  if (STRICMP(&sPath[sPath.size() - (sizeof(MODULE_EXT)-1)], MODULE_EXT) != 0) {
    sPath.append(MODULE_EXT);
  }
}

void GetFileName(string &sPath, string &sName) {
  string::size_type pos = sPath.find_last_of(FOLDERDELIMITER);
  if(pos != string::npos) {
    sName = sPath.substr(pos + 1);
  }
  else if(&sName != &sPath) {
    sName = sPath;
  }
}

void GetFileNameBase(string &sPath, string &sName) {
  GetFileName(sPath, sName);
  string::size_type pos = sName.find_last_of('.');
  if(pos != string::npos) {
    sName = sName.substr(0, sName.size() - pos + 1);
  }
  else {
    sName = sPath;
  }
}

const char * get_error_message()
{
#ifdef _WIN32
  return g_win32_error_message(GetLastError());
#else
  return dlerror();
#endif
}

void free_error_message(const char * pszError)
{
#ifdef _WIN32
  g_free((char *) pszError);
#endif
}

unsigned long get_error(const char *err_str) {
#ifdef _WIN32
  return GetLastError();
#else
  /*
  ** In Linux and likely all Unix like OSs, dlopen leaves errno as 0
  ** even after failure, If so, look in error string returned by dlerror 
  ** to try to determine if file was not found. RRR
  */ 
  unsigned long ret = errno;	// in Linux errno is 0 
  if (! ret && err_str && strstr(err_str, "No such file"))
	ret = OS_E_FILENOTFOUND;
  return ret;
#endif
}

void * load_library(const char *library_name, const char **pszError)
{
  void *handle;

  string sFile;
  string sPath(library_name);
  FixupLibraryName(sPath);
  asDllSearchPath.AddPathFromFilePath(sPath, sFile);

  // First, see if we can load the library from where ever the 
  // system thinks libraries are located.
  if( (handle = sLoad(sPath.c_str())) != 0)
    return handle;

  *pszError = get_error_message();
  unsigned long uError = get_error(*pszError);

  if (uError == OS_E_FILENOTFOUND) {
    // Failed to find the library in the system paths, so try to load
    // from one of our paths.

    free_error_message(*pszError);

    CFileSearchPath::iterator itSearchPath;
    for (itSearchPath = asDllSearchPath.begin();
        itSearchPath != asDllSearchPath.end();
        itSearchPath++) {
      sPath = *itSearchPath + sFile;

      handle = sLoad(sPath.c_str());

      if (NULL != handle) {
        return handle;
      }
      *pszError = get_error_message();
    }
  }
  if (*pszError)
      printf("Failed loading %s: %s\n",
            sPath.c_str(), *pszError);
  return NULL;
}

void free_library(void *handle)
{
#ifdef _WIN32
  FreeLibrary((HMODULE)handle);
#else
  dlclose (handle);
#endif
}

void * get_library_export(const char *name, void *library_handle, const char ** pszError)
{
  void * pExport;
#ifdef _WIN32
  pExport = (void*)GetProcAddress((HMODULE)library_handle, name);
#else
  dlerror();	// Clear possible previous errors
  pExport = dlsym(library_handle, name);
#endif
  if (NULL == pExport && pszError != NULL) {
    *pszError = get_error_message();
  }
  return pExport;
}


syntax highlighted by Code2HTML, v. 0.9.1