/*=========================================================================

Copyright (c) 1998-2003 Kitware Inc. 469 Clifton Corporate Parkway,
Clifton Park, NY, 12065, USA.
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

 * Redistributions of source code must retain the above copyright notice,
   this list of conditions and the following disclaimer.

 * Redistributions in binary form must reproduce the above copyright notice,
   this list of conditions and the following disclaimer in the documentation
   and/or other materials provided with the distribution.

 * Neither the name of Kitware nor the names of any contributors may be used
   to endorse or promote products derived from this software without specific
   prior written permission.

 * Modified source versions must be plainly marked as such, and must not be
   misrepresented as being the original software.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS''
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

=========================================================================*/
#include "vtkKWDirectoryUtilities.h"

#include "vtkObjectFactory.h"
#include "vtkString.h"

#include <ctype.h>
#include <sys/stat.h>
#include <stdio.h>
#include <errno.h>

#if !defined(_WIN32) || defined(__CYGWIN__)
#include <limits.h>
#include <stdlib.h>
#include <sys/param.h>
#include <sys/wait.h>
#endif

#if defined(_WIN32) && !defined(__CYGWIN__)
#include <windows.h>
#include <direct.h>
static inline const char* Getcwd(char* buf, unsigned int len)
{
  return _getcwd(buf, len);
}
static inline int Chdir(const char* dir)
{
  #if defined(__BORLANDC__)
  return chdir(dir);
  #else
  return _chdir(dir);
  #endif
}
#else
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
static inline const char* Getcwd(char* buf, unsigned int len)
{
  return getcwd(buf, len);
}
static inline int Chdir(const char* dir)
{
  return chdir(dir);
}
#endif

vtkCxxRevisionMacro(vtkKWDirectoryUtilities, "1.11");
vtkStandardNewMacro(vtkKWDirectoryUtilities);

//----------------------------------------------------------------------------
vtkKWDirectoryUtilities::vtkKWDirectoryUtilities()
{
  this->SystemPath = 0;
  this->UnixSlashes = 0;
  this->ProgramFound = 0;
  this->CollapsedDirectory = 0;
  this->SelfPath = 0;
  this->CWD = 0;
}

//----------------------------------------------------------------------------
vtkKWDirectoryUtilities::~vtkKWDirectoryUtilities()
{
  if(this->CWD)
    {
    delete [] this->CWD;
    }

  if(this->CollapsedDirectory)
    {
    delete [] this->CollapsedDirectory;
    }
  
  if(this->SelfPath)
    {
    delete [] this->SelfPath;
    }
  
  if(this->ProgramFound)
    {
    delete [] this->ProgramFound;
    }
  
  if(this->UnixSlashes)
    {
    delete [] this->UnixSlashes;
    }
  
  if(this->SystemPath)
    {
    char** p = this->SystemPath;
    for(;*p;++p)
      {
      delete [] *p;
      }
    delete [] this->SystemPath;
    }
}

//----------------------------------------------------------------------------
void vtkKWDirectoryUtilities::PrintSelf(ostream& os, vtkIndent indent)
{
  this->Superclass::PrintSelf(os,indent);
}

//----------------------------------------------------------------------------
const char* vtkKWDirectoryUtilities::GetEnv(const char* key)
{
  return getenv(key);
}

//----------------------------------------------------------------------------
const char* vtkKWDirectoryUtilities::GetCWD()
{
  if(this->CWD)
    {
    delete [] this->CWD;
    }
  char buf[2048];
  this->CWD = vtkString::Duplicate(Getcwd(buf, 2048));
  return this->CWD;
}

//----------------------------------------------------------------------------
const char* vtkKWDirectoryUtilities::ConvertToUnixSlashes(const char* path)
{
  if(this->UnixSlashes)
    {
    delete [] this->UnixSlashes;
    this->UnixSlashes = 0;
    }
  if(!path)
    {
    return 0;
    }
  this->UnixSlashes = vtkString::Duplicate(path);
  vtkIdType length = vtkString::Length(this->UnixSlashes);
  if(length < 1)
    {
    return this->UnixSlashes;
    }
  
  // Convert slashes.
  vtkIdType i;
  for(i=0; i < length; ++i)
    {
    if(this->UnixSlashes[i] == '\\')
      {
      this->UnixSlashes[i] = '/';
      }
    }
  
  // Remove trailing slash.
  if(this->UnixSlashes[length-1] == '/')
    {
    this->UnixSlashes[length-1] = '\0';
    }
  
  // Replace a leading "~" with home directory.
  if(this->UnixSlashes[0] == '~')
    {
    const char* home = this->GetEnv("HOME");
    if(home)
      {
      char* newStr = vtkString::Append(home, this->UnixSlashes+1);
      delete [] this->UnixSlashes;
      this->UnixSlashes = newStr;
      }
    }
  
  return this->UnixSlashes;
}

