/*
 * Copyright (C) 2011 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.
 */

#include "ring_buffer.h"

#include "integral_types.h"

namespace video_editing {

void RingBuffer::Init(int size, int num_channels, int num_readers) {
  size_ = size;
  num_channels_ = num_channels;
  num_readers_ = num_readers;
  temp_read_buffer_size_ = 1024;
  initialized_ = true;
  Reset();
}

RingBuffer::RingBuffer()
    : initialized_(false), samples_(NULL),
      num_readers_(0), temp_read_buffer_(NULL) {
}

RingBuffer::~RingBuffer() {
  delete[] samples_;
  delete[] temp_read_buffer_;
}

void RingBuffer::Reset() {
  delete[] samples_;
  samples_ = new float[size_ * num_channels_];
  memset(samples_, 0,
         size_ * num_channels_ * sizeof(samples_[0]));

  temp_read_buffer_size_ = 1024;
  delete[] temp_read_buffer_;
  temp_read_buffer_ = new float[temp_read_buffer_size_ * num_channels_];
  memset(temp_read_buffer_, 0,
         temp_read_buffer_size_ * num_channels_ * sizeof(samples_[0]));
  readers_.clear();
  for (int i = 0; i < num_readers_; ++i) {
    readers_.push_back(0LL);
  }
  head_logical_ = 0LL;
  head_ = 0;
}

int RingBuffer::available(int reader) const {
  return head_logical_ - readers_[reader];
}

int RingBuffer::overhead() const {
  int64 tail = GetTail();
  return tail + size_ - head_logical_;
}

int64 RingBuffer::GetTail() const {
  return *min_element(readers_.begin(), readers_.end());
}

int64 RingBuffer::Tell(int reader) const {
  return readers_[reader];
}

void RingBuffer::Seek(int reader, int64 position) {
  readers_[reader] = position;
}

void RingBuffer::Write(const float* samples, int num_frames) {
  if (!num_frames) {
    return;
  }
  if (head_ + num_frames <= size_) {
    memcpy(samples_ + head_ * num_channels_, samples,
           num_frames * num_channels_ * sizeof(samples[0]));
    head_ += num_frames;
  } else {
    int overhead = size_ - head_;
    memcpy(samples_ + head_ * num_channels_, samples,
           num_channels_ * overhead * sizeof(samples[0]));
    head_ = num_frames - overhead;
    memcpy(samples_, samples + overhead * num_channels_,
           num_channels_ * head_ * sizeof(samples[0]));
  }
  head_logical_ += num_frames;
}

void RingBuffer::Copy(int reader, float* destination, int num_frames) const {
  int pos = Tell(reader) % size_;
  if (pos + num_frames <= size_) {
    memcpy(destination, samples_ + pos * num_channels_,
           num_channels_ * num_frames * sizeof(destination[0]));
  } else {
    int wrapped = size_ - pos;
    memcpy(destination, samples_ + pos * num_channels_,
           num_channels_ * wrapped * sizeof(destination[0]));
    int remaining = num_frames - wrapped;
    memcpy(destination + wrapped * num_channels_, samples_,
           num_channels_ * remaining * sizeof(destination[0]));
  }
}

float* RingBuffer::GetPointer(int reader, int num_frames) {
  int pos = Tell(reader) % size_;
  if (pos + num_frames <= size_) {
    return samples_ + pos * num_channels_;
  } else {
    if (num_frames > temp_read_buffer_size_) {
      temp_read_buffer_size_ = num_frames;
      delete[] temp_read_buffer_;
      temp_read_buffer_ =
          new float[temp_read_buffer_size_ * num_channels_];  // NOLINT
    }
    Copy(reader, temp_read_buffer_, num_frames);
    return temp_read_buffer_;
  }
}

void RingBuffer::MergeBack(int reader, const float* source, int num_frames) {
  // If the source pointer is not the temporary buffer,
  // data updates were performed in place, so there is nothing to do.
  // Otherwise, copy samples from the temp buffer back to the ring buffer.
  if (source == temp_read_buffer_) {
    int pos = Tell(reader) % size_;
    if (pos + num_frames <= size_) {
      memcpy(samples_ + (pos * num_channels_), source,
             num_channels_ * num_frames * sizeof(source[0]));
    } else {
      int wrapped = size_ - pos;
      memcpy(samples_ + (pos * num_channels_), source,
             num_channels_ * wrapped * sizeof(source[0]));
      int remaining = num_frames - wrapped;
      memcpy(samples_, source + (wrapped * num_channels_),
             num_channels_ * remaining * sizeof(source[0]));
    }
  }
}

}  // namespace video_editing