// FIXME: Fails on Windows if one of the paths does not specify a drive letter
// E.g. c:\foo\bar and \foo\myfile.txt
String getRelativePath(const String& fromAbsPath, const String& toAbsPath)
{
  // Tokenize the paths
  StringVec fromTokens, toTokens;
  tokenize(posixPath(fromAbsPath), fromTokens, "/", "", "", "", "\\", false, false);
  tokenize(posixPath(toAbsPath), toTokens, "/", "", "", "", "\\", false, false);

  // Find the longest common prefix
  unsigned i = 0;
  StringVec::iterator fromIt = fromTokens.begin();
  StringVec::iterator toIt = toTokens.begin();
  for (; fromIt != fromTokens.end() && toIt != toTokens.end(); fromIt++, toIt++, i++) {
    if (!comparePaths(*fromIt, *toIt))
      break;
  }

  if (i > 0) {
    // Assemble the relative path
    StringVec relPathTokens(fromTokens.size() - i, "..");
    relPathTokens.insert(relPathTokens.end(), toIt, toTokens.end());
    return joinStrings(relPathTokens, "/");
  } else {
    // Paths have nothing in common, so no relative path exists (Windows-only)
    return sanitizePath(toAbsPath);
  }
}
VError XMacFileSystemNotification::StartWatchingForChanges( const VFolder &inFolder, VFileSystemNotifier::EventKind inKindFilter, VFileSystemNotifier::IEventHandler *inHandler, sLONG inLatency )
{
	// We need to get the folder's path into an array for us to pass along to the OS call.
	VString posixPathString;
	inFolder.GetPath( posixPathString, FPS_POSIX);

	VStringConvertBuffer buffer( posixPathString, VTC_UTF_8);
	std::string posixPath( buffer.GetCPointer());

	CFStringRef pathRef = posixPathString.MAC_RetainCFStringCopy();
	CFArrayRef pathArray = CFArrayCreate( NULL, (const void **)&pathRef, 1, NULL );
	
	FSEventStreamContext context = { 0 };
	
	// The latency the user passed us is expressed in milliseconds, but the OS call requires the latency to be
	// expressed in seconds.
	CFTimeInterval latency = (inLatency / 1000.0);
	
	// Now we can make our change data object
	XMacChangeData *data = new XMacChangeData( inFolder.GetPath(), posixPath, inKindFilter, VTask::GetCurrent(), inHandler, this);
	context.info = data;
	data->fStreamRef = FSEventStreamCreate( NULL, &FSEventCallback, &context, pathArray, kFSEventStreamEventIdSinceNow, latency, kFSEventStreamCreateFlagNone );
	if (data->fStreamRef == NULL)
	{
		CFRelease( pathArray );
		CFRelease( pathRef );
		ReleaseRefCountable( &data);
		return MAKE_NATIVE_VERROR( errno );
	}
	
	// We also need to take an initial snapshot of the directory for us to compare again
	CreateDirectorySnapshot( posixPath, data->fSnapshot, true );
	
	// Now we can add the data object we just made to our list
	fOwner->PushChangeData( data);

	// Next, we can schedule this to run on the main event loop
	FSEventStreamScheduleWithRunLoop( data->fStreamRef, CFRunLoopGetMain(), kCFRunLoopDefaultMode );

	CFRelease( pathArray );
	CFRelease( pathRef );

	// Now that we've scheduled the stream to run on a helper thread, all that is left is to
	// start executing the stream
	VError err;
	if (FSEventStreamStart( data->fStreamRef ))
	{
		err = VE_OK;
	}
	else
	{
		err = MAKE_NATIVE_VERROR( errno );
	}
	ReleaseRefCountable( &data);
	
	return err;
}
String sb_fextension(const String& path)
{
  String fixedPath = posixPath(path);

  String ext;
  size_t ext_pos = fixedPath.rfind(".");
  size_t dir_pos = fixedPath.rfind("/");

  if (ext_pos != String::npos) {
    if (dir_pos == String::npos || (dir_pos != String::npos && dir_pos < ext_pos)) {
      ext = fixedPath.substr(ext_pos + 1);
    }
  }
  return ext;
}
String sanitizePath(const String &path)
{
  // Standardize slashes to POSIX style
  String fixedPath = posixPath(path);

  // Check if path is absolute
  bool isAbsolute = isAbsolutePath(fixedPath);

  // Tokenize path
  StringList pathComponents;
  std::size_t start = 0;
  do {
    std::size_t separator = (std::min)(fixedPath.find('/', start), fixedPath.length());
    String token = fixedPath.substr(start, separator - start);
    if (token.empty() || token == ".") {
      // a/./b -> a/b and a//b -> a/b
    } else if (token == "..") {
      if (pathComponents.empty()) {
        // ../a -> ../a
        // /../a -> /a
        if (!isAbsolute)
          pathComponents.push_back(token);
      } else {
        // ../../a -> ../../a
        // a/../c -> c
        if (pathComponents.back() == "..")
          pathComponents.push_back(token);
        else
          pathComponents.pop_back();
      }
    } else {
      pathComponents.push_back(token);
    }

    start = separator + 1;
  } while (start < path.length());

  // Figure out if we need to add a leading slash
  String prefix;
  if (strBeginsWith(fixedPath, "/"))
    prefix = "/";

  // Return reassembled path
  return prefix + joinStrings(pathComponents, "/");
}
String sb_fname(const String& path)
{
  String fixedPath = posixPath(path);

  // Special case
  if (fixedPath == ".")
    return path;

  size_t ext_pos = fixedPath.rfind(".");
  size_t dir_pos = fixedPath.rfind("/");

  if (ext_pos != String::npos) {
    if (dir_pos == String::npos || (dir_pos != String::npos && dir_pos < ext_pos)) {
      return fixedPath.substr(0, ext_pos);
    }
  }
  return path;
}
String getcwd()
{
  char cwd[PATH_MAX+1];
  sbAssert(getcwd(cwd, sizeof(cwd)), "Failed to get CWD");
  return posixPath(cwd);
}