/*
    This file is part of TON Blockchain Library.
    TON Blockchain Library is free software: you can redistribute it and/or modify
    it under the terms of the GNU Lesser General Public License as published by
    the Free Software Foundation, either version 2 of the License, or
    (at your option) any later version.
    TON Blockchain Library is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Lesser General Public License for more details.
    You should have received a copy of the GNU Lesser General Public License
    along with TON Blockchain Library.  If not, see .
    Copyright 2017-2020 Telegram Systems LLP
*/
#include "LibRaptorQ.h"
#include "RaptorQ/RaptorQ_v1_hdr.hpp"
namespace RaptorQ = RaptorQ__v1;
namespace td {
namespace fec {
class SlowRaptorQEncoder::Impl {
 public:
  Impl(BufferSlice data, size_t max_symbol_size)
      : encoder_(create_encoder(data.size(), max_symbol_size)), data_(std::move(data)), symbol_size_(max_symbol_size) {
    encoder_->set_data(data_.as_slice().ubegin(), data_.as_slice().uend());
  }
  Symbol gen_symbol(uint32 id) {
    BufferSlice symbol_data(symbol_size_);
    auto *begin = symbol_data.as_slice().ubegin();
    auto *end = symbol_data.as_slice().uend();
    encoder_->encode(begin, end, id);
    return {id, symbol_data.from_slice(Slice(symbol_data.as_slice().ubegin(), begin))};
  }
  Parameters get_parameters() const {
    return Parameters{encoder_->symbols(), encoder_->symbol_size(), data_.size()};
  }
  Info get_info() const {
    return {1 << 24, computed_ ? encoder_->max_repair() : encoder_->symbols()};
  }
  void prepare_more_symbols() {
    encoder_->compute_sync();
    computed_ = true;
  }
 private:
  using Encoder = RaptorQ::Encoder;
  std::unique_ptr encoder_;
  BufferSlice data_;
  size_t symbol_size_;
  bool computed_{false};
  static std::unique_ptr create_encoder(size_t data_size, size_t symbol_size) {
    auto min_symbols = (data_size + symbol_size - 1) / symbol_size;
    RaptorQ::Block_Size block = RaptorQ::Block_Size::Block_10;
    for (auto blk : *RaptorQ::blocks) {
      // RaptorQ::blocks is a pointer to an array, just scan it to find your
      // block.
      if (static_cast(blk) >= min_symbols) {
        block = blk;
        break;
      }
    }
    CHECK(static_cast(block) >= min_symbols);
    return std::make_unique(block, symbol_size);
  }
};
class SlowRaptorQDecoder::Impl {
 public:
  using Decoder = RaptorQ::Decoder;
  Impl(SlowRaptorQEncoder::Parameters parameters)
      : decoder_(RaptorQ::Block_Size(parameters.symbols_count), parameters.symbol_size, Decoder::Report::COMPLETE)
      , data_size_(parameters.data_size) {
  }
  bool may_try_decode() const {
    return decoder_.can_decode();
  }
  Result try_decode(bool need_encoder) {
    decoder_.decode_once();
    if (!decoder_.ready()) {
      return Status::Error("Not ready");
    }
    BufferSlice data(data_size_);
    auto begin = data.as_slice().ubegin();
    decoder_.decode_bytes(begin, data.as_slice().uend(), 0, 0);
    std::unique_ptr encoder;
    if (need_encoder) {
      encoder = SlowRaptorQEncoder::create(data.copy(), decoder_.symbol_size());
    }
    return DataWithEncoder{std::move(data), std::move(encoder)};
  }
  void add_symbol(Symbol symbol) {
    auto begin = symbol.data.as_slice().ubegin();
    decoder_.add_symbol(begin, symbol.data.as_slice().uend(), symbol.id);
  }
 private:
  Decoder decoder_;
  size_t data_size_;
};
SlowRaptorQEncoder::~SlowRaptorQEncoder() = default;
std::unique_ptr SlowRaptorQEncoder::create(BufferSlice data, size_t max_symbol_size) {
  return std::make_unique(std::move(data), max_symbol_size);
}
SlowRaptorQEncoder::SlowRaptorQEncoder(BufferSlice data, size_t max_symbol_size) {
  impl_ = std::make_unique(std::move(data), max_symbol_size);
}
Symbol SlowRaptorQEncoder::gen_symbol(uint32 id) {
  return impl_->gen_symbol(id);
}
SlowRaptorQEncoder::Parameters SlowRaptorQEncoder::get_parameters() const {
  return impl_->get_parameters();
}
Encoder::Info SlowRaptorQEncoder::get_info() const {
  return impl_->get_info();
}
void SlowRaptorQEncoder::prepare_more_symbols() {
  impl_->prepare_more_symbols();
}
SlowRaptorQDecoder::~SlowRaptorQDecoder() = default;
std::unique_ptr SlowRaptorQDecoder::create(SlowRaptorQEncoder::Parameters parameters) {
  return std::make_unique(std::move(parameters));
}
bool SlowRaptorQDecoder::may_try_decode() const {
  return impl_->may_try_decode();
}
Result SlowRaptorQDecoder::try_decode(bool need_encoder) {
  return impl_->try_decode(need_encoder);
}
void SlowRaptorQDecoder::add_symbol(Symbol symbol) {
  impl_->add_symbol(std::move(symbol));
}
SlowRaptorQDecoder::SlowRaptorQDecoder(SlowRaptorQEncoder::Parameters parameters) {
  impl_ = std::make_unique(std::move(parameters));
}
}  // namespace fec
}  // namespace td