// Copyright (c) 2010 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 "base/json/json_writer.h"
#include "base/stl_util-inl.h"
#include "base/string_tokenizer.h"
#include "base/string_util.h"
#include "chrome/browser/net/test_url_fetcher_factory.h"
#include "chrome/browser/renderer_host/translation_service.h"
#include "chrome/common/render_messages.h"
#include "ipc/ipc_message.h"
#include "net/base/escape.h"
#include "net/url_request/url_request_status.h"
#include "testing/gtest/include/gtest/gtest.h"

typedef std::vector<string16> TextChunks;
typedef std::vector<TextChunks> TextChunksList;

class TestMessageSender : public IPC::Message::Sender {
 public:
  virtual ~TestMessageSender() {
    ClearMessages();
  }

  virtual bool Send(IPC::Message* msg) {
    messages_.push_back(msg);
    return true;
  }

  size_t message_count() const { return messages_.size(); }

  void GetMessageParameters(size_t message_index, int* routing_id,
                            int* work_id, int* error_id,
                            std::vector<string16>* text_chunks) {
    ASSERT_LT(message_index, messages_.size());
    *routing_id = messages_.at(message_index)->routing_id();
    ViewMsg_TranslateTextReponse::Read(messages_.at(message_index),
                                       work_id, error_id, text_chunks);
  }

  void ClearMessages() {
    STLDeleteElements(&messages_);
    messages_.clear();
  }

  std::vector<IPC::Message*> messages_;
  MessageLoop message_loop_;
};

class TestTranslationService : public TranslationService {
 public:
  explicit TestTranslationService(IPC::Message::Sender* message_sender)
      : TranslationService(message_sender) {}

  virtual int GetSendRequestDelay() const {
    return 0;
  }
};

class TranslationServiceTest : public testing::Test {
 public:
  TranslationServiceTest() : translation_service_(&message_sender_) {}

  virtual void SetUp() {
    URLFetcher::set_factory(&url_fetcher_factory_);
  }

  virtual void TearDown() {
    URLFetcher::set_factory(NULL);
  }

 protected:
  TestURLFetcherFactory url_fetcher_factory_;
  TestMessageSender message_sender_;
  TestTranslationService translation_service_;
};

static void SimulateErrorResponse(TestURLFetcher* url_fetcher,
                                  int response_code) {
  url_fetcher->delegate()->OnURLFetchComplete(url_fetcher,
                                              url_fetcher->original_url(),
                                              URLRequestStatus(),
                                              response_code,
                                              ResponseCookies(),
                                              std::string());
}

// Merges the strings in |text_chunks| into the string that the translation
// server received.
static string16 BuildResponseString(const TextChunks& text_chunks) {
  string16 text;
  for (size_t i = 0; i < text_chunks.size(); ++i) {
    text.append(ASCIIToUTF16("<a _CR_TR_ id='"));
    text.append(IntToString16(i));
    text.append(ASCIIToUTF16("'>"));
    text.append(text_chunks.at(i));
    text.append(ASCIIToUTF16("</a>"));
  }
  return text;
}

static void SimulateSuccessfulResponse(TestURLFetcher* url_fetcher,
                                       const TextChunksList& text_chunks_list) {
  scoped_ptr<ListValue> list(new ListValue());
  for (TextChunksList::const_iterator iter = text_chunks_list.begin();
       iter != text_chunks_list.end(); ++iter) {
    list->Append(Value::CreateStringValueFromUTF16(BuildResponseString(*iter)));
  }

  std::string response;
  base::JSONWriter::Write(list.get(), false, &response);
  url_fetcher->delegate()->OnURLFetchComplete(url_fetcher,
                                              url_fetcher->original_url(),
                                              URLRequestStatus(),
                                              200,
                                              ResponseCookies(),
                                              response);
}

