/*
    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
*/
#pragma once
#include "td/utils/common.h"
#include "td/utils/logging.h"
#include "td/utils/Slice.h"
#include "td/utils/StorerBase.h"
#include "td/utils/UInt.h"
//FIXME
#include "td/utils/SharedSlice.h"
#include "crypto/common/bitstring.h"
#include 
namespace td {
class TlStorerUnsafe {
  unsigned char *buf_;
 public:
  explicit TlStorerUnsafe(unsigned char *buf) : buf_(buf) {
  }
  TlStorerUnsafe(const TlStorerUnsafe &other) = delete;
  TlStorerUnsafe &operator=(const TlStorerUnsafe &other) = delete;
  template 
  void store_binary(const T &x) {
    std::memcpy(buf_, &x, sizeof(T));
    buf_ += sizeof(T);
  }
  void store_int(int32 x) {
    store_binary(x);
  }
  void store_long(int64 x) {
    store_binary(x);
  }
  void store_slice(Slice slice) {
    std::memcpy(buf_, slice.begin(), slice.size());
    buf_ += slice.size();
  }
  void store_storer(const Storer &storer) {
    size_t size = storer.store(buf_);
    buf_ += size;
  }
  template 
  void store_string(const T &str) {
    size_t len = str.size();
    if (len < 254) {
      *buf_++ = static_cast(len);
      len++;
    } else if (len < (1 << 24)) {
      *buf_++ = static_cast(254);
      *buf_++ = static_cast(len & 255);
      *buf_++ = static_cast((len >> 8) & 255);
      *buf_++ = static_cast(len >> 16);
    } else if (static_cast(len) < (static_cast(1) << 32)) {
      *buf_++ = static_cast(255);
      *buf_++ = static_cast(len & 255);
      *buf_++ = static_cast((len >> 8) & 255);
      *buf_++ = static_cast((len >> 16) & 255);
      *buf_++ = static_cast((len >> 24) & 255);
      *buf_++ = static_cast(0);
      *buf_++ = static_cast(0);
      *buf_++ = static_cast(0);
    } else {
      LOG(FATAL) << "String size " << len << " is too big to be stored";
    }
    std::memcpy(buf_, str.data(), str.size());
    buf_ += str.size();
    switch (len & 3) {
      case 1:
        *buf_++ = 0;
        // fallthrough
      case 2:
        *buf_++ = 0;
        // fallthrough
      case 3:
        *buf_++ = 0;
    }
  }
  unsigned char *get_buf() const {
    return buf_;
  }
};
class TlStorerCalcLength {
  size_t length = 0;
 public:
  TlStorerCalcLength() = default;
  TlStorerCalcLength(const TlStorerCalcLength &other) = delete;
  TlStorerCalcLength &operator=(const TlStorerCalcLength &other) = delete;
  template 
  void store_binary(const T &x) {
    length += sizeof(T);
  }
  void store_int(int32 x) {
    store_binary(x);
  }
  void store_long(int64 x) {
    store_binary(x);
  }
  void store_slice(Slice slice) {
    length += slice.size();
  }
  void store_storer(const Storer &storer) {
    length += storer.size();
  }
  template 
  void store_string(const T &str) {
    size_t add = str.size();
    if (add < 254) {
      add += 1;
    } else if (add < (1 << 24)) {
      add += 4;
    } else {
      add += 8;
    }
    add = (add + 3) & -4;
    length += add;
  }
  size_t get_length() const {
    return length;
  }
};
class TlStorerToString {
  std::string result;
  size_t shift = 0;
  void store_field_begin(const char *name) {
    result.append(shift, ' ');
    if (name && name[0]) {
      result += name;
      result += " = ";
    }
  }
  void store_field_end() {
    result += '\n';
  }
  void store_long(int64 value) {
    result += (PSLICE() << value).c_str();
  }
  void store_binary(Slice data) {
    static const char *hex = "0123456789ABCDEF";
    result.append("{ ", 2);
    for (auto c : data) {
      unsigned char byte = c;
      result += hex[byte >> 4];
      result += hex[byte & 15];
      result += ' ';
    }
    result += '}';
  }
 public:
  TlStorerToString() = default;
  TlStorerToString(const TlStorerToString &other) = delete;
  TlStorerToString &operator=(const TlStorerToString &other) = delete;
  void store_field(const char *name, bool value) {
    store_field_begin(name);
    result += (value ? "true" : "false");
    store_field_end();
  }
  void store_field(const char *name, int32 value) {
    store_field(name, static_cast(value));
  }
  void store_field(const char *name, int64 value) {
    store_field_begin(name);
    store_long(value);
    store_field_end();
  }
  void store_field(const char *name, double value) {
    store_field_begin(name);
    result += (PSLICE() << value).c_str();
    store_field_end();
  }
  void store_field(const char *name, const char *value) {
    store_field_begin(name);
    result += value;
    store_field_end();
  }
  void store_field(const char *name, const string &value) {
    store_field_begin(name);
    result += '"';
    result += value;
    result += '"';
    store_field_end();
  }
  void store_field(const char *name, const SecureString &value) {
    store_field_begin(name);
    result.append("");
    store_field_end();
  }
  template 
  void store_field(const char *name, const T &value) {
    store_field_begin(name);
    result.append(value.data(), value.size());
    store_field_end();
  }
  void store_bytes_field(const char *name, const SecureString &value) {
    store_field_begin(name);
    result.append("");
    store_field_end();
  }
  template 
  void store_bytes_field(const char *name, const BytesT &value) {
    static const char *hex = "0123456789ABCDEF";
    store_field_begin(name);
    result.append("bytes [");
    store_long(static_cast(value.size()));
    result.append("] { ");
    size_t len = min(static_cast(64), value.size());
    for (size_t i = 0; i < len; i++) {
      int b = value[static_cast(i)] & 0xff;
      result += hex[b >> 4];
      result += hex[b & 15];
      result += ' ';
    }
    if (len < value.size()) {
      result.append("...");
    }
    result += '}';
    store_field_end();
  }
  //FIXME
  void store_field(const char *name, const Bits128 &value) {
    store_field_begin(name);
    store_binary(as_slice(value));
    store_field_end();
  }
  void store_field(const char *name, const Bits256 &value) {
    store_field_begin(name);
    store_binary(as_slice(value));
    store_field_end();
  }
  void store_field(const char *name, const UInt128 &value) {
    store_field_begin(name);
    store_binary(as_slice(value));
    store_field_end();
  }
  void store_field(const char *name, const UInt256 &value) {
    store_field_begin(name);
    store_binary(as_slice(value));
    store_field_end();
  }
  void store_class_begin(const char *field_name, const char *class_name) {
    store_field_begin(field_name);
    result += class_name;
    result += " {\n";
    shift += 2;
  }
  void store_class_end() {
    CHECK(shift >= 2);
    shift -= 2;
    result.append(shift, ' ');
    result += "}\n";
  }
  std::string move_as_str() {
    return std::move(result);
  }
};
template 
size_t tl_calc_length(const T &data) {
  TlStorerCalcLength storer_calc_length;
  data.store(storer_calc_length);
  return storer_calc_length.get_length();
}
template 
size_t tl_store_unsafe(const T &data, unsigned char *dst) TD_WARN_UNUSED_RESULT;
template 
size_t tl_store_unsafe(const T &data, unsigned char *dst) {
  TlStorerUnsafe storer_unsafe(dst);
  data.store(storer_unsafe);
  return static_cast(storer_unsafe.get_buf() - dst);
}
}  // namespace td