/*
 * Copyright (C) 2016 The Android Open Source Project
 *
 * 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.
 *
 * Header file of an in-memory representation of DEX files.
 */

#ifndef ART_DEXLAYOUT_DEX_WRITER_H_
#define ART_DEXLAYOUT_DEX_WRITER_H_

#include <functional>
#include <memory>  // For unique_ptr

#include "base/os.h"
#include "base/unix_file/fd_file.h"
#include "dex/compact_dex_level.h"
#include "dex_container.h"
#include "dex/dex_file.h"
#include "dex_ir.h"

#include <queue>

namespace art {

class DexLayout;
class DexLayoutHotnessInfo;

struct MapItem {
  // Not using DexFile::MapItemType since compact dex and standard dex file may have different
  // sections.
  MapItem() = default;
  MapItem(uint32_t type, uint32_t size, size_t offset)
      : type_(type), size_(size), offset_(offset) { }

  // Sort by decreasing order since the priority_queue puts largest elements first.
  bool operator>(const MapItem& other) const {
    return offset_ > other.offset_;
  }

  uint32_t type_ = 0u;
  uint32_t size_ = 0u;
  uint32_t offset_ = 0u;
};

class MapItemQueue : public
    std::priority_queue<MapItem, std::vector<MapItem>, std::greater<MapItem>> {
 public:
  void AddIfNotEmpty(const MapItem& item);
};

class DexWriter {
 public:
  static constexpr uint32_t kDataSectionAlignment = sizeof(uint32_t) * 2;
  static constexpr uint32_t kDexSectionWordAlignment = 4;

  // Stream that writes into a dex container section. Do not have two streams pointing to the same
  // backing storage as there may be invalidation of backing storage to resize the section.
  // Random access stream (consider refactoring).
  class Stream {
   public:
    explicit Stream(DexContainer::Section* section) : section_(section) {
      SyncWithSection();
    }

    const uint8_t* Begin() const {
      return data_;
    }

    // Functions are not virtual (yet) for speed.
    size_t Tell() const {
      return position_;
    }

    void Seek(size_t position) {
      position_ = position;
      EnsureStorage(0u);
    }

    // Does not allow overwriting for bug prevention purposes.
    ALWAYS_INLINE size_t Write(const void* buffer, size_t length) {
      EnsureStorage(length);
      for (size_t i = 0; i < length; ++i) {
        DCHECK_EQ(data_[position_ + i], 0u);
      }
      memcpy(&data_[position_], buffer, length);
      position_ += length;
      return length;
    }

    ALWAYS_INLINE size_t Overwrite(const void* buffer, size_t length) {
      EnsureStorage(length);
      memcpy(&data_[position_], buffer, length);
      position_ += length;
      return length;
    }

    ALWAYS_INLINE size_t Clear(size_t position, size_t length) {
      EnsureStorage(length);
      memset(&data_[position], 0, length);
      return length;
    }

    ALWAYS_INLINE size_t WriteSleb128(int32_t value) {
      EnsureStorage(8);
      uint8_t* ptr = &data_[position_];
      const size_t len = EncodeSignedLeb128(ptr, value) - ptr;
      position_ += len;
      return len;
    }

    ALWAYS_INLINE size_t WriteUleb128(uint32_t value) {
      EnsureStorage(8);
      uint8_t* ptr = &data_[position_];
      const size_t len = EncodeUnsignedLeb128(ptr, value) - ptr;
      position_ += len;
      return len;
    }

    ALWAYS_INLINE void AlignTo(const size_t alignment) {
      position_ = RoundUp(position_, alignment);
      EnsureStorage(0u);
    }

    ALWAYS_INLINE void Skip(const size_t count) {
      position_ += count;
      EnsureStorage(0u);
    }

    class ScopedSeek {
     public:
      ScopedSeek(Stream* stream, uint32_t offset) : stream_(stream), offset_(stream->Tell()) {
        stream->Seek(offset);
      }

      ~ScopedSeek() {
        stream_->Seek(offset_);
      }

     private:
      Stream* const stream_;
      const uint32_t offset_;
    };

   private:
    ALWAYS_INLINE void EnsureStorage(size_t length) {
      size_t end = position_ + length;
      while (UNLIKELY(end > data_size_)) {
        section_->Resize(data_size_ * 3 / 2 + 1);
        SyncWithSection();
      }
    }

    void SyncWithSection() {
      data_ = section_->Begin();
      data_size_ = section_->Size();
    }

