// Copyright (c) 2008 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "base/data_pack.h"

#include <errno.h>

#include "base/file_util.h"
#include "base/logging.h"
#include "base/string_piece.h"

// For details of the file layout, see
// http://dev.chromium.org/developers/design-documents/linuxresourcesandlocalizedstrings

namespace {

// A word is four bytes.
static const size_t kWord = 4;

static const uint32 kFileFormatVersion = 1;
// Length of file header: version and entry count.
static const size_t kHeaderLength = 2 * sizeof(uint32);

#pragma pack(push,1)
struct DataPackEntry {
  uint32 resource_id;
  uint32 file_offset;
  uint32 length;

  static int CompareById(const void* void_key, const void* void_entry) {
    uint32 key = *reinterpret_cast<const uint32*>(void_key);
    const DataPackEntry* entry =
        reinterpret_cast<const DataPackEntry*>(void_entry);
    if (key < entry->resource_id) {
      return -1;
    } else if (key > entry->resource_id) {
      return 1;
    } else {
      return 0;
    }
  }
};
#pragma pack(pop)

COMPILE_ASSERT(sizeof(DataPackEntry) == 12, size_of_header_must_be_twelve);

}  // anonymous namespace

namespace base {

// In .cc for MemoryMappedFile dtor.
DataPack::DataPack() : resource_count_(0) {
}
DataPack::~DataPack() {
}

bool DataPack::Load(const FilePath& path) {
  mmap_.reset(new file_util::MemoryMappedFile);
  if (!mmap_->Initialize(path)) {
    DLOG(ERROR) << "Failed to mmap datapack";
    return false;
  }

  // Parse the header of the file.
  // First uint32: version; second: resource count.
  const uint32* ptr = reinterpret_cast<const uint32*>(mmap_->data());
  uint32 version = ptr[0];
  if (version != kFileFormatVersion) {
    LOG(ERROR) << "Bad data pack version: got " << version << ", expected "
               << kFileFormatVersion;
    mmap_.reset();
    return false;
  }
  resource_count_ = ptr[1];

  // Sanity check the file.
  // 1) Check we have enough entries.
  if (kHeaderLength + resource_count_ * sizeof(DataPackEntry) >
      mmap_->length()) {
    LOG(ERROR) << "Data pack file corruption: too short for number of "
                  "entries specified.";
    mmap_.reset();
    return false;
  }
  // 2) Verify the entries are within the appropriate bounds.
  for (size_t i = 0; i < resource_count_; ++i) {
    const DataPackEntry* entry = reinterpret_cast<const DataPackEntry*>(
        mmap_->data() + kHeaderLength + (i * sizeof(DataPackEntry)));
    if (entry->file_offset + entry->length > mmap_->length()) {
      LOG(ERROR) << "Entry #" << i << " in data pack points off end of file. "
                 << "Was the file corrupted?";
      mmap_.reset();
      return false;
    }
  }

  return true;
}

bool DataPack::GetStringPiece(uint32 resource_id, StringPiece* data) {
  // It won't be hard to make this endian-agnostic, but it's not worth
  // bothering to do right now.
#if defined(__BYTE_ORDER)
  // Linux check
  COMPILE_ASSERT(__BYTE_ORDER == __LITTLE_ENDIAN,
                 datapack_assumes_little_endian);
#elif defined(__BIG_ENDIAN__)
  // Mac check
  #error DataPack assumes little endian
#endif

  DataPackEntry* target = reinterpret_cast<DataPackEntry*>(
      bsearch(&resource_id, mmap_->data() + kHeaderLength, resource_count_,
              sizeof(DataPackEntry), DataPackEntry::CompareById));
  if (!target) {
    return false;
  }

  data->set(mmap_->data() + target->file_offset, target->length);
  return true;
}

RefCountedStaticMemory* DataPack::GetStaticMemory(uint32 resource_id) {
  base::StringPiece piece;
  if (!GetStringPiece(resource_id, &piece))
    return NULL;

  return new RefCountedStaticMemory(
      reinterpret_cast<const unsigned char*>(piece.data()), piece.length());
}

// static
bool DataPack::WritePack(const FilePath& path,
                         const std::map<uint32, StringPiece>& resources) {
  FILE* file = file_util::OpenFile(path, "wb");
  if (!file)
    return false;

  if (fwrite(&kFileFormatVersion, 1, kWord, file) != kWord) {
    LOG(ERROR) << "Failed to write file version";
    file_util::CloseFile(file);
    return false;
  }

  // Note: the python version of this function explicitly sorted keys, but
  // std::map is a sorted associative container, we shouldn't have to do that.
  uint32 entry_count = resources.size();
  if (fwrite(&entry_count, 1, kWord, file) != kWord) {
    LOG(ERROR) << "Failed to write entry count";
    file_util::CloseFile(file);
    return false;
  }

  // Each entry is 3 uint32s.
  uint32 index_length = entry_count * 3 * kWord;
  uint32 data_offset = kHeaderLength + index_length;
  for (std::map<uint32, StringPiece>::const_iterator it = resources.begin();
       it != resources.end(); ++it) {
    if (fwrite(&it->first, 1, kWord, file) != kWord) {
      LOG(ERROR) << "Failed to write id for " << it->first;
      file_util::CloseFile(file);
      return false;
    }

    if (fwrite(&data_offset, 1, kWord, file) != kWord) {
      LOG(ERROR) << "Failed to write offset for " << it->first;
      file_util::CloseFile(file);
      return false;
    }

    uint32 len = it->second.length();
    if (fwrite(&len, 1, kWord, file) != kWord) {
      LOG(ERROR) << "Failed to write length for " << it->first;
      file_util::CloseFile(file);
      return false;
    }

    data_offset += len;
  }

  for (std::map<uint32, StringPiece>::const_iterator it = resources.begin();
       it != resources.end(); ++it) {
    if (fwrite(it->second.data(), it->second.length(), 1, file) != 1) {
      LOG(ERROR) << "Failed to write data for " << it->first;
      file_util::CloseFile(file);
      return false;
    }
  }

  file_util::CloseFile(file);

  return true;
}

}  // namespace base