static void SimulateSimpleSuccessfulResponse(TestURLFetcher* url_fetcher,
                                             const char* const* c_text_chunks,
                                             size_t text_chunks_length) {
  TextChunks text_chunks;
  for (size_t i = 0; i < text_chunks_length; i++)
    text_chunks.push_back(ASCIIToUTF16(c_text_chunks[i]));

  string16 text = BuildResponseString(text_chunks);

  std::string response;
  scoped_ptr<Value> json_text(Value::CreateStringValueFromUTF16(text));
  base::JSONWriter::Write(json_text.get(), false, &response);
  url_fetcher->delegate()->OnURLFetchComplete(url_fetcher,
                                              url_fetcher->original_url(),
                                              URLRequestStatus(),
                                              200,
                                              ResponseCookies(),
                                              response);
}

// Parses the upload data from |url_fetcher| and puts the value for the q
// parameters in |text_chunks|.
static void ExtractQueryStringsFromUploadData(TestURLFetcher* url_fetcher,
                                              TextChunks* text_chunks) {
  std::string upload_data = url_fetcher->upload_data();

  CStringTokenizer str_tok(upload_data.c_str(), upload_data.c_str() +
                              upload_data.length(), "&");
  while (str_tok.GetNext()) {
    std::string tok = str_tok.token();
    if (tok.size() > 1U && tok.at(0) == 'q' && tok.at(1) == '=')
      text_chunks->push_back(UnescapeForHTML(ASCIIToUTF16(tok.substr(2))));
  }
}

TEST_F(TranslationServiceTest, MergeTestChunks) {
  TranslationService translation_service(NULL);
  std::vector<string16> input;
  input.push_back(ASCIIToUTF16("Hello"));
  string16 result = translation_service.MergeTextChunks(input);
  EXPECT_EQ(ASCIIToUTF16("Hello"), result);
  input.push_back(ASCIIToUTF16(" my name"));
  input.push_back(ASCIIToUTF16(" is"));
  input.push_back(ASCIIToUTF16(" Jay."));
  result = translation_service.MergeTextChunks(input);
  EXPECT_EQ(ASCIIToUTF16("<a _CR_TR_ id='0'>Hello</a>"
                         "<a _CR_TR_ id='1'> my name</a>"
                         "<a _CR_TR_ id='2'> is</a>"
                         "<a _CR_TR_ id='3'> Jay.</a>"),
                         result);
}

TEST_F(TranslationServiceTest, RemoveTag) {
  const char* kInputs[] = {
    "", "Hello", "<a ></a>", " <a href='http://www.google.com'> Link </a>",
    "<a >Link", "<a link</a>", "<a id=1><a id=1>broken</a></a>",
    "<a id=1>broken</a></a> bad bad</a>",
  };
  const char* kExpected[] = {
    "", "Hello", "", "  Link ", "Link", "<a link", "broken", "broken bad bad"
  };

  TranslationService translation_service(NULL);
  ASSERT_EQ(arraysize(kInputs), arraysize(kExpected));
  for (size_t i = 0; i < arraysize(kInputs); ++i) {
    SCOPED_TRACE(::testing::Message::Message() << "Iteration " << i);
    string16 input = ASCIIToUTF16(kInputs[i]);
    string16 output = translation_service.RemoveTag(input);
    EXPECT_EQ(ASCIIToUTF16(kExpected[i]), output);
  }
}