    // Current position of the stream.
    size_t position_ = 0u;
    DexContainer::Section* const section_ = nullptr;
    // Cached Begin() from the container to provide faster accesses.
    uint8_t* data_ = nullptr;
    // Cached Size from the container to provide faster accesses.
    size_t data_size_ = 0u;
  };

  static inline constexpr uint32_t SectionAlignment(DexFile::MapItemType type) {
    switch (type) {
      case DexFile::kDexTypeClassDataItem:
      case DexFile::kDexTypeStringDataItem:
      case DexFile::kDexTypeDebugInfoItem:
      case DexFile::kDexTypeAnnotationItem:
      case DexFile::kDexTypeEncodedArrayItem:
        return alignof(uint8_t);

      default:
        // All other sections are kDexAlignedSection.
        return DexWriter::kDexSectionWordAlignment;
    }
  }

  class Container : public DexContainer {
   public:
    Section* GetMainSection() override {
      return &main_section_;
    }

    Section* GetDataSection() override {
      return &data_section_;
    }

    bool IsCompactDexContainer() const override {
      return false;
    }

   private:
    VectorSection main_section_;
    VectorSection data_section_;

    friend class CompactDexWriter;
  };

  DexWriter(DexLayout* dex_layout, bool compute_offsets);

  static bool Output(DexLayout* dex_layout,
                     std::unique_ptr<DexContainer>* container,
                     bool compute_offsets,
                     std::string* error_msg) WARN_UNUSED;

  virtual ~DexWriter() {}

 protected:
  virtual bool Write(DexContainer* output, std::string* error_msg);
  virtual std::unique_ptr<DexContainer> CreateDexContainer() const;

  void WriteEncodedValue(Stream* stream, dex_ir::EncodedValue* encoded_value);
  void WriteEncodedValueHeader(Stream* stream, int8_t value_type, size_t value_arg);
  void WriteEncodedArray(Stream* stream, dex_ir::EncodedValueVector* values);
  void WriteEncodedAnnotation(Stream* stream, dex_ir::EncodedAnnotation* annotation);
  void WriteEncodedFields(Stream* stream, dex_ir::FieldItemVector* fields);
  void WriteEncodedMethods(Stream* stream, dex_ir::MethodItemVector* methods);

  // Header and id section
  virtual void WriteHeader(Stream* stream);
  virtual size_t GetHeaderSize() const;
  // reserve_only means don't write, only reserve space. This is required since the string data
  // offsets must be assigned.
  void WriteStringIds(Stream* stream, bool reserve_only);
  void WriteTypeIds(Stream* stream);
  void WriteProtoIds(Stream* stream, bool reserve_only);
  void WriteFieldIds(Stream* stream);
  void WriteMethodIds(Stream* stream);
  void WriteClassDefs(Stream* stream, bool reserve_only);
  void WriteCallSiteIds(Stream* stream, bool reserve_only);

  void WriteEncodedArrays(Stream* stream);
  void WriteAnnotations(Stream* stream);
  void WriteAnnotationSets(Stream* stream);
  void WriteAnnotationSetRefs(Stream* stream);
  void WriteAnnotationsDirectories(Stream* stream);

  // Data section.
  void WriteDebugInfoItems(Stream* stream);
  void WriteCodeItems(Stream* stream, bool reserve_only);
  void WriteTypeLists(Stream* stream);
  void WriteStringDatas(Stream* stream);
  void WriteClassDatas(Stream* stream);
  void WriteMethodHandles(Stream* stream);
  void WriteHiddenapiClassData(Stream* stream);
  void WriteMapItems(Stream* stream, MapItemQueue* queue);
  void GenerateAndWriteMapItems(Stream* stream);

  virtual void WriteCodeItemPostInstructionData(Stream* stream,
                                                    dex_ir::CodeItem* item,
                                                    bool reserve_only);
  virtual void WriteCodeItem(Stream* stream, dex_ir::CodeItem* item, bool reserve_only);
  virtual void WriteDebugInfoItem(Stream* stream, dex_ir::DebugInfoItem* debug_info);
  virtual void WriteStringData(Stream* stream, dex_ir::StringData* string_data);

  // Process an offset, if compute_offset is set, write into the dex ir item, otherwise read the
  // existing offset and use that for writing.
  void ProcessOffset(Stream* stream, dex_ir::Item* item);
  void ProcessOffset(Stream* stream, dex_ir::CollectionBase* item);

  dex_ir::Header* const header_;
  DexLayout* const dex_layout_;
  bool compute_offsets_;

 private:
  DISALLOW_COPY_AND_ASSIGN(DexWriter);
};

}  // namespace art

#endif  // ART_DEXLAYOUT_DEX_WRITER_H_
