// Copyright (c) 2014 The Chromium Embedded Framework 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 CEF_LIBCEF_DLL_WRAPPER_CEF_BROWSER_INFO_MAP_H_
#define CEF_LIBCEF_DLL_WRAPPER_CEF_BROWSER_INFO_MAP_H_
#pragma once

#include <map>

#include "include/base/cef_logging.h"

// Default traits for CefBrowserInfoMap. Override to provide different object
// destruction behavior.
template <typename ObjectType>
struct DefaultCefBrowserInfoMapTraits {
  static void Destruct(ObjectType info) { delete info; }
};

// Maps an arbitrary IdType to an arbitrary ObjectType on a per-browser basis.
template <typename IdType,
          typename ObjectType,
          typename Traits = DefaultCefBrowserInfoMapTraits<ObjectType>>
class CefBrowserInfoMap {
 public:
  // Implement this interface to visit and optionally delete objects in the map.
  class Visitor {
   public:
    using InfoIdType = IdType;
    using InfoObjectType = ObjectType;

    // Called once for each info object. Set |remove| to true to remove the
    // object from the map. It is safe to destruct removed objects in this
    // callback. Return true to continue iterating or false to stop iterating.
    virtual bool OnNextInfo(int browser_id,
                            InfoIdType info_id,
                            InfoObjectType info,
                            bool* remove) = 0;

   protected:
    virtual ~Visitor() {}
  };

  CefBrowserInfoMap() = default;

  CefBrowserInfoMap(const CefBrowserInfoMap&) = delete;
  CefBrowserInfoMap& operator=(const CefBrowserInfoMap&) = delete;

  ~CefBrowserInfoMap() { clear(); }

  // Add an object associated with the specified ID values.
  void Add(int browser_id, IdType info_id, ObjectType info) {
    InfoMap* info_map = nullptr;
    typename BrowserInfoMap::const_iterator it_browser =
        browser_info_map_.find(browser_id);
    if (it_browser == browser_info_map_.end()) {
      // No InfoMap exists for the browser ID so create it.
      info_map = new InfoMap;
      browser_info_map_.insert(std::make_pair(browser_id, info_map));
    } else {
      info_map = it_browser->second;
      // The specified ID should not already exist in the map.
      DCHECK(info_map->find(info_id) == info_map->end());
    }

    info_map->insert(std::make_pair(info_id, info));
  }

  // Find the object with the specified ID values. |visitor| can optionally be
  // used to evaluate or remove the object at the same time. If the object is
  // removed using the Visitor the caller is responsible for destroying it.
  ObjectType Find(int browser_id, IdType info_id, Visitor* vistor) {
    if (browser_info_map_.empty()) {
      return ObjectType();
    }

    typename BrowserInfoMap::iterator it_browser =
        browser_info_map_.find(browser_id);
    if (it_browser == browser_info_map_.end()) {
      return ObjectType();
    }

    InfoMap* info_map = it_browser->second;
    typename InfoMap::iterator it_info = info_map->find(info_id);
    if (it_info == info_map->end()) {
      return ObjectType();
    }

    ObjectType info = it_info->second;

    bool remove = false;
    if (vistor) {
      vistor->OnNextInfo(browser_id, it_info->first, info, &remove);
    }
    if (remove) {
      info_map->erase(it_info);

      if (info_map->empty()) {
        // No more entries in the InfoMap so remove it.
        browser_info_map_.erase(it_browser);
        delete info_map;
      }
    }

    return info;
  }

  // Find all objects. If any objects are removed using the Visitor the caller
  // is responsible for destroying them.
  void FindAll(Visitor* visitor) {
    DCHECK(visitor);

    if (browser_info_map_.empty()) {
      return;
    }

    bool remove, keepgoing = true;

    typename BrowserInfoMap::iterator it_browser = browser_info_map_.begin();
    while (it_browser != browser_info_map_.end()) {
      InfoMap* info_map = it_browser->second;

      typename InfoMap::iterator it_info = info_map->begin();
      while (it_info != info_map->end()) {
        remove = false;
        keepgoing = visitor->OnNextInfo(it_browser->first, it_info->first,
                                        it_info->second, &remove);

        if (remove) {
          info_map->erase(it_info++);
        } else {
          ++it_info;
        }

        if (!keepgoing) {
          break;
        }
      }

      if (info_map->empty()) {
        // No more entries in the InfoMap so remove it.
        browser_info_map_.erase(it_browser++);
        delete info_map;
      } else {
        ++it_browser;
      }

      if (!keepgoing) {
        break;
      }
    }
  }

  // Find all objects associated with the specified browser. If any objects are
  // removed using the Visitor the caller is responsible for destroying them.
  void FindAll(int browser_id, Visitor* visitor) {
    DCHECK(visitor);

    if (browser_info_map_.empty()) {
      return;
    }

    typename BrowserInfoMap::iterator it_browser =
        browser_info_map_.find(browser_id);
    if (it_browser == browser_info_map_.end()) {
      return;
    }

    InfoMap* info_map = it_browser->second;
    bool remove, keepgoing;

    typename InfoMap::iterator it_info = info_map->begin();
    while (it_info != info_map->end()) {
      remove = false;
      keepgoing = visitor->OnNextInfo(browser_id, it_info->first,
                                      it_info->second, &remove);

      if (remove) {
        info_map->erase(it_info++);
      } else {
        ++it_info;
      }

      if (!keepgoing) {
        break;
      }
    }

    if (info_map->empty()) {
      // No more entries in the InfoMap so remove it.
      browser_info_map_.erase(it_browser);
      delete info_map;
    }
  }

  // Returns true if the map is empty.
  bool empty() const { return browser_info_map_.empty(); }

  // Returns the number of objects in the map.
  size_t size() const {
    if (browser_info_map_.empty()) {
      return 0;
    }

    size_t size = 0;
    for (const auto& [browser_id, info_map] : browser_info_map_) {
      size += info_map->size();
    }
    return size;
  }

  // Returns the number of objects in the map that are associated with the
  // specified browser.
  size_t size(int browser_id) const {
    if (browser_info_map_.empty()) {
      return 0;
    }

    typename BrowserInfoMap::const_iterator it_browser =
        browser_info_map_.find(browser_id);
    if (it_browser != browser_info_map_.end()) {
      return it_browser->second->size();
    }

    return 0;
  }

  // Remove all objects from the map. The objects will be destructed.
  void clear() {
    if (browser_info_map_.empty()) {
      return;
    }

    for (const auto& [browser_id, info_map] : browser_info_map_) {
      for (const auto& [info_id, info] : *info_map) {
        Traits::Destruct(info);
      }
      delete info_map;
    }
    browser_info_map_.clear();
  }

  // Remove all objects from the map that are associated with the specified
  // browser. The objects will be destructed.
  void clear(int browser_id) {
    if (browser_info_map_.empty()) {
      return;
    }

    typename BrowserInfoMap::iterator it_browser =
        browser_info_map_.find(browser_id);
    if (it_browser == browser_info_map_.end()) {
      return;
    }

    InfoMap* info_map = it_browser->second;
    for (const auto& [info_id, info] : *info_map) {
      Traits::Destruct(info);
    }

    browser_info_map_.erase(it_browser);
    delete info_map;
  }

 private:
  // Map IdType to ObjectType instance.
  using InfoMap = std::map<IdType, ObjectType>;
  // Map browser ID to InfoMap instance.
  using BrowserInfoMap = std::map<int, InfoMap*>;

  BrowserInfoMap browser_info_map_;
};

#endif  // CEF_LIBCEF_DLL_WRAPPER_CEF_BROWSER_INFO_MAP_H_
