// Copyright (c) 2010 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 "pdf/chunk_stream.h"

#define __STDC_LIMIT_MACROS
#ifdef _WIN32
#include <limits.h>
#else
#include <stdint.h>
#endif

#include <algorithm>

#include "base/basictypes.h"

namespace chrome_pdf {

ChunkStream::ChunkStream() {
}

ChunkStream::~ChunkStream() {
}

void ChunkStream::Clear() {
  chunks_.clear();
  data_.clear();
}

void ChunkStream::Preallocate(size_t stream_size) {
  data_.reserve(stream_size);
}

size_t ChunkStream::GetSize() {
  return data_.size();
}

bool ChunkStream::WriteData(size_t offset, void* buffer, size_t size) {
  if (SIZE_MAX - size < offset)
    return false;

  if (data_.size() < offset + size)
    data_.resize(offset + size);

  memcpy(&data_[offset], buffer, size);

  if (chunks_.empty()) {
    chunks_[offset] = size;
    return true;
  }

  std::map<size_t, size_t>::iterator start = chunks_.upper_bound(offset);
  if (start != chunks_.begin())
    --start;  // start now points to the key equal or lower than offset.
  if (start->first + start->second < offset)
    ++start;  // start element is entirely before current chunk, skip it.

  std::map<size_t, size_t>::iterator end = chunks_.upper_bound(offset + size);
  if (start == end) {  // No chunks to merge.
    chunks_[offset] = size;
    return true;
  }

  --end;

  size_t new_offset = std::min<size_t>(start->first, offset);
  size_t new_size =
      std::max<size_t>(end->first + end->second, offset + size) - new_offset;

  chunks_.erase(start, ++end);

  chunks_[new_offset] = new_size;

  return true;
}

bool ChunkStream::ReadData(size_t offset, size_t size, void* buffer) const {
  if (!IsRangeAvailable(offset, size))
    return false;

  memcpy(buffer, &data_[offset], size);
  return true;
}

bool ChunkStream::GetMissedRanges(
    size_t offset, size_t size,
    std::vector<std::pair<size_t, size_t> >* ranges) const {
  if (IsRangeAvailable(offset, size))
    return false;

  ranges->clear();
  if (chunks_.empty()) {
    ranges->push_back(std::pair<size_t, size_t>(offset, size));
    return true;
  }

  std::map<size_t, size_t>::const_iterator start = chunks_.upper_bound(offset);
  if (start != chunks_.begin())
    --start;  // start now points to the key equal or lower than offset.
  if (start->first + start->second < offset)
    ++start;  // start element is entirely before current chunk, skip it.

  std::map<size_t, size_t>::const_iterator end =
      chunks_.upper_bound(offset + size);
  if (start == end) {  // No data in the current range available.
    ranges->push_back(std::pair<size_t, size_t>(offset, size));
    return true;
  }

  size_t cur_offset = offset;
  std::map<size_t, size_t>::const_iterator it;
  for (it = start; it != end; ++it) {
    if (cur_offset < it->first) {
      size_t new_size = it->first - cur_offset;
      ranges->push_back(std::pair<size_t, size_t>(cur_offset, new_size));
      cur_offset = it->first + it->second;
    } else if (cur_offset < it->first + it->second) {
      cur_offset = it->first + it->second;
    }
  }

  // Add last chunk.
  if (cur_offset < offset + size)
    ranges->push_back(std::pair<size_t, size_t>(cur_offset,
        offset + size - cur_offset));

  return true;
}

bool ChunkStream::IsRangeAvailable(size_t offset, size_t size) const {
  if (chunks_.empty())
    return false;

  if (SIZE_MAX - size < offset)
    return false;

  std::map<size_t, size_t>::const_iterator it = chunks_.upper_bound(offset);
  if (it == chunks_.begin())
    return false;  // No chunks includes offset byte.

  --it;  // Now it starts equal or before offset.
  return (it->first + it->second) >= (offset + size);
}

size_t ChunkStream::GetFirstMissingByte() const {
  if (chunks_.empty())
    return 0;
  std::map<size_t, size_t>::const_iterator begin = chunks_.begin();
  return begin->first > 0 ? 0 : begin->second;
}

size_t ChunkStream::GetLastByteBefore(size_t offset) const {
  if (chunks_.empty())
    return 0;
  std::map<size_t, size_t>::const_iterator it = chunks_.upper_bound(offset);
  if (it == chunks_.begin())
    return 0;
  --it;
  return it->first + it->second;
}

size_t ChunkStream::GetFirstByteAfter(size_t offset) const {
  if (chunks_.empty())
    return 0;
  std::map<size_t, size_t>::const_iterator it = chunks_.upper_bound(offset);
  if (it == chunks_.end())
    return data_.size();
  return it->first;
}

}  // namespace chrome_pdf