//----------------------------------------------------------------------------
int vtkKWDirectoryUtilities::FileExists(const char* filename)
{
  struct stat fs;
  if(stat(filename, &fs) != 0) 
    {
    return 0;
    }
  else
    {
    return 1;
    }
}

//----------------------------------------------------------------------------
int vtkKWDirectoryUtilities::FileIsDirectory(const char* name)
{  
  struct stat fs;
  if(stat(name, &fs) == 0)
    {
#if _WIN32
    return ((fs.st_mode & _S_IFDIR) != 0);
#else
    return S_ISDIR(fs.st_mode);
#endif
    }
  else
    {
    return 0;
    }
}

//----------------------------------------------------------------------------
const char*const* vtkKWDirectoryUtilities::GetSystemPath()
{
  if(this->SystemPath)
    {
    return this->SystemPath;
    }
  // adds the elements of the env variable path to the arg passed in
#if defined(_WIN32) && !defined(__CYGWIN__)
  const char* pathSep = ";";
#else
  const char* pathSep = ":";
#endif
  const char* pathEnv = this->GetEnv("PATH");
  if(!pathEnv)
    {
    this->SystemPath = new char*[1];
    this->SystemPath[0] = 0;
    return this->SystemPath;
    }
  
  vtkIdType length = vtkString::Length(pathEnv);
  char* path = 0;
  if((length > 0) && (path[length-1] != pathSep[0]))
    {
    path = vtkString::Append(pathEnv, pathSep);
    ++length;
    }
  else
    {
    path = vtkString::Duplicate(pathEnv);
    }
  vtkIdType i;
  int pathSize = 0;
  for(i=0 ;i < length; ++i)
    {
    if(path[i] == pathSep[0])
      {
      ++pathSize;
      }
    }
  
  this->SystemPath = new char*[pathSize+1];
  this->SystemPath[pathSize] = 0;
  
  int curPath = 0;
  vtkIdType lpos=0;
  while(lpos < length)
    {
    vtkIdType rpos = lpos;
    for(;path[rpos] != pathSep[0];++rpos);
    path[rpos] = '\0';
    const char* upath = this->ConvertToUnixSlashes(path+lpos);
    this->SystemPath[curPath++] = vtkString::Duplicate(upath);
    lpos = rpos+1;
    }
  
  return this->SystemPath;
}

//----------------------------------------------------------------------------
const char* vtkKWDirectoryUtilities::FindProgram(const char* name)
{
  // Find the executable with the given name.  Searches the system
  // path.  Returns the full path to the executable if it is found.
  // Otherwise, 0 is returned.

  if(this->ProgramFound)
    {
    delete [] this->ProgramFound;
    this->ProgramFound = 0;
    }
  
  // See if the executable exists as written.
  if(this->FileExists(name) && !this->FileIsDirectory(name))
    {
    this->ProgramFound = vtkString::Duplicate(name);
    return this->ProgramFound;
    }
  
  char* tryPath = 0;
#ifdef _WIN32
  tryPath = vtkString::Append(name, ".exe");
  if(this->FileExists(tryPath) && !this->FileIsDirectory(tryPath))
    {
    this->ProgramFound = tryPath;
    return this->ProgramFound;
    }
  delete [] tryPath;
#endif
  
  // Get the system search path.
  const char*const* path = this->GetSystemPath();

  // Look through the path for the program.
  char* name1 = vtkString::Append("/", name);
#ifdef _WIN32
  char* name2 = vtkString::Append(name1, ".exe");
#endif
  
  const char*const* p;
  for(p = path; *p; ++p)
    {
    tryPath = vtkString::Append(*p, name1);
    if(this->FileExists(tryPath) && !this->FileIsDirectory(tryPath))
      {
      delete [] name1;
#ifdef _WIN32
      delete [] name2;
#endif
      this->ProgramFound = tryPath;
      return this->ProgramFound;
      }    
    delete [] tryPath;
    
#ifdef _WIN32
    tryPath = vtkString::Append(*p, name2);
    if(this->FileExists(tryPath) && !this->FileIsDirectory(tryPath))
      {
      delete [] name1;
      delete [] name2;
      this->ProgramFound = tryPath;
      return this->ProgramFound;
      }    
    delete [] tryPath;
#endif    
    }
  delete [] name1;
#ifdef _WIN32
  delete [] name2;
#endif
  
  // Couldn't find the program.
  return 0;
}

