// Copyright 2009 Google Inc. All Rights Reserved.
//
// Licensed 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 "pagespeed/rules/enable_gzip_compression.h"

#include <string>

#include "base/logging.h"
#include "pagespeed/core/resource.h"
#include "pagespeed/core/resource_util.h"
#include "pagespeed/core/result_provider.h"
#include "pagespeed/core/rule_input.h"
#include "pagespeed/l10n/l10n.h"
#include "pagespeed/proto/pagespeed_output.pb.h"

namespace {

// Minimum number of bytes before it's typically a win to apply gzip
// to a resource. Below this size, the cost of gzip overhead typically
// outweighs the benefits of gzip compression (i.e. the compressed
// resource is larger than the uncompressed resource).
const size_t kMinGzipSize = 150;

}  // namespace

namespace pagespeed {

namespace rules {

namespace {

class GzipMinifier : public Minifier {
 public:
  GzipMinifier() {}

  // Minifier interface:
  virtual const char* name() const;
  virtual UserFacingString header_format() const;
  virtual UserFacingString body_format() const;
  virtual UserFacingString child_format() const;
  virtual UserFacingString child_format_post_gzip() const;
  virtual const MinifierOutput* Minify(const Resource& resource,
                                       const RuleInput& input) const;

 private:
  bool IsViolation(const Resource& resource) const;

  DISALLOW_COPY_AND_ASSIGN(GzipMinifier);
};

const char* GzipMinifier::name() const {
  return "EnableGzipCompression";
}

UserFacingString GzipMinifier::header_format() const {
  // TRANSLATOR: The name of a Page Speed rule that tells webmasters to enable
  // gzip compression on their servers (i.e. enable compressing the web content
  // before it is sent to clients).  This appears in a list of rule names
  // generated by Page Speed, telling webmasters which rules they broke in their
  // website.
  return _("Enable compression");
}

UserFacingString GzipMinifier::body_format() const {
  // TRANSLATOR: Descriptive header for a list of URLs that were served
  // uncompressed, in violation of the EnableGzipCompression rule.  It tells the
  // webmaster that compressing all of those resources could reduce the amount
  // of data transferred (which would speed up the site).  It is followed by a
  // list of uncompressed resource URLs.  "$1" is a format token that will be
  // replaced by the amount of data transferred saved by compressing (in bytes)
  // (e.g. "32.5KiB").  "$2" will be replaced by the percentage saved by
  // compressing (e.g. "25%").
  return _("Compressing the following resources with gzip could reduce their "
           "transfer size by $1 ($2 reduction).");
}

UserFacingString GzipMinifier::child_format() const {
  // TRANSLATOR: Description of a single resource that was served uncompressed,
  // in violation of the EnableGzipCompression rule.  It gives the URL of the
  // resource, as well as the amount of data that would be saved by compressing.
  // It appears in a list of all the uncompressed resources on a site.  "$1" is
  // a format token that will be replaced with the URL of the uncompressed
  // resource in question.  "$2" will be replaced with the amount of data
  // transferred saved by compressing (in bytes) (e.g. "32.5KiB").  "$3" will be
  // replaced with the percentage of data transferred saved by compressing.
  return _("Compressing $1 could save $2 ($3 reduction).");
}

UserFacingString GzipMinifier::child_format_post_gzip() const {
  // This method should never be called for this rule -- it is only called for
  // resources that were served compressed, and if the resource were
  // compressed, this rule wouldn't be giving a violation!
  DCHECK(false);
  return not_localized("$1 $2 $3");
}

const MinifierOutput* GzipMinifier::Minify(const Resource& resource,
                                           const RuleInput& input) const {
  if (!IsViolation(resource)) {
    return MinifierOutput::CannotBeMinified();
  }

  int compressed_size;
  if (!input.GetCompressedResponseBodySize(resource, &compressed_size)) {
    LOG(ERROR) << "GetCompressedResponseBodySize failed for resource: "
               << resource.GetRequestUrl();
    return MinifierOutput::Error();
  }

  return MinifierOutput::PlainMinifiedSize(compressed_size);
}

bool GzipMinifier::IsViolation(const Resource& resource) const {
  return (!resource_util::IsCompressedResource(resource) &&
          resource_util::IsCompressibleResource(resource) &&
          resource.GetResponseBody().size() >= kMinGzipSize);
}

class EnableCompressionScoreComputer : public CostBasedScoreComputer {
 public:
  EnableCompressionScoreComputer(const RuleResults* results,
                                 int64 max_possible_cost);
  virtual ~EnableCompressionScoreComputer();

 protected:
  virtual int64 ComputeCost();

 private:
  const RuleResults* const results_;
};

EnableCompressionScoreComputer::EnableCompressionScoreComputer(
    const RuleResults* results, int64 max_possible_cost)
    : CostBasedScoreComputer(max_possible_cost),
      results_(results) {
}

EnableCompressionScoreComputer::~EnableCompressionScoreComputer() {}

int64 EnableCompressionScoreComputer::ComputeCost() {
  int64 total_cost = 0;
  for (int idx = 0, end = results_->results_size(); idx < end; ++idx) {
    const Result& result = results_->results(idx);
    total_cost += result.original_response_bytes();
  }

  return total_cost;
}

}  // namespace

EnableGzipCompression::EnableGzipCompression()
    : MinifyRule(new GzipMinifier()) {}

int EnableGzipCompression::ComputeScore(const InputInformation& input_info,
                                        const RuleResults& results) {
  EnableCompressionScoreComputer score_computer(
      &results, resource_util::ComputeCompressibleResponseBytes(input_info));
  return score_computer.ComputeScore();
}

}  // namespace rules

}  // namespace pagespeed
