// Copyright 2018 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 "components/sync_sessions/test_matchers.h"

#include "components/sessions/core/session_id.h"
#include "components/sync/model/entity_data.h"
#include "components/sync/protocol/session_specifics.pb.h"
#include "components/sync_sessions/synced_session.h"

namespace sync_sessions {
namespace {

using testing::ContainerEq;
using testing::ElementsAreArray;
using testing::Matcher;
using testing::MatcherInterface;
using testing::MatchResultListener;
using testing::PrintToString;

class MatchesHeaderMatcher
    : public MatcherInterface<const sync_pb::SessionSpecifics&> {
 public:
  MatchesHeaderMatcher(Matcher<std::string> session_tag,
                       Matcher<std::vector<int>> window_ids,
                       Matcher<std::vector<int>> tab_ids)
      : session_tag_(session_tag), window_ids_(window_ids), tab_ids_(tab_ids) {}

  bool MatchAndExplain(const sync_pb::SessionSpecifics& actual,
                       MatchResultListener* listener) const override {
    if (!actual.has_header()) {
      *listener << " which is not a header entity";
      return false;
    }
    if (actual.tab_node_id() != -1) {
      *listener << " which has a valid tab node ID: " << actual.tab_node_id();
      return false;
    }
    if (!session_tag_.MatchAndExplain(actual.session_tag(), listener)) {
      *listener << " which contains an unexpected session tag: "
                << actual.session_tag();
      return false;
    }
    std::vector<int> actual_window_ids;
    std::vector<int> actual_tab_ids;
    for (const auto& window : actual.header().window()) {
      actual_window_ids.push_back(window.window_id());
      actual_tab_ids.insert(actual_tab_ids.end(), window.tab().begin(),
                            window.tab().end());
    }
    if (!window_ids_.MatchAndExplain(actual_window_ids, listener)) {
      *listener << " which contains unexpected windows: "
                << PrintToString(actual_window_ids);
      return false;
    }
    if (!tab_ids_.MatchAndExplain(actual_tab_ids, listener)) {
      *listener << " which contains unexpected tabs: "
                << PrintToString(actual_tab_ids);
      return false;
    }
    return true;
  }

  void DescribeTo(::std::ostream* os) const override {
    *os << "matches expected header";
  }

  void DescribeNegationTo(::std::ostream* os) const override {
    *os << "does not match expected header";
  }

 private:
  Matcher<std::string> session_tag_;
  Matcher<std::vector<int>> window_ids_;
  Matcher<std::vector<int>> tab_ids_;
};

class MatchesTabMatcher
    : public MatcherInterface<const sync_pb::SessionSpecifics&> {
 public:
  MatchesTabMatcher(Matcher<std::string> session_tag,
                    Matcher<int> window_id,
                    Matcher<int> tab_id,
                    Matcher<int> tab_node_id,
                    Matcher<std::vector<std::string>> urls)
      : session_tag_(session_tag),
        window_id_(window_id),
        tab_id_(tab_id),
        tab_node_id_(tab_node_id),
        urls_(urls) {}

  bool MatchAndExplain(const sync_pb::SessionSpecifics& actual,
                       MatchResultListener* listener) const override {
    if (!actual.has_tab()) {
      *listener << " which is not a tab entity";
      return false;
    }
    if (!session_tag_.MatchAndExplain(actual.session_tag(), listener)) {
      *listener << " which contains an unexpected session tag: "
                << actual.session_tag();
      return false;
    }
    if (!window_id_.MatchAndExplain(actual.tab().window_id(), listener)) {
      *listener << " which contains an unexpected window ID: "
                << actual.tab().window_id();
      return false;
    }
    if (!tab_id_.MatchAndExplain(actual.tab().tab_id(), listener)) {
      *listener << " which contains an unexpected tab ID: "
                << actual.tab().tab_id();
      return false;
    }
    if (!tab_node_id_.MatchAndExplain(actual.tab_node_id(), listener)) {
      *listener << " which contains an unexpected tab node ID: "
                << actual.tab_node_id();
      return false;
    }
    std::vector<std::string> actual_urls;
    for (const sync_pb::TabNavigation& navigation : actual.tab().navigation()) {
      actual_urls.push_back(navigation.virtual_url());
    }
    if (!urls_.MatchAndExplain(actual_urls, listener)) {
      *listener << " which contains unexpected navigation URLs";
      return false;
    }
    return true;
  }

  void DescribeTo(::std::ostream* os) const override {
    *os << "matches expected tab";
  }

  void DescribeNegationTo(::std::ostream* os) const override {
    *os << "does not match expected tab";
  }

 private:
  Matcher<std::string> session_tag_;
  Matcher<int> window_id_;
  Matcher<int> tab_id_;
  Matcher<int> tab_node_id_;
  Matcher<std::vector<std::string>> urls_;
};

class MatchesSyncedSessionMatcher
    : public MatcherInterface<const SyncedSession*> {
 public:
  MatchesSyncedSessionMatcher(
      Matcher<std::string> session_tag,
      Matcher<std::map<int, std::vector<int>>> window_id_to_tabs)
      : session_tag_(session_tag), window_id_to_tabs_(window_id_to_tabs) {}

  bool MatchAndExplain(const SyncedSession* actual,
                       MatchResultListener* listener) const override {
    if (!actual) {
      *listener << " which is null";
      return false;
    }
    if (!session_tag_.MatchAndExplain(actual->session_tag, listener)) {
      *listener << " which contains an unexpected session tag: "
                << actual->session_tag;
      return false;
    }

    std::map<int, std::vector<int>> actual_window_id_to_tabs;
    for (const auto& id_and_window : actual->windows) {
      const SessionID actual_window_id = id_and_window.first;
      if (actual_window_id != id_and_window.second->wrapped_window.window_id) {
        *listener << " which has an inconsistent window representation";
        return false;
      }
      actual_window_id_to_tabs.emplace(actual_window_id.id(),
                                       std::vector<int>());
      for (const auto& tab : id_and_window.second->wrapped_window.tabs) {
        actual_window_id_to_tabs[actual_window_id.id()].push_back(
            tab->tab_id.id());
      }
    }

    if (!window_id_to_tabs_.MatchAndExplain(actual_window_id_to_tabs,
                                            listener)) {
      return false;
    }
    return true;
  }

  void DescribeTo(::std::ostream* os) const override {
    *os << "matches expected synced session";
  }

  void DescribeNegationTo(::std::ostream* os) const override {
    *os << "does not match expected synced session";
  }

 private:
  Matcher<std::string> session_tag_;
  Matcher<std::map<int, std::vector<int>>> window_id_to_tabs_;
};

}  // namespace

Matcher<const sync_pb::SessionSpecifics&> MatchesHeader(
    Matcher<std::string> session_tag,
    Matcher<std::vector<int>> window_ids,
    Matcher<std::vector<int>> tab_ids) {
  return testing::MakeMatcher(
      new MatchesHeaderMatcher(session_tag, window_ids, tab_ids));
}

Matcher<const sync_pb::SessionSpecifics&> MatchesHeader(
    Matcher<std::string> session_tag,
    const std::vector<int>& window_ids,
    const std::vector<int>& tab_ids) {
  return MatchesHeader(session_tag, ElementsAreArray(window_ids),
                       ElementsAreArray(tab_ids));
}

Matcher<const sync_pb::SessionSpecifics&> MatchesTab(
    Matcher<std::string> session_tag,
    Matcher<int> window_id,
    Matcher<int> tab_id,
    Matcher<int> tab_node_id,
    Matcher<std::vector<std::string>> urls) {
  return testing::MakeMatcher(
      new MatchesTabMatcher(session_tag, window_id, tab_id, tab_node_id, urls));
}

Matcher<const sync_pb::SessionSpecifics&> MatchesTab(
    Matcher<std::string> session_tag,
    Matcher<int> window_id,
    Matcher<int> tab_id,
    Matcher<int> tab_node_id,
    const std::vector<std::string>& urls) {
  return MatchesTab(session_tag, window_id, tab_id, tab_node_id,
                    ElementsAreArray(urls));
}

Matcher<const SyncedSession*> MatchesSyncedSession(
    Matcher<std::string> session_tag,
    Matcher<std::map<int, std::vector<int>>> window_id_to_tabs) {
  return testing::MakeMatcher(
      new MatchesSyncedSessionMatcher(session_tag, window_id_to_tabs));
}

Matcher<const SyncedSession*> MatchesSyncedSession(
    Matcher<std::string> session_tag,
    const std::map<int, std::vector<int>>& window_id_to_tabs) {
  return MatchesSyncedSession(session_tag, ContainerEq(window_id_to_tabs));
}

}  // namespace sync_sessions
