// Copyright 2010-2020, 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/candidate_list.h"

#include <memory>
#include <string>

#include "testing/base/public/gunit.h"

namespace mozc {
namespace session {

class CandidateListTest : public testing::Test {
 protected:
  virtual void SetUp() {
    main_list_.reset(new CandidateList(true));
    sub_list_2_.reset(new CandidateList(true));
    sub_sub_list_2_1_.reset(new CandidateList(false));

    main_list_->AddCandidate(0, "0");                           // main0
    main_list_->AddCandidate(1, "1");                           // main1
    sub_list_1_ = main_list_->AllocateSubCandidateList(false);  // main2
    main_list_->AddCandidate(2, "2");                           // main3
    main_list_->AddCandidate(3, "3");                           // main4
    main_list_->AddCandidate(4, "4");                           // main5
    main_list_->AddCandidate(5, "5");                           // main6
    main_list_->AddSubCandidateList(sub_list_2_.get());         // main7
    main_list_->AddCandidate(6, "6");                           // main8
    main_list_->AddCandidate(7, "7");                           // main9
    main_list_->AddCandidate(8, "8");                           // main10
    main_list_->AddCandidate(9, "9");                           // main11
    main_list_->AddCandidate(10, "10");                         // main12

    sub_list_1_->AddCandidate(-1, "-1");  // sub10
    sub_list_1_->AddCandidate(-2, "-2");  // sub11
    sub_list_1_->AddCandidate(-3, "-3");  // sub12
    sub_list_1_->AddCandidate(-4, "-4");  // sub13
    sub_list_1_->AddCandidate(-5, "-5");  // sub14

    sub_list_2_->AddSubCandidateList(sub_sub_list_2_1_.get());  // sub20
    sub_list_2_->AddCandidate(21, "21");                        // sub21
    sub_list_2_->AddCandidate(22, "22");                        // sub22
    sub_list_2_->AddCandidate(23, "23");                        // sub23
    sub_list_2_->AddCandidate(24, "24");                        // sub24
    sub_list_2_->AddCandidate(25, "25");                        // sub25

    sub_sub_list_2_1_->AddCandidate(210, "210");  // subsub210
    sub_sub_list_2_1_->AddCandidate(211, "211");  // subsub211
    sub_sub_list_2_1_->AddCandidate(212, "212");  // subsub212
  }

