// Copyright 2014 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.

#ifndef EXTENSIONS_BROWSER_API_API_RESOURCE_MANAGER_H_
#define EXTENSIONS_BROWSER_API_API_RESOURCE_MANAGER_H_

#include <map>
#include <memory>

#include "base/containers/hash_tables.h"
#include "base/memory/ptr_util.h"
#include "base/memory/ref_counted.h"
#include "base/scoped_observer.h"
#include "base/sequence_checker.h"
#include "base/sequenced_task_runner.h"
#include "base/threading/sequenced_worker_pool.h"
#include "components/keyed_service/core/keyed_service.h"
#include "content/public/browser/browser_thread.h"
#include "extensions/browser/browser_context_keyed_api_factory.h"
#include "extensions/browser/extension_registry.h"
#include "extensions/browser/extension_registry_factory.h"
#include "extensions/browser/extension_registry_observer.h"
#include "extensions/browser/process_manager.h"
#include "extensions/browser/process_manager_factory.h"
#include "extensions/browser/process_manager_observer.h"
#include "extensions/common/extension.h"

namespace extensions {
class CastChannelAsyncApiFunction;

namespace api {
class BluetoothSocketApiFunction;
class BluetoothSocketEventDispatcher;
class SerialEventDispatcher;
class TCPServerSocketEventDispatcher;
class TCPSocketEventDispatcher;
class UDPSocketEventDispatcher;
}  // namespace api

template <typename T>
struct NamedThreadTraits {
  static bool IsMessageLoopValid() {
    return content::BrowserThread::IsMessageLoopValid(T::kThreadId);
  }

