// -*- C++ -*-
/* Provide relocation for macro and font files.
   Copyright (C) 2005-2018 Free Software Foundation, Inc.

   This program is free software; you can redistribute it and/or modify it
   under the terms of the GNU Library General Public License as published
   by the Free Software Foundation, either version 3 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
   Library General Public License for more details.

   You should have received a copy of the GNU Library General Public License
   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */

// Made after relocation code in kpathsea and gettext.

#include "lib.h"

#include <errno.h>
#include <stdlib.h>

#include "defs.h"
#include "posix.h"
#include "nonposix.h"
#include "relocate.h"

#if defined _WIN32
# define WIN32_LEAN_AND_MEAN
# include <windows.h>
#endif

#define INSTALLPATHLEN (sizeof(INSTALLPATH) - 1)
#ifndef DEBUG
# define DEBUG 0
#endif

extern "C" const char *program_name;

// The prefix (parent directory) corresponding to the binary.
char *curr_prefix = 0;
size_t curr_prefix_len = 0;

// Return the directory part of a filename, or '.' if no path separators.
char *xdirname(char *s)
{
  static const char dot[] = ".";
  if (!s)
    return 0;
  // DIR_SEPS[] are possible directory separator characters, see nonposix.h.
  // We want the rightmost separator of all possible ones.
  // Example: d:/foo\\bar.
  char *p = strrchr(s, DIR_SEPS[0]);
  const char *sep = &DIR_SEPS[1];
  while (*sep) {
    char *p1 = strrchr(s, *sep);
    if (p1 && (!p || p1 > p))
      p = p1;
    sep++;
  }
  if (p)
    *p = '\0';
  else
    s = (char *)dot;
  return s;
}

// Return the full path of NAME along the path PATHP.
// Adapted from search_path::open_file in searchpath.cpp.
char *searchpath(const char *name, const char *pathp)
{
  char *path;
  if (!name || !*name)
    return 0;
#if DEBUG
  fprintf(stderr, "searchpath: pathp: '%s'\n", pathp);
  fprintf(stderr, "searchpath: trying '%s'\n", name);
#endif
  // Try first NAME as such; success if NAME is an absolute filename,
  // or if NAME is found in the current directory.
  if (!access (name, F_OK)) {
    path = new char[path_name_max()];
#ifdef _WIN32
    path = _fullpath(path, name, path_name_max());
#else
    path = realpath(name, path);
#endif
#if DEBUG
    fprintf(stderr, "searchpath: found '%s'\n", path);
#endif
    return path;
  }
  // Secondly, try the current directory.
  // Now search along PATHP.
  size_t namelen = strlen(name);
  char *p = (char *)pathp;
  for (;;) {
    char *end = strchr(p, PATH_SEP_CHAR);
    if (!end)
      end = strchr(p, '\0');
    int need_slash = end > p && strchr(DIR_SEPS, end[-1]) == 0;
    path = new char[end - p + need_slash + namelen + 1];
    memcpy(path, p, end - p);
    if (need_slash)
      path[end - p] = '/';
    strcpy(path + (end - p) + need_slash, name);
#if DEBUG
    fprintf(stderr, "searchpath: trying '%s'\n", path);
#endif
    if (!access(path, F_OK)) {
#if DEBUG
      fprintf(stderr, "searchpath: found '%s'\n", name);
#endif
      return path;
    }
    a_delete path;
    if (*end == '\0')
      break;
    p = end + 1;
  }
  return 0;
}

// Search NAME along PATHP with the elements of PATHEXT in turn added.
char *searchpathext(const char *name, const char *pathext, const char *pathp)
{
  char *found = 0;
  char *tmpathext = strsave(pathext);	// strtok modifies this string,
					// so make a copy
  char *ext = strtok(tmpathext, PATH_SEP);
  while (ext) {
    char *namex = new char[strlen(name) + strlen(ext) + 1];
    strcpy(namex, name);
    strcat(namex, ext);
    found = searchpath(namex, pathp);
    a_delete namex;
    if (found)
       break;
    ext = strtok(0, PATH_SEP);
  }
  a_delete tmpathext;
  return found;
}

// Convert an MS path to a POSIX path.
char *msw2posixpath(char *path)
{
  char *s = path;
  while (*s) {
    if (*s == '\\')
      *s = '/';
    s++;
  }
  return path;
}

// Compute the current prefix.
void set_current_prefix()
{
  char *pathextstr;
  curr_prefix = new char[path_name_max()];
  // Obtain the full path of the current binary;
  // using GetModuleFileName on MS-Windows,
  // and searching along PATH on other systems.
#ifdef _WIN32
  int len = GetModuleFileName(0, curr_prefix, path_name_max());
  if (len)
    len = GetShortPathName(curr_prefix, curr_prefix, path_name_max());
# if DEBUG
  fprintf(stderr, "curr_prefix: %s\n", curr_prefix);
# endif /* DEBUG */
#else /* !_WIN32 */
  curr_prefix = searchpath(program_name, getenv("PATH"));
  if (!curr_prefix && !strchr(program_name, '.')) {	// try with extensions
    pathextstr = strsave(getenv("PATHEXT"));
    if (!pathextstr)
      pathextstr = strsave(PATH_EXT);
    curr_prefix = searchpathext(program_name, pathextstr, getenv("PATH"));
    a_delete pathextstr;
  }
  if (!curr_prefix)
    return;
#endif /* !_WIN32 */
  msw2posixpath(curr_prefix);
#if DEBUG
  fprintf(stderr, "curr_prefix: %s\n", curr_prefix);
#endif
  curr_prefix = xdirname(curr_prefix);	// directory of executable
  curr_prefix = xdirname(curr_prefix);	// parent directory of executable
  curr_prefix_len = strlen(curr_prefix);
#if DEBUG
  fprintf(stderr, "curr_prefix: %s\n", curr_prefix);
  fprintf(stderr, "curr_prefix_len: %d\n", curr_prefix_len);
#endif
}

// Strip the installation prefix and replace it
// with the current installation prefix; return the relocated path.
char *relocatep(const char *path)
{
#if DEBUG
  fprintf(stderr, "relocatep: path = %s\n", path);
  fprintf(stderr, "relocatep: INSTALLPATH = %s\n", INSTALLPATH);
  fprintf(stderr, "relocatep: INSTALLPATHLEN = %d\n", INSTALLPATHLEN);
#endif
  if (!curr_prefix)
    set_current_prefix();
  if (strncmp(INSTALLPATH, path, INSTALLPATHLEN))
    return strsave(path);
  char *relative_path = (char *)path + INSTALLPATHLEN;
  size_t relative_path_len = strlen(relative_path);
  char *relocated_path = new char[curr_prefix_len + relative_path_len + 1];
  strcpy(relocated_path, curr_prefix);
  strcat(relocated_path, relative_path);
#if DEBUG
  fprintf(stderr, "relocated_path: %s\n", relocated_path);
#endif /* DEBUG */
  return relocated_path;
}

// Return the original pathname if it exists;
// otherwise return the relocated path.
char *relocate(const char *path)
{
  char *p;
  if (access(path, F_OK))
    p = relocatep(path);
  else
    p = strsave(path);
#if DEBUG
  fprintf (stderr, "relocate: %s\n", p);
#endif
  return p;
}