// Tests that we deal correctly with the various results the translation server
// can return, including the buggy ones.
TEST_F(TranslationServiceTest, SplitIntoTextChunks) {
  TranslationService translation_service(NULL);

  // Simple case.
  std::vector<string16> text_chunks;
  translation_service.SplitIntoTextChunks(ASCIIToUTF16("Hello"), &text_chunks);
  ASSERT_EQ(1U, text_chunks.size());
  EXPECT_EQ(ASCIIToUTF16("Hello"), text_chunks[0]);

  text_chunks.clear();

  // Multiple chunks case, correct syntax.
  translation_service.SplitIntoTextChunks(
      ASCIIToUTF16("<a _CR_TR_ id='0'>Bonjour</a>"
                   "<a _CR_TR_ id='1'> mon nom</a>"
                   "<a _CR_TR_ id='2'> est</a>"
                   "<a _CR_TR_ id='3'> Jay.</a>"), &text_chunks);
  ASSERT_EQ(4U, text_chunks.size());
  EXPECT_EQ(ASCIIToUTF16("Bonjour"), text_chunks[0]);
  EXPECT_EQ(ASCIIToUTF16(" mon nom"), text_chunks[1]);
  EXPECT_EQ(ASCIIToUTF16(" est"), text_chunks[2]);
  EXPECT_EQ(ASCIIToUTF16(" Jay."), text_chunks[3]);
  text_chunks.clear();

  // Multiple chunks case, duplicate and unbalanced tags.
  // For info, original input:
  // <a _CR_TRANSLATE_ id='0'> Experience </a><a _CR_TRANSLATE_ id='1'>Nexus One
  // </a><a _CR_TRANSLATE_ id='2'>, the new Android phone from Google</a>
  translation_service.SplitIntoTextChunks(
      ASCIIToUTF16("<a _CR_TR_ id='0'>Experience</a> <a _CR_TR_ id='1'>Nexus"
                   "<a _CR_TR_ id='2'> One,</a></a> <a _CR_TR_ id='2'>the new "
                   "Android Phone</a>"), &text_chunks);
  ASSERT_EQ(3U, text_chunks.size());
  EXPECT_EQ(ASCIIToUTF16("Experience "), text_chunks[0]);
  EXPECT_EQ(ASCIIToUTF16("Nexus"), text_chunks[1]);
  EXPECT_EQ(ASCIIToUTF16(" One, the new Android Phone"), text_chunks[2]);
  text_chunks.clear();

  // Other incorrect case:
  // Original input:
  // <a _CR_TR_ id='0'>Benzinpreis-</a><a _CR_TR_ id='1'>vergleich</a>
  translation_service.SplitIntoTextChunks(
      ASCIIToUTF16("<a _CR_TR_ id='0'>Gasoline <a _CR_TR_ id='1'>"
                   "price-comparison</a></a>"), &text_chunks);
  ASSERT_EQ(2U, text_chunks.size());
  EXPECT_EQ(ASCIIToUTF16("Gasoline "), text_chunks[0]);
  EXPECT_EQ(ASCIIToUTF16("price-comparison"), text_chunks[1]);
  text_chunks.clear();

  // Other incorrect case:
  // Original input:
  // <a _CR_TR_ id='0'>Bußgeld-</a><a _CR_TR_ id='1'>rechner</a>
  translation_service.SplitIntoTextChunks(
      ASCIIToUTF16("<a _CR_TR_ id='1'><a _CR_TR_ id='0'>Fine-computer</a>"
                   "</a>"), &text_chunks);
  ASSERT_EQ(2U, text_chunks.size());
  EXPECT_EQ(ASCIIToUTF16(""), text_chunks[0]);
  EXPECT_EQ(ASCIIToUTF16("Fine-computer"), text_chunks[1]);
  text_chunks.clear();

  translation_service.SplitIntoTextChunks(
      ASCIIToUTF16("<a _CR_TR_ id='0'>The mountain live .</a> "
          "<a _CR_TR_ id='1'>By Philipp Wittrock</a> <a _CR_TR_ id='0'>are</a> "
          "<a _CR_TR_ id='2'>more ...</a> <a _CR_TR_ id='3'>Video</a> "
          "<a _CR_TR_ id='4'>Forum</a>"), &text_chunks);
  ASSERT_EQ(5U, text_chunks.size());
  EXPECT_EQ(ASCIIToUTF16("The mountain live . "), text_chunks[0]);
  EXPECT_EQ(ASCIIToUTF16("By Philipp Wittrock are "), text_chunks[1]);
  EXPECT_EQ(ASCIIToUTF16("more ... "), text_chunks[2]);
  EXPECT_EQ(ASCIIToUTF16("Video "), text_chunks[3]);
  EXPECT_EQ(ASCIIToUTF16("Forum"), text_chunks[4]);
  text_chunks.clear();

  // Make sure we support ending with a start tag.
  translation_service.SplitIntoTextChunks(
      ASCIIToUTF16("<a _CR_TR_ id='0'>Hello</a><a _CR_TR_ id='1'>"),
      &text_chunks);
  ASSERT_EQ(2U, text_chunks.size());
  EXPECT_EQ(ASCIIToUTF16("Hello"), text_chunks[0]);
  EXPECT_EQ(EmptyString16(), text_chunks[1]);
}

