/**********************************************************************
 *
 * gsdltools.cpp -- 
 * A component of the Greenstone digital library software
 * from the New Zealand Digital Library Project at the
 * University of Waikato, New Zealand.
 *
 * Copyright (C) 1999  The New Zealand Digital Library Project
 *
 * This program 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 of the License, or
 * (at your option) any later version.
 *
 * 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.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 *********************************************************************/

#include "gsdltools.h"
#include "fileutil.h"
#include "stdlib.h"


#if defined(GSDL_USE_OBJECTSPACE)
#  include <ospace/std/iostream>
#elif defined(GSDL_USE_IOS_H)
#  include <iostream.h>
#else
#  include <iostream>
using namespace std;
#endif


#if defined(__WIN32__)
#include <windows.h>
#include <process.h>
#endif

#if !defined (__WIN32__)
#include <sys/utsname.h>
#include <unistd.h>
#endif


bool littleEndian() {
  char s[2] = {'\xFE', '\xEF'};

  if (sizeof(unsigned short) == 2)
    return *(unsigned short*)s == 0xEFFE;
  else if (sizeof(unsigned int) == 2)
    return *(unsigned int*)s == 0xEFFE;
}

text_t dm_safe (const text_t &instring) {

  text_t outstring;
  text_t::const_iterator here = instring.begin();
  text_t::const_iterator end = instring.end();
  while (here != end) {
    if (*here == '_' || *here == '\\') outstring.push_back('\\');
    outstring.push_back(*here);
    ++here;
  }
  return outstring;
}

void dm_js_safe(const text_t &instring, text_t &outstring)
{
  text_t::const_iterator here = instring.begin();
  text_t::const_iterator end = instring.end();
  while (here != end) {
    if (*here == '_') outstring.push_back('\\');
    else if (*here == '\\' || *here == '\'') {
      outstring.push_back('\\');
      outstring.push_back('\\');
    }
    outstring.push_back(*here);
    ++here;
  }
}

text_t xml_safe(const text_t &text_string)
{
  text_t text_string_safe;
  text_t::const_iterator here = text_string.begin();
  text_t::const_iterator end = text_string.end();
  while (here != end) {
    if (*here == '&') text_string_safe += "&amp;";
    else if (*here == '<') text_string_safe += "&lt;";
    else if (*here == '>') text_string_safe += "&gt;";
    else text_string_safe.push_back(*here);
    ++here;
  }
  return text_string_safe;
}


// gsdl_system creates a new process for the cmd command (which
// may contain arguments).
// cmd should contain the full path of the program to run.
// The child process inherits the environment of the calling
// process.
// If sync is true a synchronous call will be made, otherwise
// an asyncronous call.
// If sync is true the return value will be the exit code of
// the child process or -1 if the child process wasn't started.
// If sync is false the return value will be 0 if the process
// was started ok or -1 if it failed.
int gsdl_system (const text_t &cmd, bool sync, ostream &logout) {
  if (cmd.empty()) return -1;
  char *cmd_c = cmd.getcstr();

#if defined (__WIN32__)
  // the windows version - this is implemented this way
  // to prevent windows popping up all over the place when
  // we call a console application (like perl)
  STARTUPINFO ps = {sizeof(STARTUPINFO), NULL, NULL, NULL,
                    0, 0, 0, 0, 0, 0,
                    0, STARTF_USESHOWWINDOW,
                    SW_HIDE, 0, NULL,
		    NULL, NULL, NULL};
  PROCESS_INFORMATION pi;
  BOOL res = CreateProcess(NULL,
                           cmd_c,
                           NULL,
                           NULL,
                           FALSE,
#if defined (GSDL_LOCAL_LIBRARY)
			   NULL,
#else
			   CREATE_NO_WINDOW, // previously this was: DETACHED_PROCESS, 
			   // see http://msdn.microsoft.com/en-us/library/ms682425.aspx 
			   // and http://msdn.microsoft.com/en-us/library/ms684863(VS.85).aspx
#endif
                           NULL,
                           NULL,
                           &ps,
                           &pi);

  if (!res) {
    logout << "gsdl_system failed to start " << cmd_c 
	   << " process, error code " << GetLastError() << endl;
    delete []cmd_c;
    return -1;
  } 

  DWORD ret = 0;
  if (sync) { // synchronous system call
    // wait until child process exits.
    WaitForSingleObject(pi.hProcess, INFINITE);
    // set ret to exit code of child process
    GetExitCodeProcess(pi.hProcess, &ret);
  }

  CloseHandle(pi.hProcess);
  CloseHandle(pi.hThread);

#else
  // the unix version
  int ret = 0;
  if (sync) { // synchronous system call
    // make sure the command interpreter is found
    if (system (NULL) == 0) {
      logout << "gsdl_system failed to start " << cmd_c
	     << " process, command interpreter not found" << endl;
      delete []cmd_c;
      return -1;
    }
    ret = system (cmd_c);

  } else { // asynchronous system call
    int pid = fork();
    if (pid == -1) {
      delete []cmd_c;
      return -1;
    }
    if (pid == 0) {
      // child process
      char *argv[4];
      argv[0] = "sh";
      argv[1] = "-c";
      argv[2] = cmd_c;
      argv[3] = 0;
      execv("/bin/sh", argv);
    }
  }
#endif

  delete []cmd_c;
  return ret;
}


