/*
 * Copyright 2014 Facebook, Inc.
 *
 * 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.
 */
namespace folly {
class IOBuf;
}

namespace apache { namespace thrift { namespace frozen {

namespace detail {

template <class T>
struct BufferHelpers {
  typedef typename T::value_type Item;
  static size_t size(const T& src) { return src.size(); }
  static void copyTo(const T& src, folly::Range<Item*> dst) {
    std::copy(src.begin(), src.end(), reinterpret_cast<Item*>(dst.begin()));
  }
  static void thawTo(folly::Range<const Item*> src, T& dst) {
    dst.assign(src.begin(), src.end());
  }
};

template <>
struct BufferHelpers<std::unique_ptr<folly::IOBuf>> {
  typedef uint8_t Item;
  static size_t size(const std::unique_ptr<folly::IOBuf>& src);

  static void copyTo(const std::unique_ptr<folly::IOBuf>& src,
                     folly::MutableByteRange dst);
  static void thawTo(folly::ByteRange src, std::unique_ptr<folly::IOBuf>& dst);
};

/**
 * for contiguous, blittable ranges
 */
template <class T>
struct StringLayout : public LayoutBase {
  typedef LayoutBase Base;
  typedef BufferHelpers<T> Helper;
  typedef typename Helper::Item Item;
  Field<size_t> distanceField;
  Field<size_t> countField;

  StringLayout()
      : LayoutBase(typeid(T)),
        distanceField(1, "distance"),
        countField(2, "count") {}

  FieldPosition layout(LayoutRoot& root, const T& o, LayoutPosition self) {
    FieldPosition pos = startFieldPosition();
    size_t n = Helper::size(o);
    if (!n) {
      return pos;
    }
    size_t dist = root.layoutBytesDistance(self.start, n * sizeof(Item));
    pos = root.layoutField(self, pos, distanceField, dist);
    pos = root.layoutField(self, pos, countField, n);
    return pos;
  }

  void freeze(FreezeRoot& root, const T& o, FreezePosition self) const {
    size_t n = Helper::size(o);
    folly::MutableByteRange range;
    size_t dist;
    root.appendBytes(self.start, n * sizeof(Item), range, dist);
    root.freezeField(self, distanceField, dist);
    root.freezeField(self, countField, n);
    folly::Range<Item*> target(reinterpret_cast<Item*>(range.begin()),n);
    Helper::copyTo(o, target);
  }

  void thaw(ViewPosition self, T& out) const {
    Helper::thawTo(view(self), out);
  }

  typedef folly::Range<const Item*> View;

  View view(ViewPosition self) const {
    View range;
    size_t dist, n;
    thawField(self, countField, n);
    if (n) {
      thawField(self, distanceField, dist);
      const byte* read = self.start + dist;
      range.reset(reinterpret_cast<const Item*>(read), n);
    }
    return range;
  }

  void print(std::ostream& os, int level) const override {
    LayoutBase::print(os, level);
    os << "string of " << folly::demangle(type.name());
    distanceField.print(os, level + 1);
    countField.print(os, level + 1);
  }

  void clear() final {
    LayoutBase::clear();
    distanceField.clear();
    countField.clear();
  }

  FROZEN_SAVE_INLINE(
    FROZEN_SAVE_FIELD(distance)
    FROZEN_SAVE_FIELD(count))

  FROZEN_LOAD_INLINE(
    FROZEN_LOAD_FIELD(distance, 1)
    FROZEN_LOAD_FIELD(count, 2))

  static size_t hash(const View& v) {
    return folly::hash::fnv64_buf(v.begin(), sizeof(Item) * v.size());
  }
};

} // detail

template <class T>
struct Layout<T, typename std::enable_if<IsString<T>::value>::type>
    : detail::StringLayout<typename std::decay<T>::type> {};

}}}

THRIFT_DECLARE_TRAIT(IsString, std::string)
THRIFT_DECLARE_TRAIT(IsString, folly::fbstring)
THRIFT_DECLARE_TRAIT(IsString, std::unique_ptr<folly::IOBuf>)
