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


#ifndef NET_INSTAWEB_REWRITER_PUBLIC_CSS_TAG_SCANNER_H_
#define NET_INSTAWEB_REWRITER_PUBLIC_CSS_TAG_SCANNER_H_

#include "pagespeed/kernel/base/basictypes.h"
#include "pagespeed/kernel/base/string.h"
#include "pagespeed/kernel/base/string_util.h"
#include "pagespeed/kernel/html/html_element.h"
#include "pagespeed/kernel/http/google_url.h"

namespace net_instaweb {

class MessageHandler;
class RewriteOptions;
class ServerContext;
class Writer;

class CssTagScanner {
 public:
  // Helper class for TransformUrls to allow any URL transformation to
  // be applied to a CSS file.
  class Transformer {
   public:
    virtual ~Transformer();

    enum TransformStatus { kSuccess, kNoChange, kFailure };
    // Transforms str in-place.
    // If kSuccess -> transformation succeeded and str may have changed
    // (Generally implementers should only return kSuccess if str changed, but
    // this is merely an optimization. Functionally it doesn't matter).
    // If kNoChange -> transformation succeeded and str was unchanged.
    // If kFailure -> transformation failed. str is undefined, do not use.
    virtual TransformStatus Transform(GoogleString* str) = 0;
  };

  static const char kStylesheet[];
  static const char kAlternate[];
  static const char kUriValue[];

  // An instance of CssTagScanner should be created to use
  // TransformUrlsStreaming; other APIs do not need this.
  // *transformer will be used to to transform URLs in CSS, and *handler for
  // logging.
  CssTagScanner(Transformer* transformer, MessageHandler* handler);

  // Examines an HTML element to determine if it's a CSS link, extracting out
  // the href, the media type (if any) and any nonstandard attributes.  If it's
  // not CSS, href is set to NULL, media is set to "", and no nonstandard
  // attributes are identified.  NULL may be passed for nonstandard_attributes
  // to indicate the caller doesn't need them collected.
  static bool ParseCssElement(HtmlElement* element,
                              HtmlElement::Attribute** href,
                              const char** media,
                              StringPieceVector* nonstandard_attributes);

  // Many callers don't care about nonstandard attributes, so we provide a
  // version that discards that information.
  static bool ParseCssElement(HtmlElement* element,
                              HtmlElement::Attribute** href,
                              const char** media) {
    return ParseCssElement(element, href, media,
                           NULL /* nonstandard attributes */);
  }

  // When parsing streaming input, we need to be told if the given input
  // portion goes up to end-of-file (since it affects whether something
  // may continue the last token of the input).
  enum InputPortion {
    kInputIncludesEnd,
    kInputDoesNotIncludeEnd
  };

  // Scans the contents of a CSS file, looking for the pattern url(xxx).
  // Performs an arbitrary mutation on all such URLs.
  // If xxx is quoted with single-quotes or double-quotes, those are
  // retained and the URL inside is transformed.
  static bool TransformUrls(
      StringPiece contents, Writer* writer, Transformer* transformer,
      MessageHandler* handler);

  // Like above, but handles incomplete input. In such a case, all chunks
  // other than the last one should be served with input_portion ==
  // kInputDoesNotIncludeEnd. Note that this method store some state for
  // reparsing, so you can't concurrently run two streams through the same
  // CssTagScanner object.
  bool TransformUrlsStreaming(
      StringPiece contents, InputPortion input_portion, Writer* writer);

  // Returns what was retained by TransformUrlsStreaming for reparsing.
  // Meant for use in tests.
  GoogleString RetainedForReparse() const { return reparse_; }

  // Does this CSS file contain @import? If so, it cannot be combined with
  // previous CSS files. This may give false-positives, but no false-negatives.
  static bool HasImport(const StringPiece& contents, MessageHandler* handler);

  // Detemines whether this CSS contains a URI value (aka URL).
  static bool HasUrl(const StringPiece& contents);

  // Does this attribute value represent a stylesheet or alternate stylesheet?
  // Should be called with element->AttributeValue(HtmlName::kRel) as the arg.
  static bool IsStylesheetOrAlternate(const StringPiece& attribute_value);

  // Does this rel attribute value represent an alternate stylesheet?
  static bool IsAlternateStylesheet(const StringPiece& attribute_value);

 private:
  enum UrlKind {
    kNone,
    kImport,
    kUrl
  };

  void SerializeUrlUse(UrlKind kind, const GoogleString& url,
                       bool is_quoted, bool have_term_quote, char quote,
                       bool have_term_paren,
                       Writer* writer, bool* ok);

  Transformer* transformer_;
  MessageHandler* handler_;
  GoogleString reparse_;

  DISALLOW_COPY_AND_ASSIGN(CssTagScanner);
};

// Transform URLs by:
//   1 Resolving them against old_base_url,
//   2 Mapping them appropriately with domain_rewrite_filter and then
//   3 Trimming them against new_base_url.
class RewriteDomainTransformer : public CssTagScanner::Transformer {
 public:
  RewriteDomainTransformer(const GoogleUrl* old_base_url,
                           const GoogleUrl* new_base_url,
                           const ServerContext* server_context,
                           const RewriteOptions* options,
                           MessageHandler* handler);
  virtual ~RewriteDomainTransformer();

  virtual TransformStatus Transform(GoogleString* str);

  void set_trim_urls(bool x) { trim_urls_ = x; }

 private:
  const GoogleUrl* old_base_url_;
  const GoogleUrl* new_base_url_;

  const ServerContext* server_context_;
  const RewriteOptions* options_;
  MessageHandler* handler_;

  bool trim_urls_;

  DISALLOW_COPY_AND_ASSIGN(RewriteDomainTransformer);
};


}  // namespace net_instaweb

#endif  // NET_INSTAWEB_REWRITER_PUBLIC_CSS_TAG_SCANNER_H_
