//! @file a68g-path.c
//! @author J. Marcel van der Veer

//! @section Copyright
//!
//! This file is part of Algol68G - an Algol 68 compiler-interpreter.
//! Copyright 2001-2025 J. Marcel van der Veer [algol68g@xs4all.nl].

//! @section License
//!
//! 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 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 General Public License for 
//! more details. You should have received a copy of the GNU General Public 
//! License along with this program. If not, see [http://www.gnu.org/licenses/].

//! @section Synopsis
//!
//! Low-level file path routines.

#include "a68g.h"
#include "a68g-prelude.h"
#include "a68g-genie.h"

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

//! @brief Safely get dir name from path.

char *a68g_dirname (char *src)
{
  size_t len = strlen (src) + 1;
  char *cpy = (char *) get_heap_space (len + 1);
  char *dst = (char *) get_heap_space (len + 1);
  ABEND (cpy == NO_TEXT, ERROR_MEMORY_FULL, NO_TEXT);
  ABEND (dst == NO_TEXT, ERROR_MEMORY_FULL, NO_TEXT);
  a68g_bufcpy (cpy, src, len);
  a68g_bufcpy (dst, dirname (cpy), len);
  return dst;
}

//! @brief Safely get basename from path.

char *a68g_basename (char *src)
{
  size_t len = strlen (src) + 1;
  char *cpy = (char *) get_heap_space (len + 1);
  char *dst = (char *) get_heap_space (len + 1);
  ABEND (cpy == NO_TEXT, ERROR_MEMORY_FULL, NO_TEXT);
  ABEND (dst == NO_TEXT, ERROR_MEMORY_FULL, NO_TEXT);
  a68g_bufcpy (cpy, src, len);
  a68g_bufcpy (dst, basename (cpy), len);
  return dst;
}

//! @brief Compute relative path.

#if defined (BUILD_WINDOWS)

static char *win32_slash (char *p)
{
  char *q = p;
  while (*p != '\0') {
    if (*p == '\\') {
      *p = '/';
    }
    p++;
  }
  return q;
}

static char *win32_realpath (char *name, char *resolved)
{
  char *res = NO_TEXT;
  if (name == NO_TEXT || name[0] == '\0') {
    return NO_TEXT;
  }
  if (resolved == NO_TEXT) {
    res = (char *) get_heap_space (PATH_MAX + 1);
    if (res == NO_TEXT) {
      return NO_TEXT;
    }
  } else {
    res = resolved;
  }
  int ret = GetFullPathName (name, PATH_MAX, res, (char **) NO_TEXT);
  if (ret == 0) {
    return NO_TEXT;
  } else {
    win32_slash (res);
    struct stat st;
    if (stat (res, &st) < 0) { // Should be 'lstat', but mingw does not have that.
      if (resolved == NO_TEXT) {
        a68g_free (res);
        return NO_TEXT;
      }
    }
  }
  return res;
}

#endif

char *a68g_relpath (char *p1, char *p2, char *fn)
{
  char q[PATH_MAX + 1];
  ASSERT (p1 != NO_TEXT);
  ASSERT (p2 != NO_TEXT);
  ASSERT (fn != NO_TEXT);
  #if defined (BUILD_WINDOWS)
    if (p2[0] == '\\') {
      a68g_bufcpy (q, p2, PATH_MAX);
    } else {
      a68g_bufcpy (q, p1, PATH_MAX);
      a68g_bufcat (q, "\\", PATH_MAX);
      a68g_bufcat (q, p2, PATH_MAX);
    }
    a68g_bufcat (q, "\\", PATH_MAX);
    a68g_bufcat (q, fn, PATH_MAX);
  #else
    if (p2[0] == '/') {
// Absolute path starting at root.
      a68g_bufcpy (q, p2, PATH_MAX);
    } else if (p2[0] == '~') {
      // "~" is a shell extension, not Unix.
      // We handle it anyway, as a courtesy.
      a68g_bufcpy (q, getenv("HOME"), PATH_MAX);
      a68g_bufcat (q, &p2[1], PATH_MAX);
    } else {
      a68g_bufcpy (q, p1, PATH_MAX);
      a68g_bufcat (q, "/", PATH_MAX);
      a68g_bufcat (q, p2, PATH_MAX);
    }
    a68g_bufcat (q, "/", PATH_MAX);
    a68g_bufcat (q, fn, PATH_MAX);
  #endif
// Error handling in the caller!
  errno = 0;
  #if defined (BUILD_WINDOWS)
    char *r = win32_realpath (q, NO_TEXT);
  #else
    char *r = realpath (q, NO_TEXT);
  #endif
  if (r != NO_TEXT) {
    size_t len = strlen (r) + 1;
    char *rc = (char *) get_heap_space (len);
    ABEND (rc == NO_TEXT, ERROR_MEMORY_FULL, NO_TEXT);
    a68g_bufcpy (rc, r, len);
    a68g_free (r);
    return rc;
  } else {
    return NO_TEXT;
  }
}

//! @brief PROC (STRING) STRING realpath

void genie_realpath (NODE_T * p)
{
  A68G_REF str;
  POP_REF (p, &str);
  char in[PATH_MAX + 1];
  if (a_to_c_string (p, in, str) == NO_TEXT) {
    PUSH_REF (p, empty_string (p));
  } else {
// Note that ~ is not resolved since that is the shell, not libc.
    #if defined (BUILD_WINDOWS)
      char *out = win32_realpath (in, NO_TEXT);
    #else
      char *out = realpath (in, NO_TEXT);
    #endif
    if (out == NO_TEXT) {
      PUSH_REF (p, empty_string (p));
    } else {
      PUSH_REF (p, c_to_a_string (p, out, PATH_MAX));
    }
  }
}

