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

#ifndef CHROME_BROWSER_SYNC_GLUE_SESSION_MODEL_ASSOCIATOR_H_
#define CHROME_BROWSER_SYNC_GLUE_SESSION_MODEL_ASSOCIATOR_H_
#pragma once

#include <map>
#include <string>
#include <vector>

#include "base/basictypes.h"
#include "base/format_macros.h"
#include "base/gtest_prod_util.h"
#include "base/memory/scoped_vector.h"
#include "base/observer_list.h"
#include "base/string_util.h"
#include "base/threading/non_thread_safe.h"
#include "base/utf_string_conversions.h"
#include "chrome/browser/sessions/session_id.h"
#include "chrome/browser/sessions/session_service.h"
#include "chrome/browser/sessions/session_types.h"
#include "chrome/browser/sync/engine/syncapi.h"
#include "chrome/browser/sync/glue/foreign_session_tracker.h"
#include "chrome/browser/sync/glue/model_associator.h"
#include "chrome/browser/sync/protocol/session_specifics.pb.h"
#include "chrome/browser/sync/syncable/model_type.h"
#include "chrome/browser/ui/browser_window.h"
#include "content/browser/tab_contents/tab_contents.h"

class Profile;
class ProfileSyncService;

namespace sync_api {
class ReadNode;
class WriteNode;
class WriteTransaction;
}  // namespace sync_api

namespace sync_pb {
class SessionSpecifics;
}  // namespace sync_pb

