/*
 * 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.
 */


// Base class for tests which do rewrites within CSS.

#ifndef NET_INSTAWEB_REWRITER_PUBLIC_CSS_REWRITE_TEST_BASE_H_
#define NET_INSTAWEB_REWRITER_PUBLIC_CSS_REWRITE_TEST_BASE_H_

#include "base/logging.h"
#include "net/instaweb/rewriter/public/css_filter.h"
#include "net/instaweb/rewriter/public/rewrite_options.h"
#include "net/instaweb/rewriter/public/rewrite_test_base.h"
#include "pagespeed/kernel/base/statistics.h"
#include "pagespeed/kernel/base/string.h"
#include "pagespeed/kernel/base/string_util.h"
#include "pagespeed/kernel/html/html_parse_test_base.h"

namespace net_instaweb {

class ResourceNamer;

class CssRewriteTestBase : public RewriteTestBase {
 protected:
  CssRewriteTestBase() {
    num_blocks_rewritten_ =
        statistics()->GetVariable(CssFilter::kBlocksRewritten);
    num_fallback_rewrites_ =
        statistics()->GetVariable(CssFilter::kFallbackRewrites);
    num_parse_failures_ = statistics()->GetVariable(CssFilter::kParseFailures);
    num_rewrites_dropped_ =
        statistics()->GetVariable(CssFilter::kRewritesDropped);
    total_bytes_saved_ = statistics()->GetUpDownCounter(
        CssFilter::kTotalBytesSaved);
    total_original_bytes_ =
        statistics()->GetVariable(CssFilter::kTotalOriginalBytes);
    num_uses_ = statistics()->GetVariable(CssFilter::kUses);
    num_flatten_imports_charset_mismatch_ =
        statistics()->GetVariable(CssFilter::kCharsetMismatch);
    num_flatten_imports_invalid_url_ =
        statistics()->GetVariable(CssFilter::kInvalidUrl);
    num_flatten_imports_limit_exceeded_ =
        statistics()->GetVariable(CssFilter::kLimitExceeded);
    num_flatten_imports_minify_failed_ =
        statistics()->GetVariable(CssFilter::kMinifyFailed);
    num_flatten_imports_recursion_ =
        statistics()->GetVariable(CssFilter::kRecursion);
    num_flatten_imports_complex_queries_ =
        statistics()->GetVariable(CssFilter::kComplexQueries);
  }
  ~CssRewriteTestBase();

  virtual void SetUp() {
    RewriteTestBase::SetUp();
    options()->set_always_rewrite_css(true);
    AddFilter(RewriteOptions::kRewriteCss);
  }

  enum ValidationFlags {
    kNoFlags = 0,

    kExpectSuccess = 1<<0,   // CSS parser succeeds and URL should be rewritten.
    kExpectCached = 1<<1,    // CSS parser succeeds and URL should be rewritten,
                             // but everything is from the cache so zero stats.
    kExpectNoChange = 1<<2,  // CSS parser succeeds but the URL is not rewritten
                             // because we increased the size of contents.
    kExpectFallback = 1<<3,  // CSS parser fails, fallback succeeds.
    kExpectFailure = 1<<4,   // CSS parser fails, fallback failed or disabled.

    // TODO(sligocki): Explain why we turn off stats check at each use-site.
    kNoStatCheck = 1<<5,
    // TODO(sligocki): Why would we ever want to clear fetcher?
    kNoClearFetcher = 1<<6,
    // TODO(sligocki): Explain why we turn off other contexts.
    kNoOtherContexts = 1<<7,

    kLinkCharsetIsUTF8 = 1<<8,
    kLinkScreenMedia = 1<<9,
    kLinkPrintMedia = 1<<10,

    kMetaCharsetUTF8 = 1<<11,
    kMetaCharsetISO88591 = 1<<12,
    kMetaHttpEquiv = 1<<13,
    kMetaHttpEquivUnquoted = 1<<14,

    // Flags to the check various import flattening failure statistics.
    kFlattenImportsCharsetMismatch = 1<<15,
    kFlattenImportsInvalidUrl = 1<<16,
    kFlattenImportsLimitExceeded = 1<<17,
    kFlattenImportsMinifyFailed = 1<<18,
    kFlattenImportsRecursion = 1<<19,
    kFlattenImportsComplexQueries = 1<<20,

    // Flags to allow methods to know if the HTML is the test input or output.
    kInputHtml = 1<<21,
    kOutputHtml = 1<<22,
  };

