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

#include "pagespeed/kernel/base/shared_string.h"

#include <algorithm>  // for std::min, std::max

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

namespace net_instaweb {

SharedString::SharedString() : skip_(0), size_(0) {
}

SharedString::SharedString(const StringPiece& str)
    : skip_(0),
      size_(str.size()) {
  GoogleString* storage = ref_string_.get();
  str.CopyToString(storage);
}

// 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.
SharedString::SharedString(const GoogleString& str)
    : ref_string_(str),
      skip_(0),
      size_(str.size()) {
}

// 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.
SharedString::SharedString(const char* str)
    : skip_(0) {
  GoogleString* storage = ref_string_.get();
  *storage = str;
  size_ = storage->size();
}

SharedString::SharedString(const SharedString& src)
    : ref_string_(src.ref_string_),
      skip_(src.skip_),
      size_(src.size_) {
}

SharedString& SharedString::operator=(const SharedString& src) {
  if (&src != this) {
    ref_string_ = src.ref_string_;
    skip_ = src.skip_;
    size_ = src.size_;
  }
  return *this;
}

StringPiece SharedString::Value() const {
  const GoogleString* storage = ref_string_.get();
  DCHECK_LE(size_ + skip_, static_cast<int>(storage->size()));
  return StringPiece(storage->data() + skip_, size_);
}

void SharedString::Assign(const char* data, int size) {
  // Note that 'str' might be a substring of the current storage, so
  // avoid bugs by copying to a temp and swapping.
  GoogleString temp(data, size);
  ClearIfShared();
  GoogleString* storage = ref_string_.get();
  temp.swap(*storage);
  size_ = storage->size();
}

void SharedString::UniquifyIfTruncated() {
  if (size_ != (static_cast<int>(ref_string_->size()) - skip_)) {
    if (unique()) {
      ref_string_->resize(size_ + skip_);
    } else {
      *this = SharedString(Value());
      DCHECK(unique());
    }
  }
}

void SharedString::Append(const char* new_data, size_t new_size) {
  DCHECK((new_data + new_size) <= data() ||
         (data() + size() < new_data))
      << "Append must be given non-overlapping strings";
  UniquifyIfTruncated();
  ref_string_->append(new_data, new_size);
  size_ += new_size;
}

void SharedString::Extend(int new_size) {
  if (size_ < new_size) {
    UniquifyIfTruncated();
    size_ = new_size;
    ref_string_.get()->resize(size_ + skip_);
  }
}

void SharedString::WriteAt(int dest_offset, const char* source, int count) {
  DCHECK_LT(dest_offset, size());
  DCHECK_LE(dest_offset + count, size());
  if (count > size() - dest_offset) {
    count = std::max(0, size() - dest_offset);
  }
  memcpy(mutable_data() + dest_offset, source, count);
}

void SharedString::SwapWithString(GoogleString* str) {
  ClearIfShared();
  GoogleString* storage = ref_string_.get();
  storage->swap(*str);
  skip_ = 0;
  size_ = storage->size();
}

void SharedString::DetachAndClear() {
  SharedString empty_string;
  *this = empty_string;  // Detaches other strings sharing this value.
}

void SharedString::RemovePrefix(int n) {
  DCHECK_LE(n, size_);
  if (n > size_) {
    n = size_;
  }
  skip_ += n;
  size_ -= n;
}

// Removes the last n characters from the string.  Other linked
// SharedStrings remain linked, but are unaffected by this removal
// because each has their own skip_ and size_.
void SharedString::RemoveSuffix(int n) {
  DCHECK_LE(n, size_);
  size_ -= std::min(n, size_);
}

}  // namespace net_instaweb
