// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/browser/sync/syncable/directory_manager.h"

#include <map>
#include <set>
#include <iterator>

#include "base/logging.h"
#include "base/memory/scoped_ptr.h"
#include "base/port.h"
#include "base/string_util.h"
#include "chrome/browser/sync/syncable/syncable.h"
#include "chrome/common/deprecated/event_sys-inl.h"

using browser_sync::Cryptographer;

namespace syncable {

static const FilePath::CharType kSyncDataDatabaseFilename[] =
    FILE_PATH_LITERAL("SyncData.sqlite3");

DirectoryManagerEvent DirectoryManagerShutdownEvent() {
  DirectoryManagerEvent event;
  event.what_happened = DirectoryManagerEvent::SHUTDOWN;
  return event;
}

// static
const FilePath DirectoryManager::GetSyncDataDatabaseFilename() {
  return FilePath(kSyncDataDatabaseFilename);
}

const FilePath DirectoryManager::GetSyncDataDatabasePath() const {
  return root_path_.Append(GetSyncDataDatabaseFilename());
}

DirectoryManager::DirectoryManager(const FilePath& path)
    : root_path_(path),
      managed_directory_(NULL),
      channel_(new Channel(DirectoryManagerShutdownEvent())),
      cryptographer_(new Cryptographer) {
}

DirectoryManager::~DirectoryManager() {
  base::AutoLock lock(lock_);
  DCHECK_EQ(managed_directory_, static_cast<Directory*>(NULL))
      << "Dir " << managed_directory_->name() << " not closed!";
  delete channel_;
}

bool DirectoryManager::Open(const std::string& name) {
  bool was_open = false;
  const DirOpenResult result = OpenImpl(name,
      GetSyncDataDatabasePath(), &was_open);
  return syncable::OPENED == result;
}

// Opens a directory.  Returns false on error.
DirOpenResult DirectoryManager::OpenImpl(const std::string& name,
                                         const FilePath& path,
                                         bool* was_open) {
  bool opened = false;
  {
    base::AutoLock lock(lock_);
    // Check to see if it's already open.
    if (managed_directory_) {
      DCHECK_EQ(base::strcasecmp(name.c_str(),
                                 managed_directory_->name().c_str()), 0)
          << "Can't open more than one directory.";
      opened = *was_open = true;
    }
  }

  if (opened)
    return syncable::OPENED;
  // Otherwise, open it.

  scoped_ptr<Directory> dir(new Directory);
  const DirOpenResult result = dir->Open(path, name);
  if (syncable::OPENED == result) {
    base::AutoLock lock(lock_);
    managed_directory_ = dir.release();
  }
  return result;
}

// Marks a directory as closed.  It might take a while until all the file
// handles and resources are freed by other threads.
void DirectoryManager::Close(const std::string& name) {
  // Erase from mounted and opened directory lists.
  {
    base::AutoLock lock(lock_);
    if (!managed_directory_ ||
        base::strcasecmp(name.c_str(),
                         managed_directory_->name().c_str()) != 0) {
      // It wasn't open.
      return;
    }
  }

  // TODO(timsteele): No lock?!
  // Notify listeners.
  managed_directory_->channel()->NotifyListeners(DIRECTORY_CLOSED);
  DirectoryManagerEvent event = { DirectoryManagerEvent::CLOSED, name };
  channel_->NotifyListeners(event);

  delete managed_directory_;
  managed_directory_ = NULL;
}

void DirectoryManager::FinalSaveChangesForAll() {
  base::AutoLock lock(lock_);
  if (managed_directory_)
    managed_directory_->SaveChanges();
}

void DirectoryManager::GetOpenDirectories(DirNames* result) {
  result->clear();
  base::AutoLock lock(lock_);
  if (managed_directory_)
    result->push_back(managed_directory_->name());
}

ScopedDirLookup::ScopedDirLookup(DirectoryManager* dirman,
                                 const std::string& name) : dirman_(dirman) {
  dir_ = dirman->managed_directory_ &&
         (base::strcasecmp(name.c_str(),
                           dirman->managed_directory_->name().c_str()) == 0) ?
         dirman->managed_directory_ : NULL;
  good_ = dir_ != NULL;
  good_checked_ = false;
}

ScopedDirLookup::~ScopedDirLookup() { }

Directory* ScopedDirLookup::operator -> () const {
  CHECK(good_checked_);
  DCHECK(good_);
  return dir_;
}

ScopedDirLookup::operator Directory* () const {
  CHECK(good_checked_);
  DCHECK(good_);
  return dir_;
}

}  // namespace syncable