//----------------------------------------------------------------------------
const char* vtkKWDirectoryUtilities::CollapseDirectory(const char* dir)
{
  if(this->CollapsedDirectory)
    {
    delete [] this->CollapsedDirectory;
    this->CollapsedDirectory = 0;
    }
  
#ifdef _WIN32
  // Ultra-hack warning:
  // This changes to the target directory, saves the working directory,
  // and then changes back to the original working directory.
  char* cwd = vtkString::Duplicate(this->GetCWD());
  if(dir && (vtkString::Length(dir) > 0)) { Chdir(dir); }
  this->CollapsedDirectory = vtkString::Duplicate(this->GetCWD());
  Chdir(cwd);
  delete [] cwd;
  return this->CollapsedDirectory;
#else
# ifdef MAXPATHLEN
  char resolved_name[MAXPATHLEN];
# else
#  ifdef PATH_MAX
  char resolved_name[PATH_MAX];
#  else
  char resolved_name[5024];
#  endif
# endif
  if(dir && (vtkString::Length(dir) > 0))
    {
    realpath(dir, resolved_name);
    this->CollapsedDirectory = vtkString::Duplicate(resolved_name);
    }
  else
    {
    this->CollapsedDirectory = vtkString::Duplicate(this->GetCWD());
    }
  return this->CollapsedDirectory;
#endif  
}

//----------------------------------------------------------------------------
const char* vtkKWDirectoryUtilities::FindSelfPath(const char* argv0)
{
  // Find our own executable's location.
  
  if(this->SelfPath)
    {
    delete [] this->SelfPath;
    this->SelfPath = 0;
    }
  
  // Make sure we have a name.
  if(!argv0)
    {
    return 0;
    }
  char* av0 = vtkString::Duplicate(this->ConvertToUnixSlashes(argv0));
  vtkIdType av0len = vtkString::Length(av0);
  if(av0len < 1)
    {
    delete [] av0;
    return 0;
    }
  
  // Find the last forward slash.
  vtkIdType i;
  vtkIdType pos = -1;
  for(i=av0len-1; i >= 0; --i)
    {
    if(av0[i] == '/')
      {
      pos = i;
      break;
      }
    }
  
  // If no slash, the program must be in the system path.
  if(pos < 0)
    {
    char* newAv0 = vtkString::Duplicate(this->FindProgram(av0));
    delete [] av0;
    av0 = newAv0;
    av0len = vtkString::Length(av0);
    
    // Find the trailing slash, if any.
    for(i=av0len-1; i >= 0; --i)
      {
      if(av0[i] == '/')
        {
        pos = i;
        break;
        }
      }
    }
  
  // If there is a slash, use the directory up to the last slash.
  // Otherwise, the program must be in the current working directory.
  char* selfPath=0;
  if(pos >= 0)
    {
    av0[pos] = '\0';
    selfPath = vtkString::Duplicate(this->CollapseDirectory(av0));
    }
  else
    {
    selfPath = vtkString::Duplicate(this->CollapseDirectory("."));
    }
  delete [] av0;
  this->SelfPath = vtkString::Duplicate(this->ConvertToUnixSlashes(selfPath));
  delete [] selfPath;
  return this->SelfPath;
}

//----------------------------------------------------------------------------
const char* vtkKWDirectoryUtilities::GetFilenamePath(const char *filename, 
                                                     char *path)
{
  if ( !filename || strlen(filename) == 0 )
    {
    path[0] = 0;
    return path;
    }
  const char *ptr = filename + strlen(filename) - 1;
  while (ptr > filename && *ptr != '/' && *ptr != '\\')
    {
    ptr--;
    }

  size_t length = ptr - filename;
  if (length)
    {
    strncpy(path, filename, length);
    }
  path[length] = '\0';
  
  return path;
}

//----------------------------------------------------------------------------
const char* vtkKWDirectoryUtilities::GetFilenameName(const char *filename, 
                                                     char *name)
{
  if ( !filename || strlen(filename) == 0 )
    {
    name[0] = 0;
    return name;
    }
  int found = 0;
  const char *ptr = filename + strlen(filename) - 1;
  while (ptr > filename)
    {
    if (*ptr == '/' || *ptr == '\\')
      {
      found = 1;
      break;
      }
    ptr--;
    }

  strcpy(name, ptr + found);
  
  return name;
}

