// Copyright 2008, Google Inc.
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
//    * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
//    * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
//    * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

#include "build/build_config.h"

#ifdef OS_WIN
#include <windows.h>
#include <shellapi.h>
#include <shlobj.h>
#endif

#include "base/hash_tables.h"
#include "base/path_service.h"

#include "base/lock.h"
#include "base/logging.h"
#include "base/file_util.h"
#include "base/string_util.h"

namespace base {
  bool PathProvider(int key, std::wstring* result);
#ifdef OS_WIN
  bool PathProviderWin(int key, std::wstring* result);
#endif
}

namespace {

typedef base::hash_map<int, std::wstring> PathMap;
typedef base::hash_set<int> PathSet;

// We keep a linked list of providers.  In a debug build we ensure that no two
// providers claim overlapping keys.
struct Provider {
  PathService::ProviderFunc func;
  struct Provider* next;
#ifndef NDEBUG
  int key_start;
  int key_end;
#endif
};

static Provider base_provider = {
  base::PathProvider,
  NULL,
#ifndef NDEBUG
  base::PATH_START,
  base::PATH_END
#endif
};

#ifdef OS_WIN
static Provider base_provider_win = {
  base::PathProviderWin,
  &base_provider,
#ifndef NDEBUG
  base::PATH_WIN_START,
  base::PATH_WIN_END
#endif
};
#endif

struct PathData {
  Lock      lock;
  PathMap   cache;      // Track mappings from path key to path value.
  PathSet   overrides;  // Track whether a path has been overridden.
  Provider* providers;  // Linked list of path service providers.

  PathData() {
#if defined(OS_WIN)
    providers = &base_provider_win;
#elif defined(OS_POSIX)
    providers = &base_provider;
#endif
  }
};

// We rely on the path service not being used prior to 'main' execution, and
// we are happy to let this data structure leak at process exit.
PathData* path_data = new PathData();

}  // namespace

// TODO(brettw): this function does not handle long paths (filename > MAX_PATH)
// characters). This isn't supported very well by Windows right now, so it is
// moot, but we should keep this in mind for the future.
// static
bool PathService::Get(int key, std::wstring* result) {
  DCHECK(path_data);
  DCHECK(result);
  DCHECK(key >= base::DIR_CURRENT);

  // special case the current directory because it can never be cached
  if (key == base::DIR_CURRENT) {
#if defined(OS_WIN)
    return file_util::GetCurrentDirectory(result);
#elif defined(OS_POSIX)
    char system_buffer[PATH_MAX];
    system_buffer[0] = 0;
    getcwd(system_buffer, sizeof(system_buffer));
    *result = NativeMBToWide(system_buffer);
    return true;
#endif
  }

  // TODO(darin): it would be nice to avoid holding this lock while calling out
  // to the path providers.
  AutoLock scoped_lock(path_data->lock);

  // check for a cached version
  PathMap::const_iterator it = path_data->cache.find(key);
  if (it != path_data->cache.end()) {
    *result = it->second;
    return true;
  }

  std::wstring path;

  // search providers for the requested path
  Provider* provider = path_data->providers;
  while (provider) {
    if (provider->func(key, &path))
      break;
    DCHECK(path.empty()) << "provider should not have modified path";
    provider = provider->next;
  }

  if (path.empty())
    return false;

  // Save the computed path in our cache.
  path_data->cache[key] = path;

  result->swap(path);
  return true;
}

bool PathService::IsOverridden(int key) {
  DCHECK(path_data);

  AutoLock scoped_lock(path_data->lock);
  return path_data->overrides.find(key) != path_data->overrides.end();
}

bool PathService::Override(int key, const std::wstring& path) {
  DCHECK(path_data);
  DCHECK(key > base::DIR_CURRENT) << "invalid path key";

#if defined(OS_WIN)
  wchar_t file_path_buf[MAX_PATH];
  if (!_wfullpath(file_path_buf, path.c_str(), MAX_PATH))
    return false;
  std::wstring file_path(file_path_buf);
#elif defined(OS_POSIX)
  // The other (posix-like) platforms don't use wide strings for paths. On the
  // Mac it's NFD UTF-8, and we have to assume that whatever other platforms
  // we end up on the native encoding is correct.
  // TODO: refactor all of the path code throughout the project to use a
  // per-platform path type
  char file_path_buf[PATH_MAX];
  if (!realpath(WideToNativeMB(path).c_str(), file_path_buf))
    return false;
  std::wstring file_path(NativeMBToWide(file_path_buf));
#endif

  // make sure the directory exists:
  if (!file_util::PathExists(file_path) &&
      // TODO(darin): what if this path is not that of a directory?
      !file_util::CreateDirectory(file_path))
    return false;

  file_util::TrimTrailingSeparator(&file_path);

  AutoLock scoped_lock(path_data->lock);
  path_data->cache[key] = file_path;
  path_data->overrides.insert(key);
  return true;
}

bool PathService::SetCurrentDirectory(const std::wstring& current_directory) {
#if defined(OS_WIN)
  return file_util::SetCurrentDirectory(current_directory);
#elif defined(OS_POSIX)
  int ret = chdir(WideToNativeMB(current_directory).c_str());
  return (ret == 0);
#endif
}

void PathService::RegisterProvider(ProviderFunc func, int key_start,
                                   int key_end) {
  DCHECK(path_data);
  DCHECK(key_end > key_start);

  AutoLock scoped_lock(path_data->lock);

  Provider* p;

#ifndef NDEBUG
  p = path_data->providers;
  while (p) {
    DCHECK(key_start >= p->key_end || key_end <= p->key_start) <<
      "path provider collision";
    p = p->next;
  }
#endif

  p = new Provider;
  p->func = func;
  p->next = path_data->providers;
#ifndef NDEBUG
  p->key_start = key_start;
  p->key_end = key_end;
#endif
  path_data->providers = p;
}