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

//
// Provides the ResourceCombiner class which helps implement filters that
// combine multiple resources, as well as the TimedBool, which is useful
// when figuring out how long results are valid for.

#ifndef NET_INSTAWEB_REWRITER_PUBLIC_RESOURCE_COMBINER_H_
#define NET_INSTAWEB_REWRITER_PUBLIC_RESOURCE_COMBINER_H_

#include "net/instaweb/rewriter/public/resource.h"
#include "net/instaweb/rewriter/public/server_context.h"
#include "net/instaweb/rewriter/public/url_partnership.h"
#include "pagespeed/kernel/base/basictypes.h"
#include "pagespeed/kernel/base/gtest_prod.h"
#include "pagespeed/kernel/base/string.h"
#include "pagespeed/kernel/base/string_util.h"

namespace net_instaweb {

struct ContentType;
class MessageHandler;
class OutputResource;
class RewriteDriver;
class RewriteFilter;
class Writer;

// A boolean with an expiration date.
struct TimedBool {
  // A date, in milliseconds since the epoch, after which value should
  // no longer be considered valid.
  int64 expiration_ms;
  bool value;
};

// This class is a utility for filters that combine multiple resource
// files into one. It provides two major pieces of functionality to help out:
// 1) It keeps a ResourceVector and provides methods to keep track
//    of resources and URLs that can be safely combined together while
//    encoding the information on the pieces in the combined URL.
// 2) It implements Fetch, reconstructing combinations as needed.
class ResourceCombiner {
 public:
  // Slack to leave in URL size, so that other filters running afterwards
  // can expand the URLs without going over maximum allowed sizes.
  //
  // Why 100? First example I saw, CssFilter expanded a CssCombined URL
  // by 36 chars. So 100 seemed like a nice round number to allow two
  // filters to run after this and then for there still be a little slack.
  //
  // TODO(sligocki): Set this more intelligently.
  static const int kUrlSlack = 100;

  // Note: extension should not include the leading dot here.
  ResourceCombiner(RewriteDriver* rewrite_driver,
                   const StringPiece& extension,
                   RewriteFilter* filter);

  virtual ~ResourceCombiner();

  // Resets the current combiner to an empty state, incorporating the base URL.
  // Make sure this gets called before documents --- on a ::Flush() is enough.
  // If a subclass needs to do some of its own reseting, see Clear().
  void Reset();

  // Computes a name for the URL that meets all known character-set and
  // size restrictions.
  GoogleString UrlSafeId() const;

  // Returns the number of URLs that have been successfully added.
  int num_urls() const { return partnership_.num_urls(); }

  const ResourceVector& resources() const { return resources_; }

  // Base common to all URLs. Always has a trailing slash.
  GoogleString ResolvedBase() const { return partnership_.ResolvedBase(); }

  // TODO(jmarantz): rename this to AddResource now that async flow is live.
  TimedBool AddResourceNoFetch(const ResourcePtr& resource,
                               MessageHandler* handler);

 protected:
  // Removes the last resource that was added here, assuming the last call to
  // AddResource was successful.  If the last call to AddResource returned
  // false, behavior is undefined.
  virtual void RemoveLastResource();

  // Returns one resource containing the combination of all added resources,
  // creating it if necessary.  Caller takes ownership.  Returns NULL if the
  // combination does not exist and cannot be created. Will not combine fewer
  // than 2 resources.
  OutputResourcePtr Combine(MessageHandler* handler);

  // Override this if your combination is not a matter of combining
  // text pieces (perhaps adjusted by WritePiece).
  virtual bool WriteCombination(const ResourceVector& combine_resources,
                                const OutputResourcePtr& combination,
                                MessageHandler* handler);

  // Override this to alter how pieces are processed when included inside
  // a combination. Returns whether successful. The default implementation
  // writes input->contents() to the writer without any alteration.
  // 'index' is the position of this piece in the combination, while
  // num_pieces is the total number of pieces.
  virtual bool WritePiece(int index, int num_pieces, const Resource* input,
                          OutputResource* combination, Writer* writer,
                          MessageHandler* handler);

  // Override this if you need to remove some state whenever Reset() is called.
  // Your implementation must call the superclass.
  virtual void Clear();

  ServerContext* const server_context_;
  RewriteDriver* const rewrite_driver_;

 private:
  friend class AggregateCombiner;

  // Implement this to control the content-type given the combination.
  virtual const ContentType* CombinationContentType() = 0;

  // Recomputes the leaf size if our base has changed
  void UpdateResolvedBase();

  // Computes the total size
  void ComputeLeafSize();

  // Incrementally updates the accumulated leaf size without re-examining
  // every element in the combined file.
  void AccumulateLeafSize(const StringPiece& url);

  // Determines whether our accumulated leaf size is too big, taking into
  // account both per-segment and total-url limitations.
  bool UrlTooBig();

  // Computes the total combined resources size.
  // Override this if combined resource size to be restricted to some limit.
  virtual void AccumulateCombinedSize(const ResourcePtr& resource) {}

  // Determines whether our accumulated resources size is too big.
  // Override this if combined resource size to be restricted to some limit.
  virtual bool ContentSizeTooBig() const { return false; }

  // Override this if you need to forbid some combinations based on the
  // content of the resource (e.g. with resource->HttpStatusOk())
  // This is called before the URL is added to UrlPartnership's
  // data structures. If the method returns false, it should set
  // *failure_reason to a user-comprehensible explanation.
  virtual bool ResourceCombinable(Resource* resource,
                                  GoogleString* failure_reason,
                                  MessageHandler* handler);

  UrlPartnership partnership_;
  ResourceVector resources_;
  StringVector multipart_encoder_urls_;
  int prev_num_components_;
  int accumulated_leaf_size_;
  GoogleString resolved_base_;
  const int url_overhead_;
  RewriteFilter* filter_;

  FRIEND_TEST(ResourceCombinerTest, TestRemove);
  FRIEND_TEST(ResourceCombinerTest, TestRemoveFrom3);
  FRIEND_TEST(ResourceCombinerTest, TestRebaseRemove);
  FRIEND_TEST(ResourceCombinerTest, TestRebaseRemoveAdd);

  DISALLOW_COPY_AND_ASSIGN(ResourceCombiner);
};

}  // namespace net_instaweb

#endif  // NET_INSTAWEB_REWRITER_PUBLIC_RESOURCE_COMBINER_H_