// Tests that a successful translate works as expected.
TEST_F(TranslationServiceTest, SimpleSuccessfulTranslation) {
  const char* const kEnglishTextChunks[] = {
      "An atom is talking to another atom:",
      "- I think I lost an electron.",
      "- Are you sure?",
      "- I am positive."
  };

  const char* const kFrenchTextChunks[] = {
      "Un atome parle a un autre atome:",
      "- Je crois que j'ai perdu un electron.",
      "- T'es sur?",
      "- Je suis positif."  // Note that the joke translates poorly in French.
  };

  // Translate some text unsecurely.
  std::vector<string16> text_chunks;
  for (size_t i = 0; i < arraysize(kEnglishTextChunks); ++i)
    text_chunks.push_back(ASCIIToUTF16(kEnglishTextChunks[i]));
  translation_service_.Translate(0, 0, 0, text_chunks, "en", "fr", false);

  TestURLFetcher* url_fetcher = url_fetcher_factory_.GetFetcherByID(0);
  // The message was too short to triger the sending of a request to the
  // translation request. A task has been pushed for that.
  EXPECT_TRUE(url_fetcher == NULL);
  MessageLoop::current()->RunAllPending();

  // Now the task has been run, the message should have been sent.
  url_fetcher = url_fetcher_factory_.GetFetcherByID(0);
  ASSERT_TRUE(url_fetcher != NULL);
  // Check the URL is HTTP.
  EXPECT_FALSE(url_fetcher->original_url().SchemeIsSecure());

  // Let's simulate the JSON response from the server.
  SimulateSimpleSuccessfulResponse(url_fetcher, &(kFrenchTextChunks[0]),
                                   arraysize(kFrenchTextChunks));

  // This should have triggered a ViewMsg_TranslateTextReponse message.
  ASSERT_EQ(1U, message_sender_.message_count());

  // Test the message has the right translation.
  int routing_id = 0;
  int work_id = 0;
  int error_id = 0;
  std::vector<string16> translated_text_chunks;
  message_sender_.GetMessageParameters(0, &routing_id, &work_id, &error_id,
                                       &translated_text_chunks);
  EXPECT_EQ(0, routing_id);
  EXPECT_EQ(0, error_id);
  ASSERT_EQ(arraysize(kFrenchTextChunks), translated_text_chunks.size());
  for (size_t i = 0; i < arraysize(kFrenchTextChunks); ++i)
    EXPECT_EQ(ASCIIToUTF16(kFrenchTextChunks[i]), translated_text_chunks[i]);
}

// Tests that on failure we send the expected error message.
TEST_F(TranslationServiceTest, FailedTranslation) {
  std::vector<string16> text_chunks;
  text_chunks.push_back(ASCIIToUTF16("Hello"));
  translation_service_.Translate(0, 0, 0, text_chunks, "en", "fr", false);

  // Run the task that creates the URLFetcher and sends the request.
  MessageLoop::current()->RunAllPending();

  TestURLFetcher* url_fetcher = url_fetcher_factory_.GetFetcherByID(0);
  ASSERT_TRUE(url_fetcher != NULL);
  SimulateErrorResponse(url_fetcher, 500);

  // This should have triggered a ViewMsg_TranslateTextReponse message.
  ASSERT_EQ(1U, message_sender_.message_count());

  // Test the message has some error.
  int routing_id = 0;
  int work_id = 0;
  int error_id = 0;
  std::vector<string16> translated_text_chunks;
  message_sender_.GetMessageParameters(0, &routing_id, &work_id, &error_id,
                                       &translated_text_chunks);

  EXPECT_NE(0, error_id);  // Anything but 0 means there was an error.
  EXPECT_TRUE(translated_text_chunks.empty());
}