  static bool ExactlyOneTrue(bool a, bool b) {
    return a ^ b;
  }
  static bool ExactlyOneTrue(bool a, bool b, bool c) {
    return ExactlyOneTrue(a, ExactlyOneTrue(b, c));
  }
  static bool ExactlyOneTrue(bool a, bool b, bool c, bool d) {
    return ExactlyOneTrue(a, ExactlyOneTrue(b, c, d));
  }
  static bool ExactlyOneTrue(bool a, bool b, bool c, bool d, bool e) {
    return ExactlyOneTrue(a, ExactlyOneTrue(b, c, d, e));
  }

  bool FlagSet(int flags, ValidationFlags f) const {
    return (flags & f) != 0;
  }

  // Sanity check on flags passed in.
  void CheckFlags(int flags) {
    CHECK(ExactlyOneTrue(FlagSet(flags, kExpectSuccess),
                         FlagSet(flags, kExpectCached),
                         FlagSet(flags, kExpectNoChange),
                         FlagSet(flags, kExpectFallback),
                         FlagSet(flags, kExpectFailure)));
  }

  // Check that inline CSS gets rewritten correctly.
  bool ValidateRewriteInlineCss(StringPiece id,
                                StringPiece css_input,
                                StringPiece expected_css_output,
                                int flags);

  void GetNamerForCss(StringPiece id,
                      StringPiece expected_css_output,
                      ResourceNamer* namer);

  GoogleString ExpectedUrlForNamer(const ResourceNamer& namer);

  GoogleString ExpectedUrlForCss(StringPiece id,
                                 StringPiece expected_css_output);

  // Check that external CSS gets rewritten correctly.
  void ValidateRewriteExternalCss(StringPiece id,
                                  StringPiece css_input,
                                  StringPiece expected_css_output,
                                  int flags) {
    ValidateRewriteExternalCssUrl(id, StrCat(kTestDomain, id, ".css"),
                                  css_input, expected_css_output, flags);
  }

  void ValidateRewriteExternalCssUrl(StringPiece id,
                                     StringPiece css_url,
                                     StringPiece css_input,
                                     StringPiece expected_css_output,
                                     int flags);

  // Makes an HTML document with an external CSS link.
  GoogleString MakeHtmlWithExternalCssLink(StringPiece css_url, int flags,
                                           bool insert_debug_message);

  // Makes a CSS body with an external image link, with nice indentation.
  GoogleString MakeIndentedCssWithImage(StringPiece image_url);

  // Makes a minified CSS body with an external image link.
  GoogleString MakeMinifiedCssWithImage(StringPiece image_url);

  // Extract the background image from the css text
  GoogleString ExtractCssBackgroundImage(StringPiece in_css);

  void ValidateRewrite(StringPiece id,
                       StringPiece css_input,
                       StringPiece gold_output,
                       int flags) {
    if (ValidateRewriteInlineCss(StrCat(id, "-inline"),
                                 css_input, gold_output, flags)) {
      // Don't run for external CSS unless inline succeeds.
      ValidateRewriteExternalCss(StrCat(id, "-external"),
                                 css_input, gold_output, flags);
    }
  }

  void ValidateFailParse(StringPiece id, StringPiece css_input) {
    ValidateRewrite(id, css_input, css_input, kExpectFailure);
  }

  // Reset all Variables.
  void ResetStats();

  // Validate HTML rewrite as well as checking statistics.
  bool ValidateWithStats(
      StringPiece id,
      StringPiece html_input, StringPiece expected_html_output,
      StringPiece css_input, StringPiece expected_css_output,
      int flags);

  // Helper to test for how we handle trailing junk
  void TestCorruptUrl(const char* junk);

  Variable* num_blocks_rewritten_;
  Variable* num_fallback_rewrites_;
  Variable* num_parse_failures_;
  Variable* num_rewrites_dropped_;
  UpDownCounter* total_bytes_saved_;
  Variable* total_original_bytes_;
  Variable* num_uses_;
  Variable* num_flatten_imports_charset_mismatch_;
  Variable* num_flatten_imports_invalid_url_;
  Variable* num_flatten_imports_limit_exceeded_;
  Variable* num_flatten_imports_minify_failed_;
  Variable* num_flatten_imports_recursion_;
  Variable* num_flatten_imports_complex_queries_;
};

}  // namespace net_instaweb

#endif  // NET_INSTAWEB_REWRITER_PUBLIC_CSS_REWRITE_TEST_BASE_H_
