// This file is part of AspectC++.
// Copyright (C) 1999-2015  The AspectC++ developer team.
//                                                                
// 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., 59 Temple Place, Suite 330, Boston, 
// MA  02111-1307  USA

#include "SysCall.h"
#include "ErrorStream.h"
#include <errno.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <map>
#include <iostream>
#include <unistd.h>

#include <string.h>
#include <stdlib.h>

#ifndef MAX_PATH
#define MAX_PATH PATH_MAX
#endif // MAX_PATH

namespace ACBase {

void SysCall::printerror(ErrorStream *err, const char *cmd, const char *path) {
  if (err) {
    *err << sev_fatal << path << ": command '" << cmd << "' failed, " << strerror(errno) << endMessage;
  }
}

void SysCall::printerror(ErrorStream *err, const char *cmd, int fd) {
  if (err) {
    *err << sev_fatal << fd << ": command '" << cmd << "' failed, " << strerror(errno) << endMessage;
  }
}

std::string SysCall::mktemp(const std::string &prefix, ErrorStream *err) {

  // determine directory for temporary files
  std::error_code ec;
  auto temp_path = std::filesystem::temp_directory_path(ec);
  temp_path /= prefix + "XXXXXX";
  std::string temp_name = temp_path.string();

  // create the temp file with unique name
  int fd = mkstemp((char*)temp_name.c_str());

  // close file descriptor
  if (fd == -1 || close(fd) == -1) {
    printerror(err, "mktemp", temp_name.c_str());
    temp_name.clear();
  }
#if defined (WIN32)
  make_unix_path(temp_name);
#endif
  return temp_name;
}

// get the UNIX modification time of a file
// returns 'false' on any error, otherwise result will be set
// If a message should be printed in case of an error, pass 'err'
bool SysCall::file_modification_time(const std::filesystem::path &file, std::time_t &result, ErrorStream *err) {
  struct stat st;
  // call to 'stat' can be replaced by a std::filesystem function in C++20
  if (::stat(file.string().c_str(), &st) != 0) { // calling string() to avoid wide characters on Windows
    printerror(err, "stat");
    return false;
  }
  result = st.st_mtime;
  return true;
}

// create the specified directory hierarchy; return false on error
bool SysCall::make_directory_hierarchy(const std::filesystem::path &dir) {
  // just a wrapper for filesystem library
  std::error_code ec;
  std::filesystem::create_directories(dir, ec );
  return !ec; // ec is true in case of an any error
}

#if defined (WIN32)
// replace all '\' in a string (filename path) with '/'
void SysCall::make_unix_path(std::string &path) {
  for (std::size_t i = 0; i < path.length(); ++i)
    if (path[i] == '\\')
      path[i] = '/';
}
#endif

static std::map<std::filesystem::path, std::string> canonical_cache;

// build a canonical representation of a file path
// - the resulting path is absolute and contains neither ".", "..", nor soft links
// - on Windows all '\' are replace by '/'
// returns 'false' on any error, e.g. file does not exist
bool SysCall::make_canonical_path(const std::filesystem::path &path, std::string &result, bool weakly) {

  // first check if the searched canonical path is already cached  
  auto fn = canonical_cache.find(path);
  if (fn != canonical_cache.end()) {
    result = fn->second;
    return true;
  }

  // just a wrapper for filesystem library
  std::error_code ec;
  if (weakly)
    result = std::filesystem::weakly_canonical(path, ec).string();
  else
    result = std::filesystem::canonical(path, ec).string();
  if (ec) // ec is true in case of an any error
    return false;
#if defined (WIN32)
  make_unix_path(result);
#endif
  canonical_cache.insert({ path, result });
  return true;
}

} // namespace ACBase
