// 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 COMPONENTS_BOOKMARKS_BROWSER_BOOKMARK_MODEL_H_
#define COMPONENTS_BOOKMARKS_BROWSER_BOOKMARK_MODEL_H_

#include <stddef.h>
#include <stdint.h>

#include <map>
#include <memory>
#include <set>
#include <vector>

#include "base/compiler_specific.h"
#include "base/macros.h"
#include "base/memory/scoped_refptr.h"
#include "base/memory/weak_ptr.h"
#include "base/observer_list.h"
#include "base/strings/string16.h"
#include "components/bookmarks/browser/bookmark_client.h"
#include "components/bookmarks/browser/bookmark_node.h"
#include "components/bookmarks/browser/bookmark_undo_provider.h"
#include "components/keyed_service/core/keyed_service.h"
#include "ui/gfx/image/image.h"
#include "url/gurl.h"

class PrefService;

namespace base {
class FilePath;
class SequencedTaskRunner;
}

namespace favicon_base {
struct FaviconImageResult;
}

namespace query_parser {
enum class MatchingAlgorithm;
}

namespace bookmarks {

class BookmarkCodecTest;
class BookmarkExpandedStateTracker;
class BookmarkLoadDetails;
class BookmarkModelObserver;
class BookmarkStorage;
class BookmarkUndoDelegate;
class ModelLoader;
class ScopedGroupBookmarkActions;
class TestBookmarkClient;
class TitledUrlIndex;
class UrlIndex;

struct UrlAndTitle;
struct TitledUrlMatch;

// BookmarkModel --------------------------------------------------------------

// BookmarkModel provides a directed acyclic graph of URLs and folders.
// Three graphs are provided for the three entry points: those on the 'bookmarks
// bar', those in the 'other bookmarks' folder and those in the 'mobile' folder.
//
// An observer may be attached to observe relevant events.
//
// You should NOT directly create a BookmarkModel, instead go through the
// BookmarkModelFactory.
class BookmarkModel : public BookmarkUndoProvider,
                      public KeyedService {
 public:
  explicit BookmarkModel(std::unique_ptr<BookmarkClient> client);
  ~BookmarkModel() override;

  // Loads the bookmarks. This is called upon creation of the BookmarkModel. You
  // need not invoke this directly. All load operations will be executed on
  // |io_task_runner|. |ui_task_runner| is the task runner the model runs on.
  void Load(PrefService* pref_service,
            const base::FilePath& profile_path,
            const scoped_refptr<base::SequencedTaskRunner>& io_task_runner,
            const scoped_refptr<base::SequencedTaskRunner>& ui_task_runner);

  // Returns true if the model finished loading.
  bool loaded() const { return loaded_; }

  // Returns the object responsible for tracking loading.
  ModelLoader* model_loader() { return model_loader_.get(); }

  // Returns the root node. The 'bookmark bar' node and 'other' node are
  // children of the root node.
  const BookmarkNode* root_node() const { return root_; }

  // Returns the 'bookmark bar' node. This is NULL until loaded.
  const BookmarkNode* bookmark_bar_node() const { return bookmark_bar_node_; }

  // Returns the 'other' node. This is NULL until loaded.
  const BookmarkNode* other_node() const { return other_node_; }

  // Returns the 'mobile' node. This is NULL until loaded.
  const BookmarkNode* mobile_node() const { return mobile_node_; }

  bool is_root_node(const BookmarkNode* node) const { return node == root_; }

  // Returns whether the given |node| is one of the permanent nodes - root node,
  // 'bookmark bar' node, 'other' node or 'mobile' node, or one of the root
  // nodes supplied by the |client_|.
  bool is_permanent_node(const BookmarkNode* node) const {
    return node && (node == root_ || node->parent() == root_);
  }

  void AddObserver(BookmarkModelObserver* observer);
  void RemoveObserver(BookmarkModelObserver* observer);

  // Notifies the observers that an extensive set of changes is about to happen,
  // such as during import or sync, so they can delay any expensive UI updates
  // until it's finished.
  void BeginExtensiveChanges();
  void EndExtensiveChanges();

  // Returns true if this bookmark model is currently in a mode where extensive
  // changes might happen, such as for import and sync. This is helpful for
  // observers that are created after the mode has started, and want to check
  // state during their own initializer, such as the NTP.
  bool IsDoingExtensiveChanges() const { return extensive_changes_ > 0; }

  // Removes |node| from the model and deletes it. Removing a folder node
  // recursively removes all nodes. Observers are notified immediately.
  void Remove(const BookmarkNode* node);

  // Removes all the non-permanent bookmark nodes that are editable by the user.
  // Observers are only notified when all nodes have been removed. There is no
  // notification for individual node removals.
  void RemoveAllUserBookmarks();

  // Moves |node| to |new_parent| and inserts it at the given |index|.
  void Move(const BookmarkNode* node,
            const BookmarkNode* new_parent,
            int index);

  // Inserts a copy of |node| into |new_parent| at |index|.
  void Copy(const BookmarkNode* node,
            const BookmarkNode* new_parent,
            int index);

  // Returns the favicon for |node|. If the favicon has not yet been loaded,
  // a load will be triggered and the observer of the model notified when done.
  // This also means that, on return, the node's state is guaranteed to be
  // either LOADED_FAVICON (if it was already loaded prior to the call) or
  // LOADING_FAVICON (with the exception of folders, where the call is a no-op).
  const gfx::Image& GetFavicon(const BookmarkNode* node);

  // Returns the type of the favicon for |node|. If the favicon has not yet
  // been loaded, it returns |favicon_base::IconType::kInvalid|.
  favicon_base::IconType GetFaviconType(const BookmarkNode* node);

  // Sets the title of |node|.
  void SetTitle(const BookmarkNode* node, const base::string16& title);

  // Sets the URL of |node|.
  void SetURL(const BookmarkNode* node, const GURL& url);

  // Sets the date added time of |node|.
  void SetDateAdded(const BookmarkNode* node, base::Time date_added);

  // Returns the set of nodes with the |url|.
  void GetNodesByURL(const GURL& url, std::vector<const BookmarkNode*>* nodes);

  // Returns the most recently added user node for the |url|; urls from any
  // nodes that are not editable by the user are never returned by this call.
  // Returns NULL if |url| is not bookmarked.
  const BookmarkNode* GetMostRecentlyAddedUserNodeForURL(const GURL& url);

  // Returns true if there are bookmarks, otherwise returns false.
  // This method is thread safe.
  bool HasBookmarks();

  // Returns true is there is no user created bookmarks or folders.
  bool HasNoUserCreatedBookmarksOrFolders();

  // Returns true if the specified URL is bookmarked.
  //
  // If not on the main thread you *must* invoke BlockTillLoaded first.
  bool IsBookmarked(const GURL& url);

  // Returns, by reference in |bookmarks|, the set of bookmarked urls and their
  // titles. This returns the unique set of URLs. For example, if two bookmarks
  // reference the same URL only one entry is added not matter the titles are
  // same or not.
  //
  // If not on the main thread you *must* invoke BlockTillLoaded first.
  void GetBookmarks(std::vector<UrlAndTitle>* urls);

  // Adds a new folder node at the specified position.
  const BookmarkNode* AddFolder(const BookmarkNode* parent,
                                int index,
                                const base::string16& title);

  // Adds a new folder with meta info.
  const BookmarkNode* AddFolderWithMetaInfo(
      const BookmarkNode* parent,
      int index,
      const base::string16& title,
      const BookmarkNode::MetaInfoMap* meta_info);

  // Adds a url at the specified position.
  const BookmarkNode* AddURL(const BookmarkNode* parent,
                             int index,
                             const base::string16& title,
                             const GURL& url);

  // Adds a url with a specific creation date and meta info.
  const BookmarkNode* AddURLWithCreationTimeAndMetaInfo(
      const BookmarkNode* parent,
      int index,
      const base::string16& title,
      const GURL& url,
      const base::Time& creation_time,
      const BookmarkNode::MetaInfoMap* meta_info);

  // Sorts the children of |parent|, notifying observers by way of the
  // BookmarkNodeChildrenReordered method.
  void SortChildren(const BookmarkNode* parent);

  // Order the children of |parent| as specified in |ordered_nodes|.  This
  // function should only be used to reorder the child nodes of |parent| and
  // is not meant to move nodes between different parent. Notifies observers
  // using the BookmarkNodeChildrenReordered method.
  void ReorderChildren(const BookmarkNode* parent,
                       const std::vector<const BookmarkNode*>& ordered_nodes);

  // Sets the date when the folder was modified.
  void SetDateFolderModified(const BookmarkNode* node, const base::Time time);

  // Resets the 'date modified' time of the node to 0. This is used during
  // importing to exclude the newly created folders from showing up in the
  // combobox of most recently modified folders.
  void ResetDateFolderModified(const BookmarkNode* node);

  // Returns up to |max_count| of bookmarks containing each term from |text|
  // in either the title or the URL. It uses default matching algorithm.
  void GetBookmarksMatching(const base::string16& text,
                            size_t max_count,
                            std::vector<TitledUrlMatch>* matches);

  // Returns up to |max_count| of bookmarks containing each term from |text|
  // in either the title or the URL.
  void GetBookmarksMatching(const base::string16& text,
                            size_t max_count,
                            query_parser::MatchingAlgorithm matching_algorithm,
                            std::vector<TitledUrlMatch>* matches);

  // Sets the store to NULL, making it so the BookmarkModel does not persist
  // any changes to disk. This is only useful during testing to speed up
  // testing.
  void ClearStore();

  // Returns the next node ID.
  int64_t next_node_id() const { return next_node_id_; }

  // Returns the object responsible for tracking the set of expanded nodes in
  // the bookmark editor.
  BookmarkExpandedStateTracker* expanded_state_tracker() {
    return expanded_state_tracker_.get();
  }

  // Sets the visibility of one of the permanent nodes (unless the node must
  // always be visible, see |BookmarkClient::IsPermanentNodeVisible| for more
  // details). This is set by sync.
  void SetPermanentNodeVisible(BookmarkNode::Type type, bool value);

  // Returns the permanent node of type |type|.
  const BookmarkPermanentNode* PermanentNode(BookmarkNode::Type type);

  // Sets/deletes meta info of |node|.
  void SetNodeMetaInfo(const BookmarkNode* node,
                       const std::string& key,
                       const std::string& value);
  void SetNodeMetaInfoMap(const BookmarkNode* node,
                          const BookmarkNode::MetaInfoMap& meta_info_map);
  void DeleteNodeMetaInfo(const BookmarkNode* node,
                          const std::string& key);

  // Adds |key| to the set of meta info keys that are not copied when a node is
  // cloned.
  void AddNonClonedKey(const std::string& key);

  // Returns the set of meta info keys that should not be copied when a node is
  // cloned.
  const std::set<std::string>& non_cloned_keys() const {
    return non_cloned_keys_;
  }

  // Sets the sync transaction version of |node|.
  void SetNodeSyncTransactionVersion(const BookmarkNode* node,
                                     int64_t sync_transaction_version);

  // Notify BookmarkModel that the favicons for the given page URLs (e.g.
  // http://www.google.com) and the given icon URL (e.g.
  // http://www.google.com/favicon.ico) have changed. It is valid to call
  // OnFaviconsChanged() with non-empty |page_urls| and an empty |icon_url| and
  // vice versa.
  void OnFaviconsChanged(const std::set<GURL>& page_urls,
                         const GURL& icon_url);

  // Returns the client used by this BookmarkModel.
  BookmarkClient* client() const { return client_.get(); }

  void SetUndoDelegate(BookmarkUndoDelegate* undo_delegate);

 private:
  friend class BookmarkCodecTest;
  friend class BookmarkModelFaviconTest;
  friend class BookmarkStorage;
  friend class ScopedGroupBookmarkActions;
  friend class TestBookmarkClient;

  // BookmarkUndoProvider:
  void RestoreRemovedNode(const BookmarkNode* parent,
                          int index,
                          std::unique_ptr<BookmarkNode> node) override;

  // Notifies the observers for adding every descedent of |node|.
  void NotifyNodeAddedForAllDescendents(const BookmarkNode* node);

  // Removes the node from internal maps and recurses through all children. If
  // the node is a url, its url is added to removed_urls.
  //
  // This does NOT delete the node.
  void RemoveNode(BookmarkNode* node);

  // Called when done loading. Updates internal state and notifies observers.
  void DoneLoading(std::unique_ptr<BookmarkLoadDetails> details);

  // Adds the |node| at |parent| in the specified |index| and notifies its
  // observers.
  BookmarkNode* AddNode(BookmarkNode* parent,
                        int index,
                        std::unique_ptr<BookmarkNode> node);

  // Adds |node| to |index_| and recursisvely invokes this for all children.
  void AddNodeToIndexRecursive(BookmarkNode* node);

  // Returns true if the parent and index are valid.
  bool IsValidIndex(const BookmarkNode* parent, int index, bool allow_end);

  // Notification that a favicon has finished loading. If we can decode the
  // favicon, FaviconLoaded is invoked.
  void OnFaviconDataAvailable(
      BookmarkNode* node,
      favicon_base::IconType icon_type,
      const favicon_base::FaviconImageResult& image_result);

  // Invoked from the node to load the favicon. Requests the favicon from the
  // favicon service.
  void LoadFavicon(BookmarkNode* node, favicon_base::IconType icon_type);

  // Called to notify the observers that the favicon has been loaded.
  void FaviconLoaded(const BookmarkNode* node);

  // If we're waiting on a favicon for node, the load request is canceled.
  void CancelPendingFaviconLoadRequests(BookmarkNode* node);

  // Notifies the observers that a set of changes initiated by a single user
  // action is about to happen and has completed.
  void BeginGroupedChanges();
  void EndGroupedChanges();

  // Generates and returns the next node ID.
  int64_t generate_next_node_id();

  // Sets the maximum node ID to the given value.
  // This is used by BookmarkCodec to report the maximum ID after it's done
  // decoding since during decoding codec assigns node IDs.
  void set_next_node_id(int64_t id) { next_node_id_ = id; }

  BookmarkUndoDelegate* undo_delegate() const;

  std::unique_ptr<BookmarkClient> client_;

  // Whether the initial set of data has been loaded.
  bool loaded_ = false;

  // See |root_| for details.
  std::unique_ptr<BookmarkNode> owned_root_;

  // The root node. This contains the bookmark bar node, the 'other' node and
  // the mobile node as children. The value of |root_| is initially that of
  // |owned_root_|. Once loading has completed, |owned_root_| is destroyed and
  // this is set to url_index_->root(). |owned_root_| is done as lots of
  // existing code assumes the root is non-null while loading.
  BookmarkNode* root_ = nullptr;

  BookmarkPermanentNode* bookmark_bar_node_ = nullptr;
  BookmarkPermanentNode* other_node_ = nullptr;
  BookmarkPermanentNode* mobile_node_ = nullptr;

  // The maximum ID assigned to the bookmark nodes in the model.
  int64_t next_node_id_ = 1;

  // The observers.
  base::ObserverList<BookmarkModelObserver> observers_;

  // Used for loading favicons.
  base::CancelableTaskTracker cancelable_task_tracker_;

  // Reads/writes bookmarks to disk.
  std::unique_ptr<BookmarkStorage> store_;

  std::unique_ptr<TitledUrlIndex> index_;

  // Owned by |model_loader_|.
  // WARNING: in some tests this does *not* refer to
  // |ModelLoader::history_bookmark_model_|. This is because some tests
  // directly call DoneLoading().
  // TODO: this is confusing, fix tests not to circumvent ModelLoader.
  scoped_refptr<UrlIndex> url_index_;

  // See description of IsDoingExtensiveChanges above.
  int extensive_changes_ = 0;

  std::unique_ptr<BookmarkExpandedStateTracker> expanded_state_tracker_;

  std::set<std::string> non_cloned_keys_;

  BookmarkUndoDelegate* undo_delegate_ = nullptr;
  std::unique_ptr<BookmarkUndoDelegate> empty_undo_delegate_;

  scoped_refptr<ModelLoader> model_loader_;

  base::WeakPtrFactory<BookmarkModel> weak_factory_{this};

  DISALLOW_COPY_AND_ASSIGN(BookmarkModel);
};

}  // namespace bookmarks

#endif  // COMPONENTS_BOOKMARKS_BROWSER_BOOKMARK_MODEL_H_