// Tests that a secure translation is done over a secure connection.
TEST_F(TranslationServiceTest, SecureTranslation) {
  std::vector<string16> text_chunks;
  text_chunks.push_back(ASCIIToUTF16("Hello"));
  translation_service_.Translate(0, 0, 0, text_chunks, "en", "fr",
                                 true /* secure */);

  // Run the task that creates the URLFetcher and sends the request.
  MessageLoop::current()->RunAllPending();

  TestURLFetcher* url_fetcher = url_fetcher_factory_.GetFetcherByID(0);
  ASSERT_TRUE(url_fetcher != NULL);
  EXPECT_TRUE(url_fetcher->original_url().SchemeIsSecure());
}

// Test mixing requests from different renderers.
TEST_F(TranslationServiceTest, MultipleRVRequests) {
  const char* const kExpectedRV1_1 = "Bonjour RV1";
  const char* const kExpectedRV1_2 = "Encore bonjour RV1";
  const char* const kExpectedRV1_3 = "Encore bonjour a nouveau RV1";
  const char* const kExpectedRV2 = "Bonjour RV2";
  const char* const kExpectedRV3 = "Bonjour RV3";

  TextChunks text_chunks;
  text_chunks.push_back(ASCIIToUTF16("Hello RV1"));
  text_chunks.push_back(ASCIIToUTF16("Hello again RV1"));
  translation_service_.Translate(1, 0, 0, text_chunks, "en", "fr", false);
  text_chunks.clear();
  text_chunks.push_back(ASCIIToUTF16("Hello RV2"));
  translation_service_.Translate(2, 0, 0, text_chunks, "en", "fr", false);
  text_chunks.clear();
  text_chunks.push_back(ASCIIToUTF16("Hello again one more time RV1"));
  translation_service_.Translate(1, 0, 1, text_chunks, "en", "fr", false);
  text_chunks.clear();
  text_chunks.push_back(ASCIIToUTF16("Hello RV3"));
  translation_service_.Translate(3, 0, 0, text_chunks, "en", "fr", false);

  // Run the tasks that create the URLFetcher and send the requests.
  MessageLoop::current()->RunAllPending();

  // We should have 3 pending URL fetchers.  (The 2 translate requests for RV1
  // should have been grouped in 1.)  Simluate the translation server responses.
  TestURLFetcher* url_fetcher = url_fetcher_factory_.GetFetcherByID(1);
  ASSERT_TRUE(url_fetcher != NULL);

  TextChunksList text_chunks_list;
  text_chunks.clear();
  text_chunks.push_back(ASCIIToUTF16(kExpectedRV1_1));
  text_chunks.push_back(ASCIIToUTF16(kExpectedRV1_2));
  text_chunks_list.push_back(text_chunks);
  text_chunks.clear();
  text_chunks.push_back(ASCIIToUTF16(kExpectedRV1_3));
  text_chunks_list.push_back(text_chunks);
  SimulateSuccessfulResponse(url_fetcher, text_chunks_list);

  url_fetcher = url_fetcher_factory_.GetFetcherByID(2);
  ASSERT_TRUE(url_fetcher != NULL);
  SimulateSimpleSuccessfulResponse(url_fetcher, &kExpectedRV2, 1);

  url_fetcher = url_fetcher_factory_.GetFetcherByID(3);
  ASSERT_TRUE(url_fetcher != NULL);
  SimulateSimpleSuccessfulResponse(url_fetcher, &kExpectedRV3, 1);

  // This should have triggered 4 ViewMsg_TranslateTextReponse messages.
  ASSERT_EQ(4U, message_sender_.message_count());

  const int kExpectedRoutingID[] = { 1, 1, 2, 3 };
  const int kExpectedWorkID[] = { 0, 1, 0, 0 };
  const size_t kExpectedStringCount[] = { 2U, 1U, 1U, 1U };

  // Test the messages have the expected content.
  for (size_t i = 0; i < 4; i++) {
    SCOPED_TRACE(::testing::Message::Message() << "Iteration " << i);
    int routing_id = 0;
    int work_id = 0;
    int error_id = 0;
    std::vector<string16> translated_text_chunks;
    message_sender_.GetMessageParameters(i, &routing_id, &work_id, &error_id,
                                         &translated_text_chunks);
    EXPECT_EQ(kExpectedRoutingID[i], routing_id);
    EXPECT_EQ(kExpectedWorkID[i], work_id);
    EXPECT_EQ(0, error_id);
    EXPECT_EQ(kExpectedStringCount[i], translated_text_chunks.size());
    // TODO(jcampan): we should compare the strings.
  }
}

