/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 * 
 *   http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

#include "net/instaweb/rewriter/public/google_analytics_filter.h"

#include "net/instaweb/rewriter/google_analytics_snippet.h"
#include "net/instaweb/rewriter/public/rewrite_driver.h"
#include "net/instaweb/rewriter/public/rewrite_test_base.h"
#include "pagespeed/kernel/base/gtest.h"
#include "pagespeed/kernel/base/string.h"
#include "pagespeed/kernel/base/string_util.h"

namespace net_instaweb {

namespace {

// Expected async glue methods as written to the html output.
// The test sets this list in SetUp().
const char kAsyncGlueMethods[] =
    "        '_trackPageview',\n"
    "        '_setCookiePath'\n";

// Typical initialization code for the synchronous snippet.
const char kSyncInit[] =
    "<script type=\"text/javascript\">\n"
    "try {\n"
    "var pageTracker = _gat._getTracker(\"UA-XXXXX-X\");\n"
    "pageTracker._trackPageview();"
    "} catch(err) {}"
    "</script>";

// The initialization code with _getTracker replaced.
const char kAsyncGlueInit[] =
    "<script type=\"text/javascript\">\n"
    "try {\n"
    "var pageTracker = _modpagespeed_getRewriteTracker(\"UA-XXXXX-X\");\n"
    "pageTracker._trackPageview();"
    "} catch(err) {}"
    "</script>";


class GoogleAnalyticsFilterTest : public RewriteTestBase {
 protected:
  virtual void SetUp() {
    // Setup the statistics.
    RewriteTestBase::SetUp();

    GoogleAnalyticsFilter::InitStats(statistics());
    GoogleAnalyticsFilter::MethodVector* glue_methods =
        new GoogleAnalyticsFilter::MethodVector;
    GoogleAnalyticsFilter::MethodVector* unhandled_methods =
        new GoogleAnalyticsFilter::MethodVector;
    glue_methods->push_back("_trackPageview");
    glue_methods->push_back("_setCookiePath");
    unhandled_methods->push_back("_get");
    unhandled_methods->push_back("_getLinkerUrl");

    RewriteDriver* driver = rewrite_driver();
    driver->AppendOwnedPreRenderFilter(new GoogleAnalyticsFilter(
        driver, statistics(), glue_methods, unhandled_methods));
  }

  // Create the expected html.
  GoogleString GetAsyncLoadAndInit(
      const StringPiece& load_prefix, const StringPiece& load_suffix) const {
    return StrCat(
        load_prefix,
        kGaSnippetPrefix,  // from google_analytics_snippet.h
        kAsyncGlueMethods,
        kGaSnippetSuffix,  // from google_analytics_snippet.h
        load_suffix,
        kAsyncGlueInit);
  }