  std::unique_ptr<CandidateList> main_list_;
  // sub_list_1_ will be initialized on the fly.
  CandidateList *sub_list_1_;
  std::unique_ptr<CandidateList> sub_list_2_;
  std::unique_ptr<CandidateList> sub_sub_list_2_1_;
};

TEST_F(CandidateListTest, MoveToId) {
  // main0
  EXPECT_TRUE(main_list_->MoveToId(0));
  EXPECT_EQ(0, main_list_->focused_id());
  EXPECT_EQ(0, main_list_->focused_index());

  // main1
  EXPECT_TRUE(main_list_->MoveToId(1));
  EXPECT_EQ(1, main_list_->focused_id());
  EXPECT_EQ(1, main_list_->focused_index());

  // (main2, sub13)
  EXPECT_TRUE(main_list_->MoveToId(-4));
  EXPECT_EQ(-4, main_list_->focused_id());
  EXPECT_EQ(2, main_list_->focused_index());
  EXPECT_EQ(3, sub_list_1_->focused_index());

  // (main7, sub20, subsub210)
  EXPECT_TRUE(main_list_->MoveToId(210));
  EXPECT_EQ(210, main_list_->focused_id());
  EXPECT_EQ(7, main_list_->focused_index());
  EXPECT_EQ(0, sub_list_2_->focused_index());
  EXPECT_EQ(0, sub_sub_list_2_1_->focused_index());

  // Invalid IDs
  EXPECT_FALSE(main_list_->MoveToId(13));
  EXPECT_FALSE(main_list_->MoveToId(-6));
  EXPECT_FALSE(main_list_->MoveToId(9999));
}

TEST_F(CandidateListTest, MoveNext) {
  // main0 -> main1
  EXPECT_TRUE(main_list_->MoveToId(0));
  EXPECT_TRUE(main_list_->MoveNext());
  EXPECT_EQ(1, main_list_->focused_id());
  EXPECT_EQ(1, main_list_->focused_index());

  // main1 -> (main2, sub10)
  EXPECT_TRUE(main_list_->MoveNext());
  EXPECT_EQ(-1, main_list_->focused_id());
  EXPECT_EQ(2, main_list_->focused_index());
  EXPECT_EQ(0, sub_list_1_->focused_index());

  // (main2, sub10) -> (main2, sub11)
  EXPECT_TRUE(main_list_->MoveNext());
  EXPECT_EQ(-2, main_list_->focused_id());
  EXPECT_EQ(2, main_list_->focused_index());
  EXPECT_EQ(1, sub_list_1_->focused_index());

  // (main2, sub14) -> main3: no rotation
  EXPECT_TRUE(main_list_->MoveToId(-5));
  EXPECT_TRUE(main_list_->MoveNext());
  EXPECT_EQ(2, main_list_->focused_id());
  EXPECT_EQ(3, main_list_->focused_index());

  // (main7, sub25) -> (main7, sub20, subsub210): rotation
  EXPECT_TRUE(main_list_->MoveToId(25));
  EXPECT_TRUE(main_list_->MoveNext());
  EXPECT_EQ(210, main_list_->focused_id());
  EXPECT_EQ(7, main_list_->focused_index());
  EXPECT_EQ(0, sub_list_2_->focused_index());
  EXPECT_EQ(0, sub_sub_list_2_1_->focused_index());

  // (main7, sub20, subsub210) -> (main7, sub20, subsub211)
  EXPECT_TRUE(main_list_->MoveNext());
  EXPECT_EQ(211, main_list_->focused_id());
  EXPECT_EQ(7, main_list_->focused_index());
  EXPECT_EQ(0, sub_list_2_->focused_index());
  EXPECT_EQ(1, sub_sub_list_2_1_->focused_index());
}

TEST_F(CandidateListTest, MovePrev) {
  // main1 -> main0
  EXPECT_TRUE(main_list_->MoveToId(1));
  EXPECT_TRUE(main_list_->MovePrev());
  EXPECT_EQ(0, main_list_->focused_id());
  EXPECT_EQ(0, main_list_->focused_index());

  // (main2, sub10) -> main1: no rotation
  EXPECT_TRUE(main_list_->MoveToId(-1));
  EXPECT_TRUE(main_list_->MovePrev());
  EXPECT_EQ(1, main_list_->focused_id());

  // (main7, sub20, subsub210) -> (main7, sub25)
  EXPECT_TRUE(main_list_->MoveToId(210));
  EXPECT_TRUE(main_list_->MovePrev());
  EXPECT_EQ(25, main_list_->focused_id());
}

TEST_F(CandidateListTest, MoveNextPage) {
  // main3 -> main9
  EXPECT_TRUE(main_list_->MoveToId(2));
  EXPECT_TRUE(main_list_->MoveNextPage());
  EXPECT_EQ(7, main_list_->focused_id());

  // main9 -> main0
  EXPECT_TRUE(main_list_->MoveNextPage());
  EXPECT_EQ(0, main_list_->focused_id());

  // (main2, sub10) -> main9: no ratation
  EXPECT_TRUE(main_list_->MoveToId(-1));
  EXPECT_TRUE(main_list_->MoveNextPage());
  EXPECT_EQ(7, main_list_->focused_id());

  // (main7, sub20, subsub210) -> (main7, sub20, subsub210)
  EXPECT_TRUE(main_list_->MoveToId(210));
  EXPECT_TRUE(main_list_->MoveNextPage());
  EXPECT_EQ(210, main_list_->focused_id());
}

TEST_F(CandidateListTest, MovePrevPage) {
  // main3 -> main9
  EXPECT_TRUE(main_list_->MoveToId(2));
  EXPECT_TRUE(main_list_->MovePrevPage());
  EXPECT_EQ(7, main_list_->focused_id());

  // main9 -> main0
  EXPECT_TRUE(main_list_->MovePrevPage());
  EXPECT_EQ(0, main_list_->focused_id());

  // (main2, sub10) -> main9: no ratation
  EXPECT_TRUE(main_list_->MoveToId(-1));
  EXPECT_TRUE(main_list_->MovePrevPage());
  EXPECT_EQ(7, main_list_->focused_id());

  // (main7, sub20, subsub210) -> (main7, sub20, subsub210)
  EXPECT_TRUE(main_list_->MoveToId(210));
  EXPECT_TRUE(main_list_->MovePrevPage());
  EXPECT_EQ(210, main_list_->focused_id());
}

TEST_F(CandidateListTest, MoveToPageIndex) {
  EXPECT_TRUE(main_list_->MoveToId(0));

  // main1
  EXPECT_TRUE(main_list_->MoveToPageIndex(1));
  EXPECT_EQ(1, main_list_->focused_id());

  // (main2, sub10)
  EXPECT_TRUE(main_list_->MoveToPageIndex(2));
  EXPECT_EQ(-1, main_list_->focused_id());

  // main12
  EXPECT_TRUE(main_list_->MoveToId(10));

  // invalid index
  EXPECT_FALSE(main_list_->MoveToPageIndex(7));
}

TEST_F(CandidateListTest, Clear) {
  EXPECT_TRUE(main_list_->MoveToId(0));

  main_list_->Clear();
  EXPECT_FALSE(main_list_->MoveToId(0));
  EXPECT_EQ(0, main_list_->size());

  main_list_->AddCandidate(500, "500");
  main_list_->AddCandidate(501, "501");

  EXPECT_TRUE(main_list_->MoveNext());
  EXPECT_EQ(501, main_list_->focused_id());
  EXPECT_EQ(1, main_list_->focused_index());
}

TEST_F(CandidateListTest, Duplication) {
  CandidateList main_list(true);
  CandidateList sub_list(true);

  main_list.AddCandidate(0, "0");
  main_list.AddCandidate(1, "1");
  main_list.AddCandidate(2, "2");
  main_list.AddCandidate(3, "0");  // dup
  main_list.AddCandidate(4, "0");  // dup
  main_list.AddCandidate(5, "1");  // dup
  main_list.AddSubCandidateList(&sub_list);
  sub_list.AddCandidate(6, "0");  // not dup
  sub_list.AddCandidate(7, "7");
  sub_list.AddCandidate(8, "7");  // dup

  EXPECT_EQ(4, main_list.size());
  EXPECT_EQ(2, sub_list.size());

  main_list.MoveToId(3);
  EXPECT_EQ(0, main_list.focused_id());

  main_list.MoveToId(4);
  EXPECT_EQ(0, main_list.focused_id());

  main_list.MoveToId(5);
  EXPECT_EQ(1, main_list.focused_id());

  main_list.MoveToId(6);
  EXPECT_EQ(6, main_list.focused_id());

  main_list.MoveToId(8);
  EXPECT_EQ(7, main_list.focused_id());
}

TEST_F(CandidateListTest, FocusedId) {
  CandidateList empty_list(true);
  EXPECT_EQ(0, empty_list.focused_id());
}

TEST_F(CandidateListTest, set_page_size) {
  EXPECT_EQ(9, main_list_->page_size());
  // Move to the 10th item.
  EXPECT_TRUE(main_list_->MoveToId(7));

  // Make sure the default values.
  EXPECT_EQ(7, main_list_->focused_id());
  EXPECT_EQ(9, main_list_->focused_index());
  size_t begin, end;
  main_list_->GetPageRange(main_list_->focused_index(), &begin, &end);
  EXPECT_EQ(9, begin);
  EXPECT_EQ(12, end);  // The last index.

  // Change the page size.
  main_list_->set_page_size(11);
  // The id and index should not be changed.
  EXPECT_EQ(7, main_list_->focused_id());
  EXPECT_EQ(9, main_list_->focused_index());

  // The begin and end should be changed.
  main_list_->GetPageRange(main_list_->focused_index(), &begin, &end);
  EXPECT_EQ(0, begin);
  EXPECT_EQ(10, end);
}

TEST_F(CandidateListTest, Attributes) {
  CandidateList main_list(true);

  main_list.AddCandidateWithAttributes(0, "hiragana", HIRAGANA);
  main_list.AddCandidateWithAttributes(1, "f_katakana", FULL_WIDTH | KATAKANA);
  main_list.AddCandidateWithAttributes(2, "h_ascii",
                                       (HALF_WIDTH | ASCII | LOWER));
  main_list.AddCandidateWithAttributes(3, "H_ASCII",
                                       (HALF_WIDTH | ASCII | UPPER));
  // dup entry
  main_list.AddCandidateWithAttributes(4, "h_ascii",
                                       (HALF_WIDTH | ASCII | LOWER));
  main_list.AddCandidateWithAttributes(5, "H_Ascii", (HALF_WIDTH | ASCII));
  main_list.AddCandidateWithAttributes(6, "f_ascii",
                                       (FULL_WIDTH | ASCII | LOWER));

  EXPECT_EQ(6, main_list.size());

  EXPECT_FALSE(main_list.MoveNextAttributes(HALF_WIDTH | KATAKANA));

  // h_ascii
  EXPECT_TRUE(main_list.MoveNextAttributes(HALF_WIDTH | ASCII));
  EXPECT_EQ(2, main_list.focused_id());
  EXPECT_EQ((HALF_WIDTH | ASCII | LOWER),
            main_list.focused_candidate().attributes());

  // H_ASCII
  EXPECT_TRUE(main_list.MoveNextAttributes(HALF_WIDTH | ASCII));
  EXPECT_EQ(3, main_list.focused_id());
  EXPECT_EQ((HALF_WIDTH | ASCII | UPPER),
            main_list.focused_candidate().attributes());

  // ID:4 (h_ascii) should be skipped because it's a dup.

  // H_Ascii
  EXPECT_TRUE(main_list.MoveNextAttributes(HALF_WIDTH | ASCII));
  EXPECT_EQ(5, main_list.focused_id());
  EXPECT_EQ((HALF_WIDTH | ASCII), main_list.focused_candidate().attributes());

  // h_ascii / Looped
  EXPECT_TRUE(main_list.MoveNextAttributes(HALF_WIDTH | ASCII));
  EXPECT_EQ(2, main_list.focused_id());
}

TEST_F(CandidateListTest, Attributes2) {
  CandidateList main_list(true);

  main_list.AddCandidate(0, "dvd");
  main_list.AddCandidate(1, "DVD");
  main_list.AddCandidateWithAttributes(2, "f_dvd", HIRAGANA);
  // id#3 is dup
  main_list.AddCandidateWithAttributes(3, "f_dvd", FULL_WIDTH | KATAKANA);
  main_list.AddCandidateWithAttributes(4, "h_dvd", (HALF_WIDTH | ASCII));
  // id#5 is dup
  main_list.AddCandidateWithAttributes(5, "h_dvd",
                                       (HALF_WIDTH | ASCII | LOWER));
  main_list.AddCandidateWithAttributes(6, "h_DVD",
                                       (HALF_WIDTH | ASCII | UPPER));
  main_list.AddCandidateWithAttributes(7, "h_Dvd",
                                       (HALF_WIDTH | ASCII | CAPITALIZED));
  // id#8 is dup
  main_list.AddCandidateWithAttributes(8, "f_dvd", (FULL_WIDTH | ASCII));
  // id#9 is dup
  main_list.AddCandidateWithAttributes(9, "f_dvd",
                                       (FULL_WIDTH | ASCII | LOWER));
  main_list.AddCandidateWithAttributes(10, "f_DVD",
                                       (FULL_WIDTH | ASCII | UPPER));
  main_list.AddCandidateWithAttributes(11, "f_Dvd",
                                       (FULL_WIDTH | ASCII | CAPITALIZED));
  // id#12 is dup
  main_list.AddCandidateWithAttributes(12, "h_dvd", HALF_WIDTH | KATAKANA);

  EXPECT_EQ(8, main_list.size());

  EXPECT_TRUE(main_list.MoveNextAttributes(FULL_WIDTH | ASCII));

  EXPECT_EQ(2, main_list.focused_id());
  EXPECT_EQ((HIRAGANA | KATAKANA | FULL_WIDTH | ASCII | LOWER),
            main_list.focused_candidate().attributes());

  EXPECT_TRUE(main_list.MoveNextAttributes(FULL_WIDTH | ASCII));
  EXPECT_EQ(10, main_list.focused_id());
  EXPECT_EQ((FULL_WIDTH | ASCII | UPPER),
            main_list.focused_candidate().attributes());

  EXPECT_TRUE(main_list.MoveNextAttributes(FULL_WIDTH | ASCII));
  EXPECT_EQ(11, main_list.focused_id());
  EXPECT_EQ((FULL_WIDTH | ASCII | CAPITALIZED),
            main_list.focused_candidate().attributes());

  EXPECT_TRUE(main_list.MoveNextAttributes(FULL_WIDTH | ASCII));
  EXPECT_EQ(2, main_list.focused_id());
}

TEST_F(CandidateListTest, AttributesWithSubList) {
  CandidateList main_list(true);
  main_list.AddCandidate(0, "kanji");

  CandidateList *sub_list = main_list.AllocateSubCandidateList(false);
  sub_list->AddCandidateWithAttributes(1, "hiragana", HIRAGANA);
  sub_list->AddCandidateWithAttributes(2, "f_katakana", FULL_WIDTH | KATAKANA);
  sub_list->AddCandidateWithAttributes(3, "h_ascii",
                                       (HALF_WIDTH | ASCII | LOWER));
  sub_list->AddCandidateWithAttributes(4, "H_ASCII",
                                       (HALF_WIDTH | ASCII | UPPER));
  sub_list->AddCandidateWithAttributes(5, "H_Ascii",
                                       (HALF_WIDTH | ASCII | CAPITALIZED));
  EXPECT_EQ(2, main_list.size());
  EXPECT_EQ(5, sub_list->size());

  // h_ascii
  EXPECT_TRUE(main_list.MoveNextAttributes(HALF_WIDTH | ASCII));
  EXPECT_EQ(3, main_list.focused_id());
  EXPECT_EQ((HALF_WIDTH | ASCII | LOWER),
            sub_list->focused_candidate().attributes());

  // H_ASCII
  EXPECT_TRUE(main_list.MoveNextAttributes(HALF_WIDTH | ASCII));
  EXPECT_EQ(4, main_list.focused_id());
  EXPECT_EQ((HALF_WIDTH | ASCII | UPPER),
            sub_list->focused_candidate().attributes());

  // H_Ascii
  EXPECT_TRUE(main_list.MoveNextAttributes(HALF_WIDTH | ASCII));
  EXPECT_EQ(5, main_list.focused_id());
  EXPECT_EQ((HALF_WIDTH | ASCII | CAPITALIZED),
            sub_list->focused_candidate().attributes());

  // h_ascii / Looped
  EXPECT_TRUE(main_list.MoveNextAttributes(HALF_WIDTH | ASCII));
  EXPECT_EQ(3, main_list.focused_id());
  EXPECT_EQ((HALF_WIDTH | ASCII | LOWER),
            sub_list->focused_candidate().attributes());
}

TEST_F(CandidateListTest, GetDeepestFocusedCandidate) {
  EXPECT_TRUE(main_list_->MoveToPageIndex(2));
  EXPECT_EQ(0, main_list_->focused_candidate().id());
  EXPECT_TRUE(main_list_->focused_candidate().IsSubcandidateList());

  const Candidate &deepest_candidate = main_list_->GetDeepestFocusedCandidate();
  EXPECT_EQ(-1, deepest_candidate.id());
  EXPECT_FALSE(deepest_candidate.IsSubcandidateList());
}

TEST_F(CandidateListTest, NextAvailableId) {
  EXPECT_EQ(213, main_list_->next_available_id());
  EXPECT_EQ(0, sub_list_1_->next_available_id());
  EXPECT_EQ(213, sub_list_2_->next_available_id());
  EXPECT_EQ(213, sub_sub_list_2_1_->next_available_id());

  // Append duplicate candidate.
  // next_available_id should be incremented.
  sub_sub_list_2_1_->AddCandidate(213, "212");
  EXPECT_EQ(214, main_list_->next_available_id());
  EXPECT_EQ(214, sub_sub_list_2_1_->next_available_id());
}

}  // namespace session
}  // namespace mozc