//----------------------------------------------------------------------------
const char* vtkKWDirectoryUtilities::GetFilenameExtension(const char *filename, 
                                                          char *ext)
{
  if ( !filename || strlen(filename) == 0 )
    {
    ext[0] = 0;
    return ext;
    }
  int found = 0;
  const char *ptr = filename + strlen(filename) - 1;
  while (ptr > filename)
    {
    if (*ptr == '.')
      {
      found = 1;
      break;
      }
    ptr--;
    }

  strcpy(ext, ptr + found);
  
  return ext;
}

//----------------------------------------------------------------------------
const char* vtkKWDirectoryUtilities::LocateFileInDir(const char *filename, 
                                                     const char *dir, 
                                                     char *try_fname,
                                                     int try_filename_dirs)
{
  if (!filename || !dir || !try_fname)
    {
    return 0;
    }

  // Get the basename of 'filename'

  char *filename_base = new char [strlen(filename) + 1];
  vtkKWDirectoryUtilities::GetFilenameName(filename, filename_base);

  // Check if 'dir' is really a directory 
  // If win32 and is like C:, accept it as a dir

  char *real_dir = 0;
  if (!vtkKWDirectoryUtilities::FileIsDirectory(dir))
    {
#if _WIN32
    size_t dir_len = strlen(dir);
    if (dir[dir_len - 1] != ':')
      {
#endif
      real_dir = new char [strlen(dir) + 1];
      dir = vtkKWDirectoryUtilities::GetFilenamePath(dir, real_dir);
#if _WIN32
      }
#endif
    }

  // Try to find the file in 'dir'

  const char *res = 0;
  if (filename_base && dir)
    {
    size_t dir_len = strlen(dir);
    int has_slash = 
      (dir_len && (dir[dir_len - 1] == '/' || dir[dir_len - 1] == '\\'));
    sprintf(try_fname, "%s%s%s", 
            dir, (has_slash ? "" : "/"), filename_base);

    if (vtkKWDirectoryUtilities::FileExists(try_fname))
      {
      res = try_fname;
      }

    // If not found, we can try harder by appending part of the file to
    // found to the directory to look inside
    // Example: if we were looking for /foo/bar/yo.txt in /d1/d2, then
    // try to find yo.txt in /d1/d2/bar, then /d1/d2/foo/bar, etc.

    else if (try_filename_dirs)
      {
      char *temp = new char [strlen(dir) + 1 + strlen(filename) + 1];

      char *filename_dir = vtkString::Duplicate(filename);
      char *filename_dir_base = new char [strlen(filename) + 1];
      char *filename_dir_bases = new char [strlen(filename) + 1];
      filename_dir_bases[0] = '\0';

      do
        {
        vtkKWDirectoryUtilities::GetFilenamePath(
          filename_dir, filename_dir);
        vtkKWDirectoryUtilities::GetFilenameName(
          filename_dir, filename_dir_base);
#if _WIN32
        size_t len = strlen(filename_dir_base);
        if (!*filename_dir_base || filename_dir_base[len - 1] == ':')
#else
        if (!*filename_dir_base)
#endif
          {
          break;
          }

        sprintf(temp, "%s/%s", filename_dir_base, filename_dir_bases);
        strcpy(filename_dir_bases, temp);

        sprintf(temp, "%s%s%s", dir, (has_slash ? "":"/"), filename_dir_bases);
        res = vtkKWDirectoryUtilities::LocateFileInDir(
          filename_base, temp, try_fname, 0);

        } while (!res && *filename_dir_base);

      delete [] filename_dir;
      delete [] filename_dir_base;
      delete [] filename_dir_bases;
      delete [] temp;
      }
    }
    
  // Free mem

  if (filename_base)
    {
    delete [] filename_base;
    }

  if (real_dir)
    {
    delete [] real_dir;
    }

  return res;
}

//----------------------------------------------------------------------------
int vtkKWDirectoryUtilities::FileHasSignature(const char *filename,
                                              const char *signature, 
                                              unsigned long offset)
{
  if (!filename || !signature)
    {
    return 0;
    }

  FILE *fp;
  fp = fopen(filename, "rb");
  if (!fp)
    {
    return 0;
    }

  fseek(fp, offset, SEEK_SET);

  int res = 0;
  size_t signature_len = strlen(signature);
  char *buffer = new char [signature_len];

  if (fread(buffer, 1, signature_len, fp) == signature_len)
    {
    res = (!strncmp(buffer, signature, signature_len) ? 1 : 0);
    }

  fclose(fp);
  return res;
}