  static scoped_refptr<base::SequencedTaskRunner> GetSequencedTaskRunner() {
    return content::BrowserThread::GetTaskRunnerForThread(T::kThreadId);
  }
};

// An ApiResourceManager manages the lifetime of a set of resources that
// that live on named threads (i.e. BrowserThread::IO) which ApiFunctions use.
// Examples of such resources are sockets or USB connections.
//
// Users of this class should define kThreadId to be the thread that
// ApiResourceManager to works on. The default is defined in ApiResource.
// The user must also define a static const char* service_name() that returns
// the name of the service, and in order for ApiResourceManager to use
// service_name() friend this class.
//
// In the cc file the user must define a GetFactoryInstance() and manage their
// own instances (typically using LazyInstance or Singleton).
//
// E.g.:
//
// class Resource {
//  public:
//   static const BrowserThread::ID kThreadId = BrowserThread::FILE;
//  private:
//   friend class ApiResourceManager<Resource>;
//   static const char* service_name() {
//     return "ResourceManager";
//    }
// };
//
// In the cc file:
//
// static base::LazyInstance<BrowserContextKeyedAPIFactory<
//     ApiResourceManager<Resource> > >
//         g_factory = LAZY_INSTANCE_INITIALIZER;
//
//
// template <>
// BrowserContextKeyedAPIFactory<ApiResourceManager<Resource> >*
// ApiResourceManager<Resource>::GetFactoryInstance() {
//   return g_factory.Pointer();
// }
template <class T, typename ThreadingTraits = NamedThreadTraits<T>>
class ApiResourceManager : public BrowserContextKeyedAPI,
                           public ExtensionRegistryObserver,
                           public ProcessManagerObserver {
 public:
  explicit ApiResourceManager(content::BrowserContext* context)
      : data_(new ApiResourceData()),
        extension_registry_observer_(this),
        process_manager_observer_(this) {
    extension_registry_observer_.Add(ExtensionRegistry::Get(context));
    process_manager_observer_.Add(ProcessManager::Get(context));
  }

  virtual ~ApiResourceManager() {
    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
    DCHECK(ThreadingTraits::IsMessageLoopValid())
        << "A unit test is using an ApiResourceManager but didn't provide "
           "the thread message loop needed for that kind of resource. "
           "Please ensure that the appropriate message loop is operational.";

    data_->InititateCleanup();
  }

  // TODO(lazyboy): Pass unique_ptr<T> instead of T*.
  // Takes ownership.
  int Add(T* api_resource) { return data_->Add(api_resource); }

  void Remove(const std::string& extension_id, int api_resource_id) {
    data_->Remove(extension_id, api_resource_id);
  }

  T* Get(const std::string& extension_id, int api_resource_id) {
    return data_->Get(extension_id, api_resource_id);
  }

  base::hash_set<int>* GetResourceIds(const std::string& extension_id) {
    return data_->GetResourceIds(extension_id);
  }

  // BrowserContextKeyedAPI implementation.
  static BrowserContextKeyedAPIFactory<ApiResourceManager<T>>*
  GetFactoryInstance();

  // Convenience method to get the ApiResourceManager for a profile.
  static ApiResourceManager<T>* Get(content::BrowserContext* context) {
    return BrowserContextKeyedAPIFactory<ApiResourceManager<T>>::Get(context);
  }

  // BrowserContextKeyedAPI implementation.
  static const char* service_name() { return T::service_name(); }

  // Change the resource mapped to this |extension_id| at this
  // |api_resource_id| to |resource|. Returns true and succeeds unless
  // |api_resource_id| does not already identify a resource held by
  // |extension_id|.
  bool Replace(const std::string& extension_id,
               int api_resource_id,
               T* resource) {
    return data_->Replace(extension_id, api_resource_id, resource);
  }

 protected:
  // ProcessManagerObserver:
  void OnBackgroundHostClose(const std::string& extension_id) override {
    data_->InitiateExtensionSuspendedCleanup(extension_id);
  }

  // ExtensionRegistryObserver:
  void OnExtensionUnloaded(content::BrowserContext* browser_context,
                           const Extension* extension,
                           UnloadedExtensionReason reason) override {
    data_->InitiateExtensionUnloadedCleanup(extension->id());
  }

 private:
  // TODO(rockot): ApiResourceData could be moved out of ApiResourceManager and
  // we could avoid maintaining a friends list here.
  friend class BluetoothAPI;
  friend class CastChannelAsyncApiFunction;
  friend class api::BluetoothSocketApiFunction;
  friend class api::BluetoothSocketEventDispatcher;
  friend class api::SerialEventDispatcher;
  friend class api::TCPServerSocketEventDispatcher;
  friend class api::TCPSocketEventDispatcher;
  friend class api::UDPSocketEventDispatcher;
  friend class BrowserContextKeyedAPIFactory<ApiResourceManager<T>>;

  static const bool kServiceHasOwnInstanceInIncognito = true;
  static const bool kServiceIsNULLWhileTesting = true;

  // ApiResourceData class handles resource bookkeeping on a thread
  // where resource lifetime is handled.
  class ApiResourceData : public base::RefCountedThreadSafe<ApiResourceData> {
   public:
    typedef std::map<int, std::unique_ptr<T>> ApiResourceMap;
    // Lookup map from extension id's to allocated resource id's.
    typedef std::map<std::string, base::hash_set<int>> ExtensionToResourceMap;

    ApiResourceData() : next_id_(1) { sequence_checker_.DetachFromSequence(); }

    // TODO(lazyboy): Pass unique_ptr<T> instead of T*.
    int Add(T* api_resource) {
      DCHECK(sequence_checker_.CalledOnValidSequence());
      int id = GenerateId();
      if (id > 0) {
        api_resource_map_[id] = base::WrapUnique<T>(api_resource);

        const std::string& extension_id = api_resource->owner_extension_id();
        ExtensionToResourceMap::iterator it =
            extension_resource_map_.find(extension_id);
        if (it == extension_resource_map_.end()) {
          it = extension_resource_map_
                   .insert(std::make_pair(extension_id, base::hash_set<int>()))
                   .first;
        }
        it->second.insert(id);
        return id;
      }
      return 0;
    }

    void Remove(const std::string& extension_id, int api_resource_id) {
      DCHECK(sequence_checker_.CalledOnValidSequence());
      if (GetOwnedResource(extension_id, api_resource_id)) {
        ExtensionToResourceMap::iterator it =
            extension_resource_map_.find(extension_id);
        it->second.erase(api_resource_id);
        api_resource_map_.erase(api_resource_id);
      }
    }

    T* Get(const std::string& extension_id, int api_resource_id) {
      DCHECK(sequence_checker_.CalledOnValidSequence());
      return GetOwnedResource(extension_id, api_resource_id);
    }

    // Change the resource mapped to this |extension_id| at this
    // |api_resource_id| to |resource|. Returns true and succeeds unless
    // |api_resource_id| does not already identify a resource held by
    // |extension_id|.
    bool Replace(const std::string& extension_id,
                 int api_resource_id,
                 T* api_resource) {
      DCHECK(sequence_checker_.CalledOnValidSequence());
      T* old_resource = api_resource_map_[api_resource_id].get();
      if (old_resource && extension_id == old_resource->owner_extension_id()) {
        api_resource_map_[api_resource_id] = base::WrapUnique<T>(api_resource);
        return true;
      }
      return false;
    }

    base::hash_set<int>* GetResourceIds(const std::string& extension_id) {
      DCHECK(sequence_checker_.CalledOnValidSequence());
      return GetOwnedResourceIds(extension_id);
    }

    void InitiateExtensionUnloadedCleanup(const std::string& extension_id) {
      ThreadingTraits::GetSequencedTaskRunner()->PostTask(
          FROM_HERE,
          base::Bind(&ApiResourceData::CleanupResourcesFromUnloadedExtension,
                     this, extension_id));
    }

    void InitiateExtensionSuspendedCleanup(const std::string& extension_id) {
      ThreadingTraits::GetSequencedTaskRunner()->PostTask(
          FROM_HERE,
          base::Bind(&ApiResourceData::CleanupResourcesFromSuspendedExtension,
                     this, extension_id));
    }

    void InititateCleanup() {
      ThreadingTraits::GetSequencedTaskRunner()->PostTask(
          FROM_HERE, base::Bind(&ApiResourceData::Cleanup, this));
    }

   private:
    friend class base::RefCountedThreadSafe<ApiResourceData>;

    virtual ~ApiResourceData() {}

    T* GetOwnedResource(const std::string& extension_id, int api_resource_id) {
      const std::unique_ptr<T>& ptr = api_resource_map_[api_resource_id];
      T* resource = ptr.get();
      if (resource && extension_id == resource->owner_extension_id())
        return resource;
      return NULL;
    }

    base::hash_set<int>* GetOwnedResourceIds(const std::string& extension_id) {
      DCHECK(sequence_checker_.CalledOnValidSequence());
      ExtensionToResourceMap::iterator it =
          extension_resource_map_.find(extension_id);
      if (it == extension_resource_map_.end())
        return NULL;
      return &(it->second);
    }

    void CleanupResourcesFromUnloadedExtension(
        const std::string& extension_id) {
      CleanupResourcesFromExtension(extension_id, true);
    }

    void CleanupResourcesFromSuspendedExtension(
        const std::string& extension_id) {
      CleanupResourcesFromExtension(extension_id, false);
    }

    void CleanupResourcesFromExtension(const std::string& extension_id,
                                       bool remove_all) {
      DCHECK(sequence_checker_.CalledOnValidSequence());

      ExtensionToResourceMap::iterator it =
          extension_resource_map_.find(extension_id);
      if (it == extension_resource_map_.end())
        return;

      // Remove all resources, or the non persistent ones only if |remove_all|
      // is false.
      base::hash_set<int>& resource_ids = it->second;
      for (base::hash_set<int>::iterator it = resource_ids.begin();
           it != resource_ids.end();) {
        bool erase = false;
        if (remove_all) {
          erase = true;
        } else {
          std::unique_ptr<T>& ptr = api_resource_map_[*it];
          T* resource = ptr.get();
          erase = (resource && !resource->IsPersistent());
        }

        if (erase) {
          api_resource_map_.erase(*it);
          resource_ids.erase(it++);
        } else {
          ++it;
        }
      }  // end for

      // Remove extension entry if we removed all its resources.
      if (resource_ids.size() == 0) {
        extension_resource_map_.erase(extension_id);
      }
    }

    void Cleanup() {
      DCHECK(sequence_checker_.CalledOnValidSequence());

      // Subtle: Move |api_resource_map_| to a temporary and clear that.
      // |api_resource_map_| will become empty and any destructors called
      // transitively from clearing |local_api_resource_map| will see empty
      // |api_resource_map_| instead of trying to access being-destroyed map.
      ApiResourceMap local_api_resource_map;
      api_resource_map_.swap(local_api_resource_map);
      local_api_resource_map.clear();
      // Do the same as above for |extension_resource_map_|.
      ExtensionToResourceMap local_extension_resource_map;
      extension_resource_map_.swap(local_extension_resource_map);
      local_extension_resource_map.clear();
    }

    int GenerateId() { return next_id_++; }

    int next_id_;
    ApiResourceMap api_resource_map_;
    ExtensionToResourceMap extension_resource_map_;
    base::SequenceChecker sequence_checker_;
  };

  content::NotificationRegistrar registrar_;
  scoped_refptr<ApiResourceData> data_;

  ScopedObserver<ExtensionRegistry, ExtensionRegistryObserver>
      extension_registry_observer_;
  ScopedObserver<ProcessManager, ProcessManagerObserver>
      process_manager_observer_;

  SEQUENCE_CHECKER(sequence_checker_);
};

template <class T>
struct BrowserContextFactoryDependencies<ApiResourceManager<T>> {
  static void DeclareFactoryDependencies(
      BrowserContextKeyedAPIFactory<ApiResourceManager<T>>* factory) {
    factory->DependsOn(
        ExtensionsBrowserClient::Get()->GetExtensionSystemFactory());
    factory->DependsOn(ExtensionRegistryFactory::GetInstance());
    factory->DependsOn(ProcessManagerFactory::GetInstance());
  }
};

}  // namespace extensions

#endif  // EXTENSIONS_BROWSER_API_API_RESOURCE_MANAGER_H_
