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

//
// Implements a ref-counted string class, with full sharing.  This
// class does *not* implement copy-on-write semantics, however, it
// does support a unique() method for determining, prior to writing,
// whether other references exist.  Thus it is feasible to implement
// copy-on-write as a layer over this class.

#ifndef PAGESPEED_KERNEL_BASE_SHARED_STRING_H_
#define PAGESPEED_KERNEL_BASE_SHARED_STRING_H_

#include <cstddef>                     // for size_t

#include "base/logging.h"
#include "pagespeed/kernel/base/ref_counted_ptr.h"
#include "pagespeed/kernel/base/string.h"
#include "pagespeed/kernel/base/string_util.h"

namespace net_instaweb {

// Reference-counted string.  This class allows for shared underlying
// storage with other SharedString instances, but for trimming a
// SharedString instance's view of it via RemoveSuffix() and RemovePrefix().
class SharedString {
 public:
  SharedString();

  explicit SharedString(const StringPiece& str);

  // When constructing with a GoogleString, going through the StringPiece
  // ctor above causes an extra copy compared with string implementations that
  // use copy-on-write.  So we make an explicit GoogleString constructor.
  explicit SharedString(const GoogleString& str);

  // Given the two constructors above, it is ambiguous which one gets
  // called when passed a string-literal, so making an explicit const char*
  // constructor eliminates the ambiguity.  This is likely beneficial mostly
  // for tests.
  explicit SharedString(const char* str);

  // Storage-sharing occurs as a result of the copy-constructor and
  // assignment operator.
  SharedString(const SharedString& src);
  SharedString& operator=(const SharedString& src);

  // Returns the value as a StringPiece, taking into account any calls
  // to RemovePrefix, RemoveSuffix, and any string-mutations due to
  // Append or WriteAt on this or any other SharedStrings sharing
  // storage due to the assignment operator or operator=.
  StringPiece Value() const;

  // Resets SharedString to be a copy of str, erasing any previous
  // prefix/suffix.  Calling this function detaches any connected
  // SharedStrings.
  //
  // It is valid to assign from a value inside the SharedString.  In
  // other words, shared_string.Assign(shared_string.Value().substr(...))
  // will work.
  void Assign(StringPiece str) { Assign(str.data(), str.size()); }
  void Assign(const char* data, int size);

  // Appends a new string to the underlying storage.  Other SharedStrings will
  // not be affected by this mutation.
  //
  // This function tries to avoid detaching from other SharedStrings, and only
  // needs to do so if this has been truncated.
  //
  // Unlike Assign, it is invalid to append characters managed by this
  // SharedString.  In other words, shared_string.Append(shared_string.Value())
  // will fail.
  //
  // Note: Append() is not thread-safe.  Concurrent accesses to any
  // SharedStrings with the same storage will fail.
  void Append(StringPiece str) { Append(str.data(), str.size()); }
  void Append(const char* data, size_t size);

  // Makes the string representation at least 'new_size' large,
  // without specifying how new bytes should be filled in.  Typically
  // this will be followed by a call to WriteAt().
  //
  // This function does *not* detach other SharedStrings -- the
  // underlying storage will still be shared.  Consequently this
  // function does not shrink strings, as that could invalidate
  // trimmed SharedStrings sharing the storage.
  //
  // If this method is called on a truncated SharedString, then it will be
  // detached prior to extending it.
  void Extend(int new_size);

  // Swaps storage with the the passed-in string, detaching from any other
  // previously-linked SharedStrings.
  void SwapWithString(GoogleString* str);

  // Clears the contents of the string, and erases any removed prefix
  // or suffix, detaching from any other previously-linked SharedStrings.
  void DetachAndClear();

  // Removes the first n characters from the string.  Other linked
  // SharedStrings remain linked, but are unaffected by this removal
  // because each has its own skip_ and size_.
  void RemovePrefix(int n);

  // Removes the last n characters from the string.  Other linked
  // SharedStrings remain linked, but are unaffected by this removal
  // because each has its own skip_ and size_.
  void RemoveSuffix(int n);

  // Computes the size, taking into account any removed prefix or suffix.
  int size() const { return size_; }
  bool empty() const { return size_ == 0; }
  const char* data() const { return ref_string_->data() + skip_; }

  // WriteAt allows mutation of the underlying string data.  The
  // string must already be sized as needed via previous Append() or
  // Extend() calls.  Mutations done via this method will affect all
  // references to the underlying storage.
  void WriteAt(int dest_offset, const char* source, int count);

  // Disassociates this SharedString with any others that have linked
  // the same storage.  Retains the same string value.
  void DetachRetainingContent() {
    if (!unique()) {
      *this = SharedString(Value());
      DCHECK(unique());
    }
  }

  // Determines whether this SharedString shares storage from other
  // SharedStrings.
  bool unique() const { return ref_string_.unique(); }

  // Determines whether RemovePrefix or RemoveSuffix has every been called
  // on this SharedString.  Note that other SharedStrings sharing the
  // same storage as this may be trimmed differently.
  bool trimmed() const {
    return size_ != static_cast<int>(ref_string_->size());
  }

  // Returns back a GoogleString* representation for the contained value.
  //
  // This is makes sense to call only if the string is not trimmed.  If
  // RemovePrefix or RemoveSuffix has been called on this SharedString, the
  // returned string may have extra characters in it.
  //
  // Note: we suggest against using this routine.  It is better to consume
  // the data via the StringPiece returned from Value().
  //
  // This routine is, however, useful to call from tests to determine
  // storage uniqueness.
  const GoogleString* StringValue() const {
    return ref_string_.get();
  }

  // Determines whether this and that share the same storage.
  bool SharesStorage(const SharedString& that) const {
    return ref_string_.get() == that.ref_string_.get();
  }

 private:
  void UniquifyIfTruncated();
  char* mutable_data() { return &(*ref_string_.get())[0] + skip_; }
  void ClearIfShared() {
    if (!unique()) {
      DetachAndClear();
    }
  }

  RefCountedObj<GoogleString> ref_string_;

  int skip_;  // Number of bytes to skip at the beginning of the string.
  int size_;  // Number of bytes visible in the current view.
};

}  // namespace net_instaweb

#endif  // PAGESPEED_KERNEL_BASE_SHARED_STRING_H_
