/*
    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 "td/utils/base64.h"
#include "td/utils/common.h"
#include "td/utils/Slice.h"
#include "td/utils/Status.h"
#include 
#include 
namespace td {
template 
static const char *get_characters() {
  return is_url ? "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"
                : "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
}
template 
static const unsigned char *get_character_table() {
  static unsigned char char_to_value[256];
  static bool is_inited = [] {
    auto characters = get_characters();
    std::fill(std::begin(char_to_value), std::end(char_to_value), static_cast(64));
    for (unsigned char i = 0; i < 64; i++) {
      char_to_value[static_cast(characters[i])] = i;
    }
    return true;
  }();
  CHECK(is_inited);
  return char_to_value;
}
template 
string base64_encode_impl(Slice input) {
  auto characters = get_characters();
  string base64;
  base64.reserve((input.size() + 2) / 3 * 4);
  for (size_t i = 0; i < input.size();) {
    size_t left = min(input.size() - i, static_cast(3));
    int c = input.ubegin()[i++] << 16;
    base64 += characters[c >> 18];
    if (left != 1) {
      c |= input.ubegin()[i++] << 8;
    }
    base64 += characters[(c >> 12) & 63];
    if (left == 3) {
      c |= input.ubegin()[i++];
    }
    if (left != 1) {
      base64 += characters[(c >> 6) & 63];
    } else if (!is_url) {
      base64 += '=';
    }
    if (left == 3) {
      base64 += characters[c & 63];
    } else if (!is_url) {
      base64 += '=';
    }
  }
  return base64;
}
string base64_encode(Slice input) {
  return base64_encode_impl(input);
}
string base64url_encode(Slice input) {
  return base64_encode_impl(input);
}
template 
Result base64_drop_padding(Slice base64) {
  size_t padding_length = 0;
  while (!base64.empty() && base64.back() == '=') {
    base64.remove_suffix(1);
    padding_length++;
  }
  if (padding_length >= 3) {
    return Status::Error("Wrong string padding");
  }
  if ((!is_url || padding_length > 0) && ((base64.size() + padding_length) & 3) != 0) {
    return Status::Error("Wrong padding length");
  }
  if (is_url && (base64.size() & 3) == 1) {
    return Status::Error("Wrong string length");
  }
  return base64;
}
static Status do_base64_decode_impl(Slice base64, const unsigned char *table, char *ptr) {
  for (size_t i = 0; i < base64.size();) {
    size_t left = min(base64.size() - i, static_cast(4));
    int c = 0;
    for (size_t t = 0; t < left; t++) {
      auto value = table[base64.ubegin()[i++]];
      if (value == 64) {
        return Status::Error("Wrong character in the string");
      }
      c |= value << ((3 - t) * 6);
    }
    *ptr++ = static_cast(static_cast(c >> 16));  // implementation-defined
    if (left == 2) {
      if ((c & ((1 << 16) - 1)) != 0) {
        return Status::Error("Wrong padding in the string");
      }
    } else {
      *ptr++ = static_cast(static_cast(c >> 8));  // implementation-defined
      if (left == 3) {
        if ((c & ((1 << 8) - 1)) != 0) {
          return Status::Error("Wrong padding in the string");
        }
      } else {
        *ptr++ = static_cast(static_cast(c));  // implementation-defined
      }
    }
  }
  return Status::OK();
}
template 
static T create_empty(size_t size);
template <>
string create_empty(size_t size) {
  return string(size, '\0');
}
template <>
SecureString create_empty(size_t size) {
  return SecureString{size};
}
template 
static Result base64_decode_impl(Slice base64) {
  TRY_RESULT_ASSIGN(base64, base64_drop_padding(base64));
  T result = create_empty(base64.size() / 4 * 3 + ((base64.size() & 3) + 1) / 2);
  TRY_STATUS(do_base64_decode_impl(base64, get_character_table(), as_mutable_slice(result).begin()));
  return std::move(result);
}
Result base64_decode(Slice base64) {
  return base64_decode_impl(base64);
}
Result base64_decode_secure(Slice base64) {
  return base64_decode_impl(base64);
}
Result base64url_decode(Slice base64) {
  return base64_decode_impl(base64);
}
template 
static bool is_base64_impl(Slice input) {
  size_t padding_length = 0;
  while (!input.empty() && input.back() == '=') {
    input.remove_suffix(1);
    padding_length++;
  }
  if (padding_length >= 3) {
    return false;
  }
  if ((!is_url || padding_length > 0) && ((input.size() + padding_length) & 3) != 0) {
    return false;
  }
  if (is_url && (input.size() & 3) == 1) {
    return false;
  }
  auto table = get_character_table();
  for (auto c : input) {
    if (table[static_cast(c)] == 64) {
      return false;
    }
  }
  if ((input.size() & 3) == 2) {
    auto value = table[static_cast(input.back())];
    if ((value & 15) != 0) {
      return false;
    }
  }
  if ((input.size() & 3) == 3) {
    auto value = table[static_cast(input.back())];
    if ((value & 3) != 0) {
      return false;
    }
  }
  return true;
}
bool is_base64(Slice input) {
  return is_base64_impl(input);
}
bool is_base64url(Slice input) {
  return is_base64_impl(input);
}
template 
static bool is_base64_characters_impl(Slice input) {
  auto table = get_character_table();
  for (auto c : input) {
    if (table[static_cast(c)] == 64) {
      return false;
    }
  }
  return true;
}
bool is_base64_characters(Slice input) {
  return is_base64_characters_impl(input);
}
bool is_base64url_characters(Slice input) {
  return is_base64_characters_impl(input);
}
string base64_filter(Slice input) {
  auto table = get_character_table();
  string res;
  res.reserve(input.size());
  for (auto c : input) {
    if (table[static_cast(c)] != 64 || c == '=') {
      res += c;
    }
  }
  return res;
}
static const char *const symbols32_lc = "abcdefghijklmnopqrstuvwxyz234567";
static const char *const symbols32_uc = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
string base32_encode(Slice input, bool upper_case) {
  auto *symbols32 = (upper_case ? symbols32_uc : symbols32_lc);
  string base32;
  base32.reserve((input.size() * 8 + 4) / 5);
  uint32 c = 0;
  uint32 length = 0;
  for (size_t i = 0; i < input.size(); i++) {
    c = (c << 8) | input.ubegin()[i];
    length += 8;
    while (length >= 5) {
      length -= 5;
      base32.push_back(symbols32[(c >> length) & 31]);
    }
  }
  if (length != 0) {
    base32.push_back(symbols32[(c << (5 - length)) & 31]);
  }
  //TODO: optional padding
  return base32;
}
static unsigned char b32_char_to_value[256];
static void init_base32_table() {
  static bool is_inited = [] {
    std::fill(std::begin(b32_char_to_value), std::end(b32_char_to_value), static_cast(32));
    for (unsigned char i = 0; i < 32; i++) {
      b32_char_to_value[static_cast(symbols32_lc[i])] = i;
      b32_char_to_value[static_cast(symbols32_uc[i])] = i;
    }
    return true;
  }();
  CHECK(is_inited);
}
Result base32_decode(Slice base32) {
  init_base32_table();
  string res;
  res.reserve(base32.size() * 5 / 8);
  uint32 c = 0;
  uint32 length = 0;
  for (size_t i = 0; i < base32.size(); i++) {
    auto value = b32_char_to_value[base32.ubegin()[i]];
    if (value == 32) {
      return Status::Error("Wrong character in the string");
    }
    c = (c << 5) | value;
    length += 5;
    while (length >= 8) {
      length -= 8;
      res.push_back(td::uint8((c >> length) & 255));
    }
  }
  if ((c & ((1 << length) - 1)) != 0) {
    return Status::Error("Nonzero padding");
  }
  //TODO: check padding
  return res;
}
}  // namespace td