  GoogleAnalyticsFilter* google_analytics_filter_;
};

TEST_F(GoogleAnalyticsFilterTest, SyncScriptSrcMadeAsync) {
  GoogleString html_input = StrCat(
      "<script src=\"http://www.google-analytics.com/ga.js\""
          " type=\"text/javascript\">\n"
      "</script>\n",
      kSyncInit);
  GoogleString expected_html = GetAsyncLoadAndInit(
      "<script type=\"text/javascript\">",
      "\n</script>\n");  // starting newline leftover from empty script
  ValidateExpected("sync_script_src_made_async",
                   html_input, expected_html);
}

// Test the boundary case of load script without a characters node
TEST_F(GoogleAnalyticsFilterTest, SyncScriptSrcNoCharactersNodeMadeAsync) {
  GoogleString html_input = StrCat(
      "<script src=\"http://www.google-analytics.com/ga.js\""
          " type=\"text/javascript\"></script>\n",
      kSyncInit);
  GoogleString expected_html = GetAsyncLoadAndInit(
      "<script type=\"text/javascript\">",
      "</script>\n");  // no newline between script tags
  ValidateExpected("sync_script_src_no_characters_node_made_async",
                   html_input, expected_html);
}

TEST_F(GoogleAnalyticsFilterTest, SyncDocumentWriteMadeAsync) {
  GoogleString html_input = StrCat(
      "<script type='text/javascript'>\n"
      "var gaJsHost = ((\"https:\" == document.location.protocol) ? "
          "\"https://ssl.\" : \"http://www.\");\n"
      "document.write(unescape(\"%3Cscript src=\" + gaJsHost + \"google-"
      "analytics.com/ga.js' type='text/javascript'%3E%3C/script%3E\"));\n"
      "</script>\n",
      kSyncInit);
  GoogleString expected_html = GetAsyncLoadAndInit(
      "<script type='text/javascript'>\n"
      "var gaJsHost = ((\"https:\" == document.location.protocol) ? "
          "\"https://ssl.\" : \"http://www.\");\n",
      "\n</script>\n");
  ValidateExpected("sync_document_write_made_async",
                   html_input, expected_html);
}

// Test the boundary case of "document.write" at the first position
// of the characters node.
TEST_F(GoogleAnalyticsFilterTest, SyncDocumentWriteAtPositionZero) {
  GoogleString html_input = StrCat(
      "<script type='text/javascript'>document.write("
          "unescape(\"%3Cscript src=\"http://www.google-analytics.com/ga.js' "
          "type='text/javascript'%3E%3C/script%3E\"));\n"
      "</script>\n",
      kSyncInit);
  GoogleString expected_html = GetAsyncLoadAndInit(
      "<script type='text/javascript'>",
      "\n</script>\n");
  ValidateExpected("sync_document_write_made_async",
                   html_input, expected_html);
}

TEST_F(GoogleAnalyticsFilterTest, MultipleLoadReducedToSingle) {
  GoogleString html_input = StrCat(
      "<script type='text/javascript'>document.write("
          "unescape(\"%3Cscript src=\"http://www.google-analytics.com/ga.js' "
          "type='text/javascript'%3E%3C/script%3E\"));\n"
      "</script>\n",
      kSyncInit,
      "<script type='text/javascript'>\n"
      "var gaJsHost = ((\"https:\" == document.location.protocol) ? "
          "\"https://ssl.\" : \"http://www.\");\n"
      "document.write(unescape(\"%3Cscript src=\" + gaJsHost + \"google-"
      "analytics.com/ga.js' type='text/javascript'%3E%3C/script%3E\"));\n"
      "</script>\n",
      kSyncInit,
      "<script src=\"http://www.google-analytics.com/ga.js\""
          " type=\"text/javascript\"></script>",
      kSyncInit);
  GoogleString expected_html = StrCat(
      GetAsyncLoadAndInit(
          "<script type='text/javascript'>",
          "\n</script>\n"),
      "<script type='text/javascript'>\n"
      "var gaJsHost = ((\"https:\" == document.location.protocol) ?"
          " \"https://ssl.\" : \"http://www.\");\n"
      // The second load (document.write) is removed completely.
      "\n"
      "</script>\n",
      kAsyncGlueInit,
      // The third load (script src) is removed completely.
      kAsyncGlueInit);
  ValidateExpected("multiple_load_reduced to_single",
                   html_input, expected_html);
}

TEST_F(GoogleAnalyticsFilterTest, AsyncGiveNoChanges) {
  GoogleString html_input =
      "<script type=\"text/javascript\">\n"
      "var _gaq = _gaq || [];\n"
      "_gaq.push(['_setAccount', 'UA-XXXXX-X']);\n"
      "_gaq.push(['_trackPageview']);\n"
      "(function() {\n"
      "  var ga = document.createElement('script');\n"
      "  ga.type = 'text/javascript'; ga.async = true;\n"
      "  ga.src = ('https:' == document.location.protocol ? 'https://ssl' : "
          "'http://www') +\n"
      "    '.google-analytics.com/ga.js';\n"
      "  var s = document.getElementsByTagName('script')[0]\n"
      "  s.parentNode.insertBefore(ga, s);\n"
      "})();\n"
      "</script>\n";
  ValidateNoChanges("async_unchanged", html_input);
}

TEST_F(GoogleAnalyticsFilterTest, NonstandardInitGiveNoChanges) {
  GoogleString html_input =
      "<script type=\"text/javascript\">\n"
      "googleAnalytics = new TFHtmlUtilsGoogleAnalytics({\n"
      "  'trackingcode': \"UA-XXXXX-X\"\n"
      "});\n"
      "googleAnalytics.trackPageview();\n"
      "} catch(err) {}\n"
      "</script>\n";
  ValidateNoChanges("nonstandard_init_unchanged", html_input);
}

TEST_F(GoogleAnalyticsFilterTest, UnhandledCallCausesSkippedRewrite) {
  GoogleString html_input = StrCat(
      "<script src=\"http://www.google-analytics.com/ga.js\""
          " type=\"text/javascript\"></script>\n",
      kSyncInit,
      // _getLinkerUrl is listed as an unhandled call.
      "<script type=\"text/javascript\">pageTracker._getLinkerUrl ();\n"
      "</script>\n");
  ValidateNoChanges("unhandled_call_causes_skipped_rewrite", html_input);
}

TEST_F(GoogleAnalyticsFilterTest, UnknownCallStillAllowsRewrite) {
  GoogleString html_input = StrCat(
      "<script src=\"http://www.google-analytics.com/ga.js\""
          " type=\"text/javascript\"></script>\n",
      kSyncInit,
      // _getFoo is not explicitly listed as an unhandled call.
      "<script type=\"text/javascript\">pageTracker._getFoo();</script>\n");
  GoogleString expected_html = StrCat(
      GetAsyncLoadAndInit(
          "<script type=\"text/javascript\">",
          "</script>\n"),
      "<script type=\"text/javascript\">pageTracker._getFoo();</script>\n");
  ValidateExpected("unknown_call_still_allows_rewrite",
                   html_input, expected_html);
}

}  // namespace

}  // namespace net_instaweb