// Tests sending more than the max size.
TEST_F(TranslationServiceTest, MoreThanMaxSizeRequests) {
  std::string one_kb_string(1024U, 'A');
  TextChunks text_chunks;
  text_chunks.push_back(ASCIIToUTF16(one_kb_string));
  // Send 2 small requests, then a big one.
  translation_service_.Translate(1, 0, 0, text_chunks, "en", "fr", false);
  translation_service_.Translate(1, 0, 1, text_chunks, "en", "fr", false);
  // We need a string big enough to be more than 30KB on top of the other 2
  // requests, but to be less than 30KB when sent (that sizes includes the
  // other parameters required by the translation server).
  std::string twenty_nine_kb_string(29 * 1024, 'G');
  text_chunks.clear();
  text_chunks.push_back(ASCIIToUTF16(twenty_nine_kb_string));
  translation_service_.Translate(1, 0, 2, text_chunks, "en", "fr", false);

  // Without any task been run, the 2 first requests should have been sent.
  TestURLFetcher* url_fetcher = url_fetcher_factory_.GetFetcherByID(1);
  TextChunks query_strings;
  ExtractQueryStringsFromUploadData(url_fetcher, &query_strings);
  ASSERT_EQ(2U, query_strings.size());
  EXPECT_EQ(one_kb_string, UTF16ToASCII(query_strings[0]));
  EXPECT_EQ(one_kb_string, UTF16ToASCII(query_strings[1]));

  // Then when the task runs, the big request is sent.
  MessageLoop::current()->RunAllPending();

  url_fetcher = url_fetcher_factory_.GetFetcherByID(1);
  query_strings.clear();
  ExtractQueryStringsFromUploadData(url_fetcher, &query_strings);
  ASSERT_EQ(1U, query_strings.size());
  EXPECT_EQ(twenty_nine_kb_string, UTF16ToASCII(query_strings[0]));
}

// Test mixing secure/insecure requests and that secure requests are always sent
// over HTTPS.
TEST_F(TranslationServiceTest, MixedHTTPAndHTTPS) {
  const char* kUnsecureMessage = "Hello";
  const char* kSecureMessage = "Hello_Secure";

  std::vector<string16> text_chunks;
  text_chunks.push_back(ASCIIToUTF16(kUnsecureMessage));
  translation_service_.Translate(0, 0, 0, text_chunks, "en", "fr", false);
  text_chunks.clear();
  text_chunks.push_back(ASCIIToUTF16(kSecureMessage));
  translation_service_.Translate(0, 0, 0, text_chunks, "en", "fr", true);

  // Run the task that creates the URLFetcher and send the request.
  MessageLoop::current()->RunAllPending();

  // We only get the last URLFetcher since we id them based on their routing id
  // which we want to be the same in that test.  We'll just check that as
  // expected the last one is the HTTPS and contains only the secure string.
  TestURLFetcher* url_fetcher = url_fetcher_factory_.GetFetcherByID(0);
  TextChunks query_strings;
  ExtractQueryStringsFromUploadData(url_fetcher, &query_strings);
  ASSERT_EQ(1U, query_strings.size());
  EXPECT_EQ(kSecureMessage, UTF16ToASCII(query_strings[0]));
}