namespace browser_sync {

static const char kSessionsTag[] = "google_chrome_sessions";

// Contains all logic for associating the Chrome sessions model and
// the sync sessions model.
class SessionModelAssociator
    : public PerDataTypeAssociatorInterface<TabContents, size_t>,
      public base::NonThreadSafe {
 public:
  // Does not take ownership of sync_service.
  explicit SessionModelAssociator(ProfileSyncService* sync_service);
  SessionModelAssociator(ProfileSyncService* sync_service,
                         bool setup_for_test);
  virtual ~SessionModelAssociator();

  // The has_nodes out parameter is set to true if the sync model has
  // nodes other than the permanent tagged nodes.  The method may
  // return false if an error occurred.
  virtual bool SyncModelHasUserCreatedNodes(bool* has_nodes);

  // AssociatorInterface and PerDataTypeAssociator Interface implementation.
  virtual void AbortAssociation() {
    // No implementation needed, this associator runs on the main thread.
  }

  // See ModelAssociator interface.
  virtual bool CryptoReadyIfNecessary();

  // Returns sync id for the given chrome model id.
  // Returns sync_api::kInvalidId if the sync node is not found for the given
  // chrome id.
  virtual int64 GetSyncIdFromChromeId(const size_t& id);

  // Returns sync id for the given session tag.
  // Returns sync_api::kInvalidId if the sync node is not found for the given
  // tag
  virtual int64 GetSyncIdFromSessionTag(const std::string& tag);

  // Not used.
  virtual const TabContents* GetChromeNodeFromSyncId(int64 sync_id);

  // Not used.
  virtual bool InitSyncNodeFromChromeId(const size_t& id,
                                        sync_api::BaseNode* sync_node);

  // Resync local window information. Updates the local sessions header node
  // with the status of open windows and the order of tabs they contain. Should
  // only be called for changes that affect a window, not a change within a
  // single tab.
  //
  // If |reload_tabs| is true, will also resync all tabs (same as calling
  // ReassociateTabs with a vector of all tabs).
  void ReassociateWindows(bool reload_tabs);

  // Loads and reassociates the local tabs referenced in |tabs|.
  void ReassociateTabs(const std::vector<TabContents*>& tabs);

  // Reassociates a single tab with the sync model. Will check if the tab
  // already is associated with a sync node and allocate one if necessary.
  void ReassociateTab(const TabContents& tab);

  // Associate a local tab and it's sync node. Will overwrite the contents of
  // the sync node with new specifics built from the tab.
  virtual void Associate(const TabContents* tab, int64 sync_id);

  // Looks up the specified sync node, and marks that tab as closed, then marks
  // the node as free and deletes association.
  virtual void Disassociate(int64 sync_id);

  // Load any foreign session info stored in sync db and update the sync db
  // with local client data. Processes/reuses any sync nodes owned by this
  // client and creates any further sync nodes needed to store local header and
  // tab info.
  virtual bool AssociateModels();

  // Initializes the given sync node from the given chrome node id.
  // Returns false if no sync node was found for the given chrome node id or
  // if the initialization of sync node fails.
  virtual bool InitSyncNodeFromChromeId(const std::string& id,
                                        sync_api::BaseNode* sync_node);

  // Clear local sync data buffers. Does not delete sync nodes to avoid
  // tombstones. TODO(zea): way to eventually delete orphaned nodes.
  virtual bool DisassociateModels();

  // Returns the tag used to uniquely identify this machine's session in the
  // sync model.
  inline const std::string& GetCurrentMachineTag() {
    DCHECK(!current_machine_tag_.empty());
    return current_machine_tag_;
  }

  // Load and associate window and tab data for a foreign session
  bool AssociateForeignSpecifics(const sync_pb::SessionSpecifics& specifics,
                                 int64 modification_time);

  // Removes a foreign session from our internal bookkeeping.
  void DisassociateForeignSession(const std::string& foreign_session_tag);

  // Builds a list of all foreign sessions.
  // Caller does NOT own ForeignSession objects.
  bool GetAllForeignSessions(std::vector<const ForeignSession*>* sessions);

  // Loads all windows for foreign session with session tag |tag|.
  // Caller does NOT own ForeignSession objects.
  bool GetForeignSession(const std::string& tag,
                         std::vector<SessionWindow*>* windows);

  // Looks up the foreign tab identified by |tab_id| and belonging to foreign
  // session |tag|.
  // Caller does NOT own the SessionTab object.
  bool GetForeignTab(const std::string& tag,
                     const SessionID::id_type tab_id,
                     const SessionTab** tab);

  // Specifies whether the window has tabs to sync. The new tab page does not
  // count. If no tabs to sync, it returns true, otherwise false;
  static bool SessionWindowHasNoTabsToSync(const SessionWindow& window);

  // Control which local tabs we're interested in syncing.
  // Ensures the profile matches sync's profile and that the tab has at least
  // one navigation entry and is not an empty tab.
  bool IsValidTab(const TabContents& tab);

  // Control which foreign tabs we're interested in displaying.
  // Checks that the tab has navigations and is not a new tab.
  // Note: a new tab page with back/forward history is valid.
  static bool IsValidSessionTab(const SessionTab& tab);

  // Returns the syncable model type.
  static syncable::ModelType model_type() { return syncable::SESSIONS; }

 private:
  FRIEND_TEST_ALL_PREFIXES(ProfileSyncServiceSessionTest, WriteSessionToNode);
  FRIEND_TEST_ALL_PREFIXES(ProfileSyncServiceSessionTest,
                           WriteFilledSessionToNode);
  FRIEND_TEST_ALL_PREFIXES(ProfileSyncServiceSessionTest,
                           WriteForeignSessionToNode);
  FRIEND_TEST_ALL_PREFIXES(ProfileSyncServiceSessionTest, TabNodePoolEmpty);
  FRIEND_TEST_ALL_PREFIXES(ProfileSyncServiceSessionTest, TabNodePoolNonEmpty);
  FRIEND_TEST_ALL_PREFIXES(SessionModelAssociatorTest, PopulateSessionWindow);
  FRIEND_TEST_ALL_PREFIXES(SessionModelAssociatorTest, PopulateSessionTab);

  // Keep all the links to local tab data in one place.
  class TabLinks {
   public:
    // To support usage as second value in maps we need default and copy
    // constructors.
    TabLinks()
        : sync_id_(0),
          session_tab_(NULL),
          tab_(NULL) {}

    // We only ever have either a SessionTab (for foreign tabs), or a
    // TabContents (for local tabs).
    TabLinks(int64 sync_id, const TabContents* tab)
      : sync_id_(sync_id),
        session_tab_(NULL) {
      tab_ = const_cast<TabContents*>(tab);
    }
    TabLinks(int64 sync_id, const SessionTab* session_tab)
      : sync_id_(sync_id),
        tab_(NULL) {
      session_tab_ = const_cast<SessionTab*>(session_tab);
    }

    inline int64 sync_id() const { return sync_id_; }
    inline const SessionTab* session_tab() const { return session_tab_; }
    inline const TabContents* tab() const { return tab_; }
   private:
    int64 sync_id_;
    SessionTab* session_tab_;
    TabContents* tab_;
  };

  // A pool for managing free/used tab sync nodes. Performs lazy creation
  // of sync nodes when necessary.
  class TabNodePool {
   public:
    explicit TabNodePool(ProfileSyncService* sync_service);
    ~TabNodePool();

    // Add a previously allocated tab sync node to our pool. Increases the size
    // of tab_syncid_pool_ by one and marks the new tab node as free.
    // Note: this should only be called when we discover tab sync nodes from
    // previous sessions, not for freeing tab nodes we created through
    // GetFreeTabNode (use FreeTabNode below for that).
    void AddTabNode(int64 sync_id);

    // Returns the sync_id for the next free tab node. If none are available,
    // creates a new tab node.
    // Note: We make use of the following "id's"
    // - a sync_id: an int64 used in |sync_api::InitByIdLookup|
    // - a tab_id: created by session service, unique to this client
    // - a tab_node_id: the id for a particular sync tab node. This is used
    //   to generate the sync tab node tag through:
    //       tab_tag = StringPrintf("%s_%ui", local_session_tag, tab_node_id);
    // tab_node_id and sync_id are both unique to a particular sync node. The
    // difference is that tab_node_id is controlled by the model associator and
    // is used when creating a new sync node, which returns the sync_id, created
    // by the sync db.
    int64 GetFreeTabNode();

    // Return a tab node to our free pool.
    // Note: the difference between FreeTabNode and AddTabNode is that
    // FreeTabNode does not modify the size of |tab_syncid_pool_|, while
    // AddTabNode increases it by one. In the case of FreeTabNode, the size of
    // the |tab_syncid_pool_| should always be equal to the amount of tab nodes
    // associated with this machine.
    void FreeTabNode(int64 sync_id);

    // Clear tab pool.
    inline void clear() { tab_syncid_pool_.clear(); }

    // Return the number of tab nodes this client currently has allocated
    // (including both free and used nodes)
    inline size_t capacity() const { return tab_syncid_pool_.size(); }

    // Return empty status (all tab nodes are in use).
    inline bool empty() const { return tab_pool_fp_ == -1; }

    // Return full status (no tab nodes are in use).
    inline bool full() {
      return tab_pool_fp_ == static_cast<int64>(tab_syncid_pool_.size())-1;
    }

    inline void set_machine_tag(const std::string& machine_tag) {
      machine_tag_ = machine_tag;
    }
   private:
    // Pool of all available syncid's for tab's we have created.
    std::vector<int64> tab_syncid_pool_;

    // Free pointer for tab pool. Only those node id's, up to and including the
    // one indexed by the free pointer, are valid and free. The rest of the
    // |tab_syncid_pool_| is invalid because the nodes are in use.
    // To get the next free node, use tab_syncid_pool_[tab_pool_fp_--].
    int64 tab_pool_fp_;

    // The machiine tag associated with this tab pool. Used in the title of new
    // sync nodes.
    std::string machine_tag_;

    // Our sync service profile (for making changes to the sync db)
    ProfileSyncService* sync_service_;

    DISALLOW_COPY_AND_ASSIGN(TabNodePool);
  };

  // Datatypes for accessing local tab data.
  typedef std::map<SessionID::id_type, TabLinks> TabLinksMap;

  // Delete all foreign session/window/tab objects allocated dynamically.
  // This is comprised of ForeignSession*, IDToSessionTabMap*, and any orphaned
  // SessionTab*'s.
  void DeleteForeignSessions();

  // Determine if a window is of a type we're interested in syncing.
  static bool ShouldSyncWindowType(const Browser::Type& type);

  // Build a sync tag from tab_node_id.
  static inline std::string TabIdToTag(
      const std::string machine_tag,
      size_t tab_node_id) {
    return StringPrintf("%s %"PRIuS"",
        machine_tag.c_str(), tab_node_id);
  }

  // Initializes the tag corresponding to this machine.
  void InitializeCurrentMachineTag(sync_api::WriteTransaction* trans);

  // Updates the server data based upon the current client session.  If no node
  // corresponding to this machine exists in the sync model, one is created.
  void UpdateSyncModelDataFromClient();

  // Pulls the current sync model from the sync database and returns true upon
  // update of the client model. Will associate any foreign sessions as well as
  // keep track of any local tab nodes, adding them to our free tab node pool.
  bool UpdateAssociationsFromSyncModel(const sync_api::ReadNode& root,
                                       const sync_api::BaseTransaction* trans);

  // Fills a tab sync node with data from a TabContents object.
  // (from a local navigation event)
  bool WriteTabContentsToSyncModel(const Browser& browser,
                                   const TabContents& tab,
                                   const int64 sync_id,
                                   sync_api::WriteTransaction* trans);

  // Used to populate a session window from the session specifics window
  // provided. Tracks any foreign session data created through |tracker|.
  static void PopulateSessionWindowFromSpecifics(
      const std::string& foreign_session_tag,
      const sync_pb::SessionWindow& window,
      const int64 mtime,
      SessionWindow* session_window,
      ForeignSessionTracker* tracker);

  // Used to populate a session tab from the session specifics tab provided.
  static void PopulateSessionTabFromSpecifics(const sync_pb::SessionTab& tab,
                                              const int64 mtime,
                                              SessionTab* session_tab);

  // Used to populate a session tab from the session specifics tab provided.
  static void AppendSessionTabNavigation(
     const sync_pb::TabNavigation& navigation,
     std::vector<TabNavigation>* navigations);

  // Populates the navigation portion of the session specifics.
  static void PopulateSessionSpecificsNavigation(
     const TabNavigation* navigation,
     sync_pb::TabNavigation* tab_navigation);

  // Returns the session service from |sync_service_|.
  SessionService* GetSessionService();

  // Internal method used in the callback to obtain the current session.
  // We don't own |windows|.
  void OnGotSession(int handle, std::vector<SessionWindow*>* windows);

  // Populate a session specifics header from a list of SessionWindows
  void PopulateSessionSpecificsHeader(
      const std::vector<SessionWindow*>& windows,
      sync_pb::SessionHeader* header_s);

  // Populates the window portion of the session specifics.
  void PopulateSessionSpecificsWindow(const SessionWindow& window,
                                      sync_pb::SessionWindow* session_window);

  // Syncs all the tabs in |window| with the local sync db. Will allocate tab
  // nodes if needed.
  bool SyncLocalWindowToSyncModel(const SessionWindow& window);

  // Fills a tab sync node with data from a SessionTab object.
  // (from ReadCurrentSessions)
  bool WriteSessionTabToSyncModel(const SessionTab& tab,
                                  const int64 sync_id,
                                  sync_api::WriteTransaction* trans);

  // Populates the tab portion of the session specifics.
  void PopulateSessionSpecificsTab(const SessionTab& tab,
                                   sync_pb::SessionTab* session_tab);

  // Local client name.
  std::string current_machine_tag_;

  // Pool of all used/available sync nodes associated with tabs.
  TabNodePool tab_pool_;

  // SyncID for the sync node containing all the window information for this
  // client.
  int64 local_session_syncid_;

  // Mapping of current open (local) tabs to their sync identifiers.
  TabLinksMap tab_map_;

  ForeignSessionTracker foreign_session_tracker_;

  // Weak pointer.
  ProfileSyncService* sync_service_;

  // Consumer used to obtain the current session.
  CancelableRequestConsumer consumer_;

  // To avoid certain checks not applicable to tests.
  bool setup_for_test_;

  DISALLOW_COPY_AND_ASSIGN(SessionModelAssociator);
};

}  // namespace browser_sync

#endif  // CHROME_BROWSER_SYNC_GLUE_SESSION_MODEL_ASSOCIATOR_H_