// file      : tests/link/driver.cxx -*- C++ -*-
// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd
// license   : MIT; see accompanying LICENSE file

#include <cassert>

#ifndef __cpp_lib_modules
#include <set>
#include <utility>      // pair
#include <system_error>
#endif

// Other includes.

#ifdef __cpp_modules
#ifdef __cpp_lib_modules
import std.core;
#endif
import butl.path;
import butl.fdstream;
import butl.filesystem;
#else
#include <libbutl/path.mxx>
#include <libbutl/fdstream.mxx>
#include <libbutl/filesystem.mxx>
#endif

using namespace std;
using namespace butl;

static const char text[] = "ABCDEF";

static bool
link_file (const path& target, const path& link, bool hard, bool check_content)
{
  try
  {
    if (hard)
      mkhardlink (target, link);
    else
      mksymlink (target, link);
  }
  catch (const system_error&)
  {
    return false;
  }

  if (!check_content)
    return true;

  string s;
  ifdstream ifs (link);
  ifs >> s;
  ifs.close (); // Not to miss failed close of the underlying file descriptor.
  return s == text;
}

#ifndef _WIN32
static bool
link_dir (const dir_path& target,
          const dir_path& link,
          bool hard,
          bool check_content)
{
  try
  {
    if (hard)
      mkhardlink (target, link);
    else
      mksymlink (target, link);
  }
  catch (const system_error& e)
  {
    //cerr << e << endl;
    return false;
  }

  if (!check_content)
    return true;

  dir_path tp (target.absolute () ? target : link.directory () / target);

  set<pair<entry_type, path>> te;
  for (const dir_entry& de: dir_iterator (tp))
    te.emplace (de.ltype (), de.path ());

  set<pair<entry_type, path>> le;
  for (const dir_entry& de: dir_iterator (link))
    le.emplace (de.ltype (), de.path ());

  return te == le;
}
#endif

int
main ()
{
  dir_path td (dir_path::temp_directory () / dir_path ("butl-link"));

  // Recreate the temporary directory (that possibly exists from the previous
  // faulty run) for the test files. Delete the directory only if the test
  // succeeds to simplify the failure research.
  //
  try_rmdir_r (td);
  assert (try_mkdir (td) == mkdir_status::success);

  // Prepare the target file.
  //
  path fn ("target");
  path fp (td / fn);

  {
    ofdstream ofs (fp);
    ofs << text;
    ofs.close ();
  }

  // Create the file hard link.
  //
  assert (link_file (fp, td / path ("hlink"), true, true));

#ifndef _WIN32
  // Create the file symlink using an absolute path.
  //
  assert (link_file (fp, td / path ("slink"), false, true));

  // Create the file symlink using a relative path.
  //
  assert (link_file (fn, td / path ("rslink"), false, true));

  // Create the file symlink using an unexistent file path.
  //
  assert (link_file (fp + "-a", td / path ("sa"), false, false));

  // Prepare the target directory.
  //
  dir_path dn ("dir");
  dir_path dp (td / dn);
  assert (try_mkdir (dp) == mkdir_status::success);
  assert (link_file (fp, dp / path ("hlink"), true, true));
  assert (link_file (fp, dp / path ("slink"), false, true));

  // Create the directory symlink using an absolute path.
  //
  assert (link_dir (dp, td / dir_path ("dslink"), false, true));

  // Create the directory symlink using a relative path.
  //
  assert (link_dir (dn, td / dir_path ("rdslink"), false, true));

  // Create the directory symlink using an unexistent directory path.
  //
  assert (link_dir (dp / dir_path ("a"), td / dir_path ("dsa"), false, false));
#endif

  rmdir_r (td);
}