//=-- PlatformAbstractionUnix.cpp - Definition of the Abstraction Layer ------=
//
//  This file gives a definition of the PlatformAbstraction layer for a unix
//  machine.  This can be further specialized by the varient of unix if needed.
//  The definition of this class for the Windows platform exists in the
//  PlatformAbstractionWin32.cpp file.
//
//=---------------------------------------------------------------------------=
//  This file is copyright (c) 1997-1998 Chris Lattner
//=---------------------------------------------------------------------------=

#include "PlatformAbstraction.h"  // Find out if we're on a unix platform...

#ifdef UNIX
#include <dlfcn.h>     // dlopen 
#include <dirent.h>    // opendir/readdir/closedir
#include <sys/stat.h>  // stat
#include <sys/types.h>
#include <sys/time.h>  // gettimeofday
#include <unistd.h>
#include <pwd.h>
#include <errno.h>     // errno
#include <string.h>
#include <strings.h>
#include <signal.h>    // sigaction

#include "LinkedList.h"
#include "MSFilename.h"
#include "ResourceManager.h"

// The UNIX platform uses the .so suffix to denote a shared object.  These 
// files are the mechanism used to implement plugins...
const char *PlatformAbstraction::PluginSuffix = ".so";

// UNIX prefers the / character as the directory seperator.
//
const char  PlatformAbstraction::DirectorySeperator = '/';

PlatformAbstraction::PlatformAbstraction() {     // Initialize platform specific stuff...

  // Should be relative to the install location read from the registry.
  ResourceManager::AddResourceLocation("/usr/local/lib/MagicStats/", 
                                       "/stats/", ResourceManager::ServerResource);

  ResourceManager::AddResourceLocation("~/.MagicStats/", 
                                       "/~%u/stats/", ResourceManager::UserResource);
}

PlatformAbstraction::~PlatformAbstraction() {    // Shutdown platform specific stuff...
}


String PlatformAbstraction::GetUserName() {
  passwd *PWRec = getpwuid(geteuid());
  if (PWRec == 0) 
    return "Unknown";
  return PWRec->pw_name;
}

const char *PlatformAbstraction::GetServerConfigFilename() {
  return "/etc/MagicStats.conf";
}

const char *PlatformAbstraction::LoadPlugin(const MSFilename &Filename) {
  int NewFD = dup(2);  // Dup stderr...
  if (NewFD == -1) return strerror(errno);  // Error!
  close(2);  // Close stderr so that dlopen doesn't print anything to it.

  // May print gunk to stderr... which we closed so we don't see it!
  void *Result = dlopen((char*)(const char*)Filename, RTLD_NOW);

  dup2(NewFD, 2);     // Restore stderr
  close(NewFD);       // Close copy.
  if (Result) return 0;
  return dlerror();
}

int PlatformAbstraction::ScanDirectory(const MSFilename &Dir, LinkedList<String> &Dest, FileType FT) {
  struct stat StatBuf;
  dirent *DirEnt;
  DIR *D;

  char FilenameBuffer[3000];
  char *FilenameEnd;

  strcpy(FilenameBuffer, Dir);
  FilenameEnd = FilenameBuffer + strlen(FilenameBuffer);
  *FilenameEnd++ = '/'; // Append a / character
  // No need to put trailing null... that is supplied by strcpy function later...

  D = opendir(Dir);
  if (D == 0) return 1;  // Hmmm... the directory doesn't exist!

  while ((DirEnt = readdir(D)) != 0) {
    strcpy(FilenameEnd, DirEnt->d_name);  // Filename = Dir + "/" + DirEnt->d_name;

    int Status = stat(FilenameBuffer, &StatBuf);
    if (Status != 0) {    // Eeekk, how can readdir return sometime that doesn't exist?
      closedir(D);
      return 1;
    }

    if ((FT == RegularFile) && !S_ISREG(StatBuf.st_mode))
      continue;

    if ((FT == Directory) && !S_ISDIR(StatBuf.st_mode))
      continue;

    Dest.AddToTail(DirEnt->d_name);
  }

  closedir(D);
  return 0;
}

// DecodeFilename - Decode filename from a possible tilde representation.
//   For example, convert ~sabre to /home/sabre or ~/.MagicStats to
//   /home/sabre/.MagicStats/ etc.
//
void PlatformAbstraction::DecodeFilename(String &Filename) {
  struct passwd *UserInfo;
  char UserName[256];
  int c;

  if (Filename.Length() == 0) return;

  int i = Filename.strchr('\\');       // Convert DOS style \'s to /'s
  while (i != -1) {
    Filename[i] = '/';
    i = Filename.strchr('\\', i);
  }

  Filename.ReplaceSubString("//", "/");// Merge sequential //'s into a single /

  if (Filename[0] != '~') return;
  
  if (Filename[1] == '/') {            // The current user
    UserInfo = getpwuid(getuid());
    ::strcpy(UserName, UserInfo->pw_name);
    c = 1;
  } else {                             // The specified user
    c = Filename.strchr('/');
    if (c == -1)
      c = ::strlen(Filename);
    memcpy(UserName, &Filename[0]+1, c-1);
    UserName[c-1] = 0;
    UserInfo = getpwnam(&Filename[0]+1);
  }
  
  String Result = UserInfo->pw_dir;
  Filename.Right(Filename.Length()-c);
  Filename = Result+Filename;
}


// ChDir - Set the current working directory in the local filesystem.
//
int PlatformAbstraction::ChDir(const MSFilename &directory) {
  return chdir(directory);
}

// GetCWD - Get the current working directory in the local filesystem.
//
MSFilename PlatformAbstraction::GetCWD(void) {
  char Buffer[10000];
  return getcwd(Buffer, 9999);
}

// Unix and windows don't agree on any one function to do case insensitive
// string comparisons.  Because Unix is better than Windows, we name the 
// function the unix name.
//
int PlatformAbstraction::strcasecmp(const char *s1, const char *s2) {
  return ::strcasecmp(s1, s2);
}

// CreateHTMLSymlink - Creates a link between OutputFilename and LinkFilename.
// on a unix platform, this would create an actual symlink between the two 
// files, but on a machine without link support, this could create a meta 
// refresh page to forward the user to their destination...
void PlatformAbstraction::CreateHTMLSymlink(const MSFilename &OutputFilename, 
                                            const MSFilename &LinkFilename) {
  unlink(LinkFilename);
  symlink(OutputFilename, LinkFilename);
}


// GetSystemTime() - Return the time in seconds (with as much precision as 
// possible) to the caller from an abitrary reference point (like when the 
// machine was booted or 1970, whatever.  :)
//
double PlatformAbstraction::GetSystemTime() {
  struct timeval TimeStruct;
  if (gettimeofday(&TimeStruct, 0) == 0) 
	  return TimeStruct.tv_sec + TimeStruct.tv_usec/1000000.0;
  return -1;
}


extern "C" {
static volatile int *NotifyInt = 0;
static void SIGINTHandler(int SigNum) {
  *NotifyInt = 1;   // notify the main loop to stop!
}

};


// InstallInterruptHandler(int *x) - Install a signal handler to catch the
// interrupt character (ie CTRL-C).  When pressed, set the specified int
// to a true value...
//
void PlatformAbstraction::InstallInterruptHandler(volatile int *Notifier) {
  NotifyInt = Notifier;

  // Install SIGINT Handler...
  struct sigaction Act;
  Act.sa_handler = SIGINTHandler;
  sigemptyset(&Act.sa_mask);
  Act.sa_flags = 0;
  sigaction(SIGINT, &Act, 0);
}


#endif   // #IFDEF UNIX