static bool gsdl_setenv_done = false;
static char* retain_gsdlosc   = NULL;
static char* retain_gsdlhomec = NULL;
static char* retain_pathc     = NULL;

bool set_gsdl_env_vars (const text_t& gsdlhome) 
{
  if (gsdl_setenv_done) { return true; }

  // set up GSDLOS, GSDLHOME and PATH environment variables

  text_t gsdlos, path;
  unsigned int path_separator = ':';

#if defined (__WIN32__)
  gsdlos = "windows";
  path_separator = ';';

  path = filename_cat (gsdlhome, "bin", "windows", "perl", "bin;");

#else
  struct utsname *buf = new struct utsname();
  int i = uname (buf);
  if (i == -1) gsdlos = "linux"; // uname failed, default to linux
  else gsdlos.setcstr (buf->sysname);
  delete buf;
  lc (gsdlos);
#endif

  // according to getenv documentation (after a bit of digging), *getenv*
  // is responsible for the char* pointer returned, so no need for us
  // to free it (in fact that would be a mistake!)

  char* orig_pathc = getenv ("PATH"); 
  path += filename_cat (gsdlhome, "bin", gsdlos);
  path.push_back (path_separator);
  path += filename_cat (gsdlhome, "bin", "script");
  if (orig_pathc != NULL) {
    path.push_back (path_separator);
    path += orig_pathc;
  }  

  text_t putpath     = "PATH=" + path;
  text_t putgsdlos   = "GSDLOS=" + gsdlos;
  text_t putgsdlhome = "GSDLHOME=" + gsdlhome;

  retain_gsdlosc   = putgsdlos.getcstr();
  retain_gsdlhomec = putgsdlhome.getcstr();
  retain_pathc     = putpath.getcstr();
  
  if ((putenv(retain_gsdlosc)!=0) || (putenv(retain_gsdlhomec)!=0)  
  || (putenv(retain_pathc)!=0))
    {
      // Would be better if the ostream& logout was used here.
      cerr << "Error setting Greenstone environment variables with putenv()" << endl;
      // perror("putenv(...): "); // This didn't yield any noticable output under Windows running local library server
      return false;
    }

  // Need to keep memory allocated setgsdlosc, setgsdlhomec or pathc around
  // (i.e. can't delete them).  This is because putenv() doesn't take
  // a copy, but places them in the actual environment.  

  gsdl_setenv_done = true;

  return true;
}



// attempts to work out if perl is functional
bool perl_ok (ostream &logout) {
#if defined(__WIN32__)
  text_t cmd = "perl -e \"exit 0\"";
#else 
  text_t cmd = "perl -e 'exit 0'";
#endif
  int i = gsdl_system (cmd, true, logout);
  return (i == 0);
}


#if defined(_MSC_VER)
// A very useful function to call to debug mysterious heap memory errors when
// their cause isn't clear in the Vis Studio debugger. Call it from various 
// places of the code and narrow down the point at which the heap memory becomes 
// corrupted. The title param can be the method this function is called from.
void check_system_heap(char* title)
{
  int heapstatus = _heapchk();

  char message[1024];

  switch (heapstatus) {
  case _HEAPOK:
    sprintf(message,"%s","OK - heap is fine");
    break;
  case _HEAPEMPTY:
    sprintf(message,"%s","OK - heap is empty");
    break;
  case _HEAPBADBEGIN:
    sprintf(message,"%s","ERROR - bad start of heap");
    break;
  case _HEAPBADNODE:
    sprintf(message,"%s","Error - bad node in heap");
    break;
  default:
    sprintf(message,"%s: %d","Unrecognized heap status",heapstatus);
  }

  cerr << message << endl;

  // When debugging w32server, having the messages appear in a popup
  // window can be more convenient
  // If you put this line in, make just server.exe (LOCAL_LIBRARY=1)
  //MessageBox (NULL, message, title, MB_OK);
}

#endif
