// Copyright 2010-2021, Google Inc.
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
//     * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
//     * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
//     * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

#include "session/internal/session_output.h"

#include <cstdint>
#include <string>

#include "base/port.h"
#include "base/text_normalizer.h"
#include "base/util.h"
#include "converter/segments.h"
#include "protocol/candidates.pb.h"
#include "protocol/commands.pb.h"
#include "session/internal/candidate_list.h"
#include "testing/base/public/gunit.h"

namespace mozc {
namespace session {

struct DummySegment {
  const char *value;
  const int32_t usage_id;
  const char *usage_title;
  const char *usage_description;
};

void FillDummySegment(const DummySegment *dummy_segments, const size_t num,
                      Segment *segment, CandidateList *candidate_list) {
  for (size_t i = 0; i < num; ++i) {
    Segment::Candidate *cand = segment->push_back_candidate();
    candidate_list->AddCandidate(i, dummy_segments[i].value);
    cand->value = dummy_segments[i].value;
    cand->usage_id = dummy_segments[i].usage_id;
    cand->usage_title = dummy_segments[i].usage_title;
    cand->usage_description = dummy_segments[i].usage_description;
  }
}

TEST(SessionOutputTest, FillCandidate) {
  Segment segment;
  Candidate candidate;
  CandidateList candidate_list(true);
  commands::Candidates_Candidate candidate_proto;

  const std::string kValue13 = "Value only";
  const std::string kValue42 = "The answer";
  const std::string kPrefix42 = "prefix";
  const std::string kSuffix42 = "suffix";
  const std::string kDescription42 = "description";
  const std::string kSubcandidateList = "Subcandidates";

  // Make 100 candidates
  for (size_t i = 0; i < 100; ++i) {
    segment.push_back_candidate();
  }
  segment.mutable_candidate(13)->value = kValue13;
  segment.mutable_candidate(42)->value = kValue42;
  segment.mutable_candidate(42)->prefix = kPrefix42;
  segment.mutable_candidate(42)->suffix = kSuffix42;
  segment.mutable_candidate(42)->description = kDescription42;
  candidate_list.set_name(kSubcandidateList);
  static const int kFirstIdInSubList = -123;
  candidate_list.AddCandidate(kFirstIdInSubList, "minus 123");
  candidate_list.AddCandidate(-456, "minus 456");
  candidate_list.AddCandidate(-789, "minus 789");

  candidate.set_id(13);
  SessionOutput::FillCandidate(segment, candidate, &candidate_proto);
  EXPECT_EQ(13, candidate_proto.id());
  EXPECT_EQ(kValue13, candidate_proto.value());
  EXPECT_FALSE(candidate_proto.has_annotation());

  candidate.Clear();
  candidate_proto.Clear();
  candidate.set_id(42);
  SessionOutput::FillCandidate(segment, candidate, &candidate_proto);
  EXPECT_EQ(42, candidate_proto.id());
  EXPECT_EQ(kValue42, candidate_proto.value());
  EXPECT_TRUE(candidate_proto.has_annotation());
  EXPECT_EQ(kPrefix42, candidate_proto.annotation().prefix());
  EXPECT_EQ(kSuffix42, candidate_proto.annotation().suffix());
  EXPECT_EQ(kDescription42, candidate_proto.annotation().description());

  candidate.Clear();
  candidate_proto.Clear();
  candidate.set_subcandidate_list(&candidate_list);
  SessionOutput::FillCandidate(segment, candidate, &candidate_proto);
  EXPECT_TRUE(candidate_proto.has_id());
  EXPECT_EQ(kFirstIdInSubList, candidate_proto.id());
  EXPECT_EQ(kSubcandidateList, candidate_proto.value());
  EXPECT_FALSE(candidate_proto.has_annotation());
}

TEST(SessionOutputTest, FillCandidates) {
  Segment segment;
  Candidate candidate;
  CandidateList candidate_list(true);
  CandidateList subcandidate_list(true);
  commands::Candidates candidates_proto;

  const std::string kSubcandidateList = "Subcandidates";
  const char *kValues[5] = {"0", "1", "2:sub0", "3:sub1", "4:sub2"};

  // Make 5 candidates
  for (size_t i = 0; i < 5; ++i) {
    segment.push_back_candidate()->value = kValues[i];
  }

  candidate_list.set_focused(true);
  candidate_list.set_page_size(9);
  candidate_list.AddCandidate(0, "0");
  candidate_list.AddCandidate(1, "1");
  candidate_list.AddSubCandidateList(&subcandidate_list);
  subcandidate_list.set_focused(true);
  subcandidate_list.set_name(kSubcandidateList);
  subcandidate_list.AddCandidate(2, "2");
  subcandidate_list.AddCandidate(3, "3");
  subcandidate_list.AddCandidate(4, "4");

  // Focused index = 0. page_size = 9.
  SessionOutput::FillCandidates(segment, candidate_list, 0, &candidates_proto);
  EXPECT_EQ(9, candidates_proto.page_size());
  EXPECT_EQ(3, candidates_proto.candidate_size());
  EXPECT_EQ(0, candidates_proto.position());
  EXPECT_TRUE(candidates_proto.has_focused_index());
  EXPECT_EQ(0, candidates_proto.focused_index());
  EXPECT_EQ(kValues[0], candidates_proto.candidate(0).value());
  EXPECT_EQ(kValues[1], candidates_proto.candidate(1).value());
  EXPECT_EQ(kSubcandidateList, candidates_proto.candidate(2).value());
  EXPECT_FALSE(candidates_proto.has_subcandidates());

  // Focused index = 2 with a subcandidate list. page_size = 5.
  candidates_proto.Clear();
  candidate_list.MoveToId(3);
  candidate_list.set_page_size(5);
  SessionOutput::FillCandidates(segment, candidate_list, 1, &candidates_proto);
  EXPECT_EQ(5, candidates_proto.page_size());
  EXPECT_EQ(3, candidates_proto.candidate_size());
  EXPECT_EQ(1, candidates_proto.position());
  EXPECT_TRUE(candidates_proto.has_focused_index());
  EXPECT_EQ(2, candidates_proto.focused_index());
  EXPECT_EQ(kValues[0], candidates_proto.candidate(0).value());
  EXPECT_EQ(kValues[1], candidates_proto.candidate(1).value());
  EXPECT_EQ(kSubcandidateList, candidates_proto.candidate(2).value());
  EXPECT_EQ(0, candidates_proto.candidate(0).index());
  EXPECT_EQ(1, candidates_proto.candidate(1).index());
  EXPECT_EQ(2, candidates_proto.candidate(2).index());

  // Check the values of the subcandidate list.
  EXPECT_TRUE(candidates_proto.has_subcandidates());
  EXPECT_EQ(3, candidates_proto.subcandidates().candidate_size());
  EXPECT_EQ(2, candidates_proto.subcandidates().position());
  EXPECT_TRUE(candidates_proto.subcandidates().has_focused_index());
  EXPECT_EQ(1, candidates_proto.subcandidates().focused_index());
  EXPECT_EQ(kValues[2], candidates_proto.subcandidates().candidate(0).value());
  EXPECT_EQ(kValues[3], candidates_proto.subcandidates().candidate(1).value());
  EXPECT_EQ(kValues[4], candidates_proto.subcandidates().candidate(2).value());

  // Check focused_index.
  candidates_proto.Clear();
  candidate_list.set_focused(false);
  subcandidate_list.set_focused(true);
  SessionOutput::FillCandidates(segment, candidate_list, 0, &candidates_proto);
  EXPECT_FALSE(candidates_proto.has_focused_index());
  EXPECT_TRUE(candidates_proto.subcandidates().has_focused_index());

  candidates_proto.Clear();
  candidate_list.set_focused(false);
  subcandidate_list.set_focused(false);
  SessionOutput::FillCandidates(segment, candidate_list, 0, &candidates_proto);
  EXPECT_FALSE(candidates_proto.has_focused_index());
  EXPECT_FALSE(candidates_proto.subcandidates().has_focused_index());

  candidates_proto.Clear();
  candidate_list.set_focused(true);
  subcandidate_list.set_focused(false);
  SessionOutput::FillCandidates(segment, candidate_list, 0, &candidates_proto);
  EXPECT_TRUE(candidates_proto.has_focused_index());
  EXPECT_FALSE(candidates_proto.subcandidates().has_focused_index());
}

TEST(SessionOutputTest, FillAllCandidateWords) {
  // IDs are ordered by BFS.
  //
  //  ID|Idx| Candidate list tree
  //   1| 0 | [1:[sub1_1,
  //   5| 1 |    sub1_2:[subsub1_1,
  //   6| 2 |            subsub1_2],
  //   2| 3 |    sub1_3],
  //   0| 4 |  2,
  //   3| 5 |  3:[sub2_1,
  //   4| 6 |     sub2_2]]
  CandidateList main_list(true);
  CandidateList sub1(true);
  CandidateList sub2(true);
  CandidateList subsub1(true);
  commands::CandidateList candidates_proto;

  // Initialize Segment
  Segment segment;
  const char *kNormalKey = "key";
  segment.set_key(kNormalKey);
  const char *kDescription = "desc";

  const char *kValues[7] = {"2",      "sub1_1",    "sub1_3",   "sub2_1",
                            "sub2_2", "subsub1_1", "subsub1_2"};
  const size_t kValueSize = arraysize(kValues);
  for (size_t i = 0; i < kValueSize; ++i) {
    Segment::Candidate *candidate = segment.push_back_candidate();
    candidate->content_key = kNormalKey;
    candidate->value = kValues[i];
    candidate->description = kDescription;
    for (size_t j = 0; j < i; ++j) {
      candidate->PushBackInnerSegmentBoundary(1, 1, 1, 1);
    }
  }
  // Set special key to ID:4 / Index:6
  const char *kSpecialKey = "Special Key";
  segment.mutable_candidate(4)->content_key = kSpecialKey;

  // Main
  main_list.AddSubCandidateList(&sub1);
  main_list.AddCandidate(0, kValues[0]);
  main_list.AddSubCandidateList(&sub2);

  // Sub1
  sub1.AddCandidate(1, kValues[1]);
  sub1.AddSubCandidateList(&subsub1);
  sub1.AddCandidate(2, kValues[2]);

  // Sub2
  sub2.AddCandidate(3, kValues[3]);
  sub2.AddCandidate(4, kValues[4]);

  // SubSub1
  subsub1.AddCandidate(5, kValues[5]);
  subsub1.AddCandidate(6, kValues[6]);

  // Set forcus to ID:5 / Index:1
  main_list.set_focused(true);
  sub1.set_focused(true);
  subsub1.set_focused(true);
  main_list.MoveToId(5);
  EXPECT_EQ(5, main_list.focused_id());
  EXPECT_EQ(0, main_list.focused_index());
  EXPECT_EQ(1, sub1.focused_index());
  EXPECT_EQ(0, subsub1.focused_index());
  // End of Initialization

  // Exexcute FillAllCandidateWords
  const commands::Category kCategory = commands::PREDICTION;
  SessionOutput::FillAllCandidateWords(segment, main_list, kCategory,
                                       &candidates_proto);

  // Varidation
  EXPECT_EQ(1, candidates_proto.focused_index());
  EXPECT_EQ(kCategory, candidates_proto.category());
  EXPECT_EQ(kValueSize, candidates_proto.candidates_size());

  EXPECT_EQ(1, candidates_proto.candidates(0).id());
  EXPECT_EQ(5, candidates_proto.candidates(1).id());
  EXPECT_EQ(6, candidates_proto.candidates(2).id());
  EXPECT_EQ(2, candidates_proto.candidates(3).id());
  EXPECT_EQ(0, candidates_proto.candidates(4).id());
  EXPECT_EQ(3, candidates_proto.candidates(5).id());
  EXPECT_EQ(4, candidates_proto.candidates(6).id());

  EXPECT_EQ(0, candidates_proto.candidates(0).index());
  EXPECT_EQ(1, candidates_proto.candidates(1).index());
  EXPECT_EQ(2, candidates_proto.candidates(2).index());
  EXPECT_EQ(3, candidates_proto.candidates(3).index());
  EXPECT_EQ(4, candidates_proto.candidates(4).index());
  EXPECT_EQ(5, candidates_proto.candidates(5).index());
  EXPECT_EQ(6, candidates_proto.candidates(6).index());

  EXPECT_FALSE(candidates_proto.candidates(0).has_key());
  EXPECT_FALSE(candidates_proto.candidates(1).has_key());
  EXPECT_FALSE(candidates_proto.candidates(2).has_key());
  EXPECT_FALSE(candidates_proto.candidates(3).has_key());
  EXPECT_FALSE(candidates_proto.candidates(4).has_key());
  EXPECT_FALSE(candidates_proto.candidates(5).has_key());
  EXPECT_TRUE(candidates_proto.candidates(6).has_key());
  EXPECT_EQ(kSpecialKey, candidates_proto.candidates(6).key());

  EXPECT_EQ(kValues[1], candidates_proto.candidates(0).value());
  EXPECT_EQ(kValues[5], candidates_proto.candidates(1).value());
  EXPECT_EQ(kValues[6], candidates_proto.candidates(2).value());
  EXPECT_EQ(kValues[2], candidates_proto.candidates(3).value());
  EXPECT_EQ(kValues[0], candidates_proto.candidates(4).value());
  EXPECT_EQ(kValues[3], candidates_proto.candidates(5).value());
  EXPECT_EQ(kValues[4], candidates_proto.candidates(6).value());

  EXPECT_TRUE(candidates_proto.candidates(0).has_annotation());
  EXPECT_TRUE(candidates_proto.candidates(1).has_annotation());
  EXPECT_TRUE(candidates_proto.candidates(2).has_annotation());
  EXPECT_TRUE(candidates_proto.candidates(3).has_annotation());
  EXPECT_TRUE(candidates_proto.candidates(4).has_annotation());
  EXPECT_TRUE(candidates_proto.candidates(5).has_annotation());
  EXPECT_TRUE(candidates_proto.candidates(6).has_annotation());

  EXPECT_EQ(1, candidates_proto.candidates(0).num_segments_in_candidate());
  EXPECT_EQ(5, candidates_proto.candidates(1).num_segments_in_candidate());
  EXPECT_EQ(6, candidates_proto.candidates(2).num_segments_in_candidate());
  EXPECT_EQ(2, candidates_proto.candidates(3).num_segments_in_candidate());
  EXPECT_EQ(1, candidates_proto.candidates(4).num_segments_in_candidate());
  EXPECT_EQ(3, candidates_proto.candidates(5).num_segments_in_candidate());
  EXPECT_EQ(4, candidates_proto.candidates(6).num_segments_in_candidate());
}

TEST(SessionOutputTest, FillAllCandidateWords_Attributes) {
  CandidateList candidate_list(true);
  commands::CandidateList candidates_proto;

  // Initialize Segment
  Segment segment;
  const char *kKey = "key";
  segment.set_key(kKey);

  const char *kValues[5] = {"value_0", "value_1", "value_2", "value_3",
                            "value_4"};
  const size_t kValueSize = arraysize(kValues);
  for (size_t i = 0; i < kValueSize; ++i) {
    Segment::Candidate *candidate = segment.push_back_candidate();
    candidate->content_key = kKey;
    candidate->value = kValues[i];

    candidate_list.AddCandidate(i, kValues[i]);
  }

  segment.mutable_candidate(1)->attributes =
      Segment::Candidate::Attribute::USER_DICTIONARY;
  segment.mutable_candidate(2)->attributes =
      Segment::Candidate::Attribute::USER_HISTORY_PREDICTION |
      Segment::Candidate::Attribute::NO_VARIANTS_EXPANSION;
  segment.mutable_candidate(3)->attributes =
      Segment::Candidate::Attribute::SPELLING_CORRECTION |
      Segment::Candidate::Attribute::NO_EXTRA_DESCRIPTION;
  segment.mutable_candidate(4)->attributes =
      Segment::Candidate::Attribute::TYPING_CORRECTION |
      Segment::Candidate::Attribute::BEST_CANDIDATE;

  candidate_list.set_focused(true);
  candidate_list.MoveToId(0);
  EXPECT_EQ(0, candidate_list.focused_id());
  EXPECT_EQ(0, candidate_list.focused_index());
  // End of Initialization

  // Exexcute FillAllCandidateWords
  const commands::Category kCategory = commands::PREDICTION;
  SessionOutput::FillAllCandidateWords(segment, candidate_list, kCategory,
                                       &candidates_proto);

  // Varidation
  EXPECT_EQ(0, candidates_proto.focused_index());
  EXPECT_EQ(kCategory, candidates_proto.category());
  EXPECT_EQ(kValueSize, candidates_proto.candidates_size());

  EXPECT_EQ(0, candidates_proto.candidates(0).attributes_size());

  EXPECT_EQ(1, candidates_proto.candidates(1).attributes_size());
  EXPECT_EQ(commands::CandidateAttribute::USER_DICTIONARY,
            candidates_proto.candidates(1).attributes(0));

  EXPECT_EQ(1, candidates_proto.candidates(2).attributes_size());
  EXPECT_EQ(commands::CandidateAttribute::USER_HISTORY,
            candidates_proto.candidates(2).attributes(0));

  EXPECT_EQ(1, candidates_proto.candidates(3).attributes_size());
  EXPECT_EQ(commands::CandidateAttribute::SPELLING_CORRECTION,
            candidates_proto.candidates(3).attributes(0));

  EXPECT_EQ(1, candidates_proto.candidates(4).attributes_size());
  EXPECT_EQ(commands::CandidateAttribute::TYPING_CORRECTION,
            candidates_proto.candidates(4).attributes(0));
}

TEST(SessionOutputTest, ShouldShowUsages) {
  {
    Segment segment;
    CandidateList candidate_list(true);
    CandidateList sub(true);
    static const DummySegment dummy_segments[] = {
        {"val0", 0, "", ""}, {"val1", 0, "", ""}, {"val2", 0, "", ""},
        {"val3", 0, "", ""}, {"val4", 0, "", ""},
    };
    FillDummySegment(dummy_segments, 5, &segment, &candidate_list);
    candidate_list.AddSubCandidateList(&sub);
    candidate_list.set_focused(true);
    ASSERT_TRUE(candidate_list.MoveToId(0));
    ASSERT_FALSE(SessionOutput::ShouldShowUsages(segment, candidate_list));
  }
  {
    Segment segment;
    CandidateList candidate_list(true);
    CandidateList sub(true);
    static const DummySegment dummy_segments[] = {
        {"val0", 0, "", ""}, {"val1", 10, "title1", ""}, {"val2", 0, "", ""},
        {"val3", 0, "", ""}, {"val4", 0, "", ""},
    };
    FillDummySegment(dummy_segments, 5, &segment, &candidate_list);
    candidate_list.AddSubCandidateList(&sub);
    candidate_list.set_focused(true);
    ASSERT_TRUE(candidate_list.MoveToId(0));
    ASSERT_TRUE(SessionOutput::ShouldShowUsages(segment, candidate_list));
  }
  {
    Segment segment;
    CandidateList candidate_list(true);
    CandidateList sub(true);
    static const DummySegment dummy_segments[] = {
        {"val00", 10, "title00", ""}, {"val01", 0, "", ""},
        {"val02", 0, "", ""},         {"val03", 0, "", ""},
        {"val04", 0, "", ""},         {"val05", 0, "", ""},
        {"val06", 0, "", ""},         {"val07", 0, "", ""},
        {"val08", 0, "", ""},         {"val09", 0, "", ""},
        {"val10", 20, "title10", ""}, {"val11", 0, "", ""},
        {"val12", 0, "", ""},         {"val13", 30, "title13", ""},
        {"val14", 0, "", ""},         {"val15", 0, "", ""},
        {"val16", 0, "", ""},         {"val17", 0, "", ""},
        {"val18", 0, "", ""},         {"val19", 0, "", ""},
        {"val20", 0, "", ""},         {"val21", 0, "", ""},
        {"val22", 0, "", ""},         {"val23", 0, "", ""},
        {"val24", 0, "", ""},         {"val25", 0, "", ""},
        {"val26", 0, "", ""},         {"val27", 0, "", ""},
        {"val28", 0, "", ""},         {"val29", 0, "", ""},
    };
    FillDummySegment(dummy_segments, 30, &segment, &candidate_list);
    candidate_list.AddSubCandidateList(&sub);
    // pages of candidate_list:
    //  [00-08],[09-17],[18-26],[27-29]+subcandidate
    candidate_list.set_focused(true);
    ASSERT_TRUE(candidate_list.MoveToId(0));
    ASSERT_TRUE(SessionOutput::ShouldShowUsages(segment, candidate_list));
    ASSERT_TRUE(candidate_list.MoveToId(8));
    ASSERT_TRUE(SessionOutput::ShouldShowUsages(segment, candidate_list));
    ASSERT_TRUE(candidate_list.MoveToId(9));
    ASSERT_TRUE(SessionOutput::ShouldShowUsages(segment, candidate_list));
    ASSERT_TRUE(candidate_list.MoveToId(17));
    ASSERT_TRUE(SessionOutput::ShouldShowUsages(segment, candidate_list));
    ASSERT_TRUE(candidate_list.MoveToId(18));
    ASSERT_FALSE(SessionOutput::ShouldShowUsages(segment, candidate_list));
    ASSERT_TRUE(candidate_list.MoveToId(26));
    ASSERT_FALSE(SessionOutput::ShouldShowUsages(segment, candidate_list));
    ASSERT_TRUE(candidate_list.MoveToId(27));
    ASSERT_FALSE(SessionOutput::ShouldShowUsages(segment, candidate_list));
  }
}

TEST(SessionOutputTest, FillUsages) {
  Segment segment;
  CandidateList candidate_list(true);
  CandidateList sub(true);
  commands::Candidates candidates_proto;
  static const DummySegment dummy_segments[] = {
      {"val00", 10, "title00", "desc00"},
      {"val01", 0, "", ""},
      {"val02", 0, "", ""},
      {"val03", 0, "", ""},
      {"val04", 20, "title04", "desc04"},
      {"val05", 0, "", ""},
      {"val06", 0, "", ""},
      {"val07", 0, "", ""},
      {"val08", 0, "", ""},
      {"val09", 0, "", ""},
      {"val10", 30, "title10", "desc10"},
      {"val11", 40, "title11", "desc11"},
      {"val12", 50, "title12", "desc12"},
      {"val13", 60, "title13", "desc13"},
      {"val14", 0, "", ""},
      {"val15", 0, "", ""},
      {"val16", 0, "", ""},
      {"val17", 0, "", ""},
      {"val18", 0, "", ""},
      {"val19", 100, "title100", "desc100"},
      {"val20", 110, "title110", "desc110"},
      {"val21", 100, "title100", "desc100"},
      {"val22", 110, "title110", "desc110"},
      {"val23", 0, "", ""},
      {"val24", 0, "", ""},
      {"val25", 0, "", ""},
      {"val26", 0, "", ""},
      {"val27", 0, "", ""},
      {"val28", 0, "", ""},
      {"val29", 0, "", ""},
  };
  FillDummySegment(dummy_segments, 30, &segment, &candidate_list);
  candidate_list.AddSubCandidateList(&sub);

  // pages of candidate_list:
  //  [00-08],[09-17],[18-26],[27-29]+subcandidate

  candidate_list.set_focused(true);

  candidate_list.MoveToId(2);
  candidates_proto.Clear();
  SessionOutput::FillUsages(segment, candidate_list, &candidates_proto);
  ASSERT_TRUE(candidates_proto.has_usages());
  // There is no focused usage.
  EXPECT_FALSE(candidates_proto.usages().has_focused_index());
  EXPECT_EQ(2, candidates_proto.usages().information_size());
  EXPECT_EQ(10, candidates_proto.usages().information(0).id());
  EXPECT_EQ(dummy_segments[0].usage_title,
            candidates_proto.usages().information(0).title());
  EXPECT_EQ(dummy_segments[0].usage_description,
            candidates_proto.usages().information(0).description());
  EXPECT_EQ(20, candidates_proto.usages().information(1).id());
  EXPECT_EQ(dummy_segments[4].usage_title,
            candidates_proto.usages().information(1).title());
  EXPECT_EQ(dummy_segments[4].usage_description,
            candidates_proto.usages().information(1).description());

  candidate_list.MoveToId(12);
  candidates_proto.Clear();
  SessionOutput::FillUsages(segment, candidate_list, &candidates_proto);
  ASSERT_TRUE(candidates_proto.has_usages());
  // Focused usage index is 20
  EXPECT_TRUE(candidates_proto.usages().has_focused_index());
  EXPECT_EQ(2, candidates_proto.usages().focused_index());
  EXPECT_EQ(4, candidates_proto.usages().information_size());
  EXPECT_EQ(30, candidates_proto.usages().information(0).id());
  EXPECT_EQ(dummy_segments[10].usage_title,
            candidates_proto.usages().information(0).title());
  EXPECT_EQ(dummy_segments[10].usage_description,
            candidates_proto.usages().information(0).description());
  EXPECT_EQ(40, candidates_proto.usages().information(1).id());
  EXPECT_EQ(dummy_segments[11].usage_title,
            candidates_proto.usages().information(1).title());
  EXPECT_EQ(dummy_segments[11].usage_description,
            candidates_proto.usages().information(1).description());
  EXPECT_EQ(50, candidates_proto.usages().information(2).id());
  EXPECT_EQ(dummy_segments[12].usage_title,
            candidates_proto.usages().information(2).title());
  EXPECT_EQ(dummy_segments[12].usage_description,
            candidates_proto.usages().information(2).description());
  EXPECT_EQ(60, candidates_proto.usages().information(3).id());
  EXPECT_EQ(dummy_segments[13].usage_title,
            candidates_proto.usages().information(3).title());
  EXPECT_EQ(dummy_segments[13].usage_description,
            candidates_proto.usages().information(3).description());

  candidate_list.MoveToId(19);
  candidates_proto.Clear();
  SessionOutput::FillUsages(segment, candidate_list, &candidates_proto);
  ASSERT_TRUE(candidates_proto.has_usages());
  EXPECT_TRUE(candidates_proto.usages().has_focused_index());
  EXPECT_EQ(0, candidates_proto.usages().focused_index());
  // usages(id:100) of "val19" and "val21" are merged
  EXPECT_EQ(2, candidates_proto.usages().information_size());
  EXPECT_EQ(100, candidates_proto.usages().information(0).id());
  EXPECT_EQ(dummy_segments[19].usage_title,
            candidates_proto.usages().information(0).title());
  EXPECT_EQ(dummy_segments[19].usage_description,
            candidates_proto.usages().information(0).description());
  EXPECT_EQ(110, candidates_proto.usages().information(1).id());
  EXPECT_EQ(dummy_segments[20].usage_title,
            candidates_proto.usages().information(1).title());
  EXPECT_EQ(dummy_segments[20].usage_description,
            candidates_proto.usages().information(1).description());

  candidate_list.MoveToId(20);
  candidates_proto.Clear();
  SessionOutput::FillUsages(segment, candidate_list, &candidates_proto);
  ASSERT_TRUE(candidates_proto.has_usages());
  EXPECT_TRUE(candidates_proto.usages().has_focused_index());
  EXPECT_EQ(1, candidates_proto.usages().focused_index());

  // usages(id:100) of "val19" and "val21" are merged
  candidate_list.MoveToId(21);
  candidates_proto.Clear();
  SessionOutput::FillUsages(segment, candidate_list, &candidates_proto);
  ASSERT_TRUE(candidates_proto.has_usages());
  EXPECT_TRUE(candidates_proto.usages().has_focused_index());
  EXPECT_EQ(0, candidates_proto.usages().focused_index());

  // usages(id:110) of "val20" and "val22" are merged
  candidate_list.MoveToId(22);
  candidates_proto.Clear();
  SessionOutput::FillUsages(segment, candidate_list, &candidates_proto);
  ASSERT_TRUE(candidates_proto.has_usages());
  EXPECT_TRUE(candidates_proto.usages().has_focused_index());
  EXPECT_EQ(1, candidates_proto.usages().focused_index());

  candidate_list.MoveToId(28);
  candidates_proto.Clear();
  SessionOutput::FillUsages(segment, candidate_list, &candidates_proto);
  ASSERT_FALSE(candidates_proto.has_usages());
}

TEST(SessionOutputTest, FillShortcuts) {
  const std::string kDigits = "123456789";

  commands::Candidates candidates_proto1;
  for (size_t i = 0; i < 10; ++i) {
    candidates_proto1.add_candidate();
  }
  ASSERT_EQ(10, candidates_proto1.candidate_size());

  SessionOutput::FillShortcuts(kDigits, &candidates_proto1);
  EXPECT_EQ(kDigits.substr(0, 1),
            candidates_proto1.candidate(0).annotation().shortcut());
  EXPECT_EQ(kDigits.substr(8, 1),
            candidates_proto1.candidate(8).annotation().shortcut());
  EXPECT_FALSE(candidates_proto1.candidate(9).annotation().has_shortcut());

  commands::Candidates candidates_proto2;
  for (size_t i = 0; i < 3; ++i) {
    candidates_proto2.add_candidate();
  }
  ASSERT_EQ(3, candidates_proto2.candidate_size());

  SessionOutput::FillShortcuts(kDigits, &candidates_proto2);
  EXPECT_EQ(kDigits.substr(0, 1),
            candidates_proto2.candidate(0).annotation().shortcut());
  EXPECT_EQ(kDigits.substr(2, 1),
            candidates_proto2.candidate(2).annotation().shortcut());
}

TEST(SessionOutputTest, FillFooter) {
  commands::Candidates candidates;
  EXPECT_TRUE(SessionOutput::FillFooter(commands::SUGGESTION, &candidates));
  EXPECT_TRUE(candidates.has_footer());

#if defined(CHANNEL_DEV) && defined(GOOGLE_JAPANESE_INPUT_BUILD)
  EXPECT_FALSE(candidates.footer().has_label());
  EXPECT_TRUE(candidates.footer().has_sub_label());
  EXPECT_EQ(0, candidates.footer().sub_label().find("build "));
#else   // CHANNEL_DEV && GOOGLE_JAPANESE_INPUT_BUILD
  EXPECT_TRUE(candidates.footer().has_label());
  EXPECT_FALSE(candidates.footer().has_sub_label());
  const char kLabel[] = "Tabキーで選択";
  EXPECT_EQ(kLabel, candidates.footer().label());
#endif  // CHANNEL_DEV && GOOGLE_JAPANESE_INPUT_BUILD

  EXPECT_FALSE(candidates.footer().index_visible());
  EXPECT_FALSE(candidates.footer().logo_visible());

  candidates.Clear();
  EXPECT_TRUE(SessionOutput::FillFooter(commands::PREDICTION, &candidates));
  EXPECT_TRUE(candidates.has_footer());
  EXPECT_FALSE(candidates.footer().has_label());
  EXPECT_TRUE(candidates.footer().index_visible());
  EXPECT_TRUE(candidates.footer().logo_visible());

  candidates.Clear();
  EXPECT_TRUE(SessionOutput::FillFooter(commands::CONVERSION, &candidates));
  EXPECT_TRUE(candidates.has_footer());
  EXPECT_FALSE(candidates.footer().has_label());
  EXPECT_TRUE(candidates.footer().index_visible());
  EXPECT_TRUE(candidates.footer().logo_visible());

  candidates.Clear();
  EXPECT_FALSE(
      SessionOutput::FillFooter(commands::TRANSLITERATION, &candidates));
  EXPECT_FALSE(candidates.has_footer());

  candidates.Clear();
  EXPECT_FALSE(SessionOutput::FillFooter(commands::USAGE, &candidates));
  EXPECT_FALSE(candidates.has_footer());

  candidates.Clear();
  for (int i = 0; i < 20; ++i) {
    commands::Candidates::Candidate *c = candidates.add_candidate();
    c->set_index(i);
    c->set_value("dummy");
    c->set_id(i);
    // Candidates with even Id can be deleted.
    c->mutable_annotation()->set_deletable(i % 2 == 0);
  }
  for (int i = 0; i < 20; ++i) {
    candidates.clear_footer();
    candidates.set_focused_index(i);
    EXPECT_TRUE(SessionOutput::FillFooter(commands::PREDICTION, &candidates));
    if (i % 2 == 0) {
      ASSERT_TRUE(candidates.has_footer());
      ASSERT_TRUE(candidates.footer().has_label());
#if defined(__APPLE__)
      const char kDeleteInstruction[] = "control+fn+deleteで履歴から削除";
#elif defined(OS_CHROMEOS)
      const char kDeleteInstruction[] = "ctrl+alt+backspaceで履歴から削除";
#else   // !__APPLE__ && !OS_CHROMEOS
      const char kDeleteInstruction[] = "Ctrl+Delで履歴から削除";
#endif  // __APPLE__ || OS_CHROMEOS
      EXPECT_EQ(kDeleteInstruction, candidates.footer().label());
#if defined(CHANNEL_DEV) && defined(GOOGLE_JAPANESE_INPUT_BUILD)
    } else {
      EXPECT_FALSE(candidates.footer().has_label());
      EXPECT_TRUE(candidates.footer().has_sub_label());
      EXPECT_EQ(0, candidates.footer().sub_label().find("build "));
#endif  // CHANNEL_DEV && GOOGLE_JAPANESE_INPUT_BUILD
    }
  }
}

TEST(SessionOutputTest, FillSubLabel) {
  commands::Footer footer;
  footer.set_label("to be deleted");
  SessionOutput::FillSubLabel(&footer);
  EXPECT_TRUE(footer.has_sub_label());
  EXPECT_FALSE(footer.has_label());
  EXPECT_GT(footer.sub_label().size(), 6);  // 6 == strlen("build ")
  // sub_label should start with "build ".
  EXPECT_EQ(0, footer.sub_label().find("build "));
}

TEST(SessionOutputTest, AddSegment) {
  commands::Preedit preedit;
  int index = 0;
  {
    // "〜" is a character to be processed by TextNormalizer::NormalizeText
    const std::string kKey = "ゔ〜 preedit focused";
    const std::string kValue = "ゔ〜 PREEDIT FOCUSED";
    const int types = SessionOutput::PREEDIT | SessionOutput::FOCUSED;
    EXPECT_TRUE(SessionOutput::AddSegment(kKey, kValue, types, &preedit));
    EXPECT_EQ(index + 1, preedit.segment_size());
    const commands::Preedit::Segment &segment = preedit.segment(index);

    std::string normalized_key;
    TextNormalizer::NormalizeText(kKey, &normalized_key);
    EXPECT_EQ(normalized_key, segment.key());
    std::string normalized_value;
    TextNormalizer::NormalizeText(kValue, &normalized_value);
    EXPECT_EQ(normalized_value, segment.value());
    EXPECT_EQ(Util::CharsLen(normalized_value), segment.value_length());
    EXPECT_EQ(commands::Preedit::Segment::UNDERLINE, segment.annotation());
    ++index;
  }

  {
    const std::string kKey = "ゔ〜 preedit";
    const std::string kValue = "ゔ〜 PREEDIT";
    const int types = SessionOutput::PREEDIT;
    EXPECT_TRUE(SessionOutput::AddSegment(kKey, kValue, types, &preedit));
    EXPECT_EQ(index + 1, preedit.segment_size());
    const commands::Preedit::Segment &segment = preedit.segment(index);

    std::string normalized_key;
    TextNormalizer::NormalizeText(kKey, &normalized_key);
    EXPECT_EQ(normalized_key, segment.key());
    std::string normalized_value;
    TextNormalizer::NormalizeText(kValue, &normalized_value);
    EXPECT_EQ(normalized_value, segment.value());
    EXPECT_EQ(Util::CharsLen(normalized_value), segment.value_length());
    EXPECT_EQ(commands::Preedit::Segment::UNDERLINE, segment.annotation());
    ++index;
  }

  {
    const std::string kKey = "ゔ〜 conversion focused";
    const std::string kValue = "ゔ〜 CONVERSION FOCUSED";
    const int types = SessionOutput::CONVERSION | SessionOutput::FOCUSED;
    EXPECT_TRUE(SessionOutput::AddSegment(kKey, kValue, types, &preedit));
    EXPECT_EQ(index + 1, preedit.segment_size());
    const commands::Preedit::Segment &segment = preedit.segment(index);

    std::string normalized_key;
    TextNormalizer::NormalizeText(kKey, &normalized_key);
    EXPECT_EQ(normalized_key, segment.key());
    // Normalization is performed in Rewriter.
    std::string normalized_value = kValue;
    EXPECT_EQ(normalized_value, segment.value());
    EXPECT_EQ(Util::CharsLen(normalized_value), segment.value_length());
    EXPECT_EQ(commands::Preedit::Segment::HIGHLIGHT, segment.annotation());
    ++index;
  }

  {
    const std::string kKey = "ゔ〜 conversion";
    const std::string kValue = "ゔ〜 CONVERSION";
    const int types = SessionOutput::CONVERSION;
    EXPECT_TRUE(SessionOutput::AddSegment(kKey, kValue, types, &preedit));
    EXPECT_EQ(index + 1, preedit.segment_size());
    const commands::Preedit::Segment &segment = preedit.segment(index);

    std::string normalized_key;
    TextNormalizer::NormalizeText(kKey, &normalized_key);
    EXPECT_EQ(normalized_key, segment.key());
    // Normalization is performed in Rewriter.
    std::string normalized_value = kValue;
    EXPECT_EQ(normalized_value, segment.value());
    EXPECT_EQ(Util::CharsLen(normalized_value), segment.value_length());
    EXPECT_EQ(commands::Preedit::Segment::UNDERLINE, segment.annotation());
    ++index;
  }

  {
    const std::string kKey = "abc";
    const std::string kValue = "";  // empty value
    const int types = SessionOutput::CONVERSION;
    EXPECT_FALSE(SessionOutput::AddSegment(kKey, kValue, types, &preedit));
    EXPECT_EQ(index, preedit.segment_size());
  }
}

TEST(SessionOutputTest, FillConversionResultWithoutNormalization) {
  const char kInput[] = "ゔ";

  commands::Result result;
  SessionOutput::FillConversionResultWithoutNormalization(kInput, kInput,
                                                          &result);
  EXPECT_EQ(commands::Result::STRING, result.type());
  EXPECT_EQ(kInput, result.key());    // should not be normalized
  EXPECT_EQ(kInput, result.value());  // should not be normalized
}

TEST(SessionOutputTest, FillConversionResult) {
  commands::Result result;
  SessionOutput::FillConversionResult("abc", "ABC", &result);
  EXPECT_EQ(commands::Result::STRING, result.type());
  EXPECT_EQ("abc", result.key());
  EXPECT_EQ("ABC", result.value());
}

TEST(SessionOutputTest, FillPreeditResult) {
  commands::Result result;
  SessionOutput::FillPreeditResult("ABC", &result);
  EXPECT_EQ(commands::Result::STRING, result.type());
  EXPECT_EQ("ABC", result.key());
  EXPECT_EQ("ABC", result.value());
}

TEST(SessionOutputTest, FillAllCandidateWords_NonForcused) {
  // Test against b/3059255
  // Even when no candidate was focused, all_candidate_words had focused_index.

  CandidateList main_list(true);
  commands::CandidateList candidates_proto;
  main_list.AddCandidate(0, "key");

  // Initialize Segment
  Segment segment;
  const char *kNormalKey = "key";
  segment.set_key(kNormalKey);

  Segment::Candidate *candidate = segment.push_back_candidate();
  candidate->content_key = "key";
  candidate->value = "value";

  {
    // Exexcute FillAllCandidateWords
    const commands::Category kCategory = commands::SUGGESTION;
    SessionOutput::FillAllCandidateWords(segment, main_list, kCategory,
                                         &candidates_proto);

    // Varidation
    EXPECT_FALSE(candidates_proto.has_focused_index());
  }
  {
    main_list.set_focused(true);
    // Exexcute FillAllCandidateWords
    // When the category is SUGGESTION, has_focused_index never return true in
    // real usage. This is just a testcase.
    const commands::Category kCategory = commands::SUGGESTION;
    SessionOutput::FillAllCandidateWords(segment, main_list, kCategory,
                                         &candidates_proto);

    // Varidation
    // If a candidate is forcused, true is expected.
    EXPECT_TRUE(candidates_proto.has_focused_index());
  }
}

}  // namespace session
}  // namespace mozc
