// -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
// vi: set et ts=4 sw=2 sts=2:

#if HAVE_CONFIG_H
#include "config.h"
#endif

#include <algorithm>
#include <iterator>
#include <string>

#include <dune/common/exceptions.hh>
#include <dune/common/stringutility.hh>
#include <dune/common/path.hh>

namespace Dune {
  /**
   * @addtogroup Path Filesystem Paths
   * @ingroup Common
   * @{
   */

  /**
   * @file
   * @author Jö Fahlke <jorrit@jorrit.de>
   * @brief Utilites for handling filesystem paths
   */

  //! concatenate two paths
  std::string concatPaths(const std::string& base, const std::string& p) {
    if(p == "") return base;
    if(p[0] == '/') return p;
    if(base == "") return p;
    if(hasSuffix(base, "/")) return base+p;
    else return base+'/'+p;
  }

  //! sanitize a path for further processing
  std::string processPath(const std::string& p) {
    std::string result = p;
    std::string::size_type src, dst;

    // append a '/' to non-empty paths
    if(result != "") result += '/';

    // each path component now has a trailing '/'

    // collapse any occurance of multiple '/' to a single '/'
    dst = src = 0;
    while(src < result.size()) {
      result[dst] = result[src];
      ++src;
      if(result[dst] == '/')
        while(src < result.size() && result[src] == '/')
          ++src;
      ++dst;
    }
    result.resize(dst);

    // the path is now free of multiple '/' in a row

    // collapse any occurance of "/./" to "/"
    dst = src = 0;
    while(src < result.size()) {
      result[dst] = result[src];
      ++src;
      if(result[dst] == '/')
        while(src+1 < result.size() && result[src] == '.' &&
              result[src+1] == '/')
          src+=2;
      ++dst;
    }
    result.resize(dst);

    // there may be at most one leading "./".  If so, remove it
    if(hasPrefix(result, "./")) result.erase(0, 2);

    // the path is now free of "."-components

    // remove any "<component>/../" pairs
    src = 0;
    while(true) {
      src = result.find("/../", src);
      if(src == std::string::npos)
        break;
      for(dst = src; dst > 0 && result[dst-1] != '/'; --dst) ;
      if(result.substr(dst, src-dst) == "..") {
        // don't remove "../../"
        src += 3;
        continue;
      }
      if(dst == src)
        // special case: "<component>" is the empty component.  This means we
        // found a leading "/../" in an absolute path, remove "/.."
        result.erase(0, 3);
      else {
        // remove "<component>/../".
        result.erase(dst, src-dst+4);
        src = dst;
        // try to back up one character so we are at a '/' instead of at the
        // beginning of a component
        if(src > 0) --src;
      }
    }

    // absolute paths are now free of ".." components, and relative paths
    // contain only leading ".." components

    return result;
  }

  //! check whether the given path indicates that it is a directory
  bool pathIndicatesDirectory(const std::string& p) {
    if(p == "") return true;
    if(p == ".") return true;
    if(p == "..") return true;
    if(hasSuffix(p, "/")) return true;
    if(hasSuffix(p, "/.")) return true;
    if(hasSuffix(p, "/..")) return true;
    else return false;
  }

  //! pretty print path
  std::string prettyPath(const std::string& p, bool isDirectory) {
    std::string result = processPath(p);
    // current directory
    if(result == "") return ".";
    // root directory
    if(result == "/") return result;

    // remove the trailing '/' for now
    result.resize(result.size()-1);

    // if the result ends in "..", we don't need to append '/' to make clear
    // it's a directory
    if(result == ".." || hasSuffix(result, "/.."))
      return result;

    // if it's a directory, tuck the '/' back on
    if(isDirectory) result += '/';

    return result;
  }

  //! pretty print path
  std::string prettyPath(const std::string& p) {
    return prettyPath(p, pathIndicatesDirectory(p));
  }

  //! compute a relative path between two paths
  std::string relativePath(const std::string& newbase, const std::string& p)
  {
    bool absbase = hasPrefix(newbase, "/");
    bool absp    = hasPrefix(p, "/");
    if(absbase != absp)
      DUNE_THROW(NotImplemented, "relativePath: paths must be either both "
                 "relative or both absolute: newbase=\"" << newbase << "\" "
                 "p=\"" << p << "\"");

    std::string mybase = processPath(newbase);
    std::string myp =    processPath(p);

    // remove as many matching leading components as possible
    // determine prefix length
    std::string::size_type preflen = 0;
    while(preflen < mybase.size() && preflen < myp.size() &&
          mybase[preflen] == myp[preflen])
      ++preflen;
    // backup to the beginning of the component
    while(preflen > 0 && myp[preflen-1] != '/')
      --preflen;
    mybase.erase(0, preflen);
    myp.erase(0,preflen);

    // if mybase contains leading ".." components, we're screwed
    if(hasPrefix(mybase, "../"))
      DUNE_THROW(NotImplemented, "relativePath: newbase has too many leading "
                 "\"..\" components: newbase=\"" << newbase << "\" "
                 "p=\"" << p << "\"");

    // count the number of components in mybase
    typedef std::iterator_traits<std::string::iterator>::difference_type
    count_t;
    count_t count = std::count(mybase.begin(), mybase.end(), '/');

    std::string result;
    // prefix with that many leading components
    for(count_t i = 0; i < count; ++i)
      result += "../";
    // append what is left of p
    result += myp;

    return result;
  }

  /** @} group Path */
}
