#ifndef SECURITY_H
#define SECURITY_H

#include "binom/utils/byte_array.h"

#include <cstdint>
#include <openssl/ecdh.h>


/**
 * @brief Security methods
 */
class Security {
  static void handleErrors(void);
public:

  /**
   * @brief AES256 key and initialization vector
   */
  struct AES_t {
    uint8_t key[32] = {
      0,0,0,0,0,0,0,0,0,0,
      0,0,0,0,0,0,0,0,0,0,
      0,0,0,0,0,0,0,0,0,0,
      0,0
    };
    uint8_t init_vector[16] = {
      0,0,0,0,0,0,0,0,0,0,
      0,0,0,0,0,0
    };
    bool isEmpty() const {
        static const AES_t empty_aes;
        return !std::memcmp(this, &empty_aes, sizeof (AES_t));
    }
    inline operator bool() const {return !isEmpty();}
    void clear() {*this = AES_t();}
    AES_t() = default;
    AES_t(binom::ByteArray data) {
      *this = *reinterpret_cast<AES_t*>(data.begin());
    }
  };

  enum class HashMethod : uint8_t {
    sha1            = 0,
    sha224          = 1,
    sha256          = 2,
    sha384          = 3,
    sha512          = 4,
    sha512_224      = 5,
    sha512_256      = 6,
    sha3_224        = 7,
    sha3_256        = 8,
    sha3_384        = 9,
    sha3_512        = 10,
    shake128        = 11,
    shake256        = 12
  };

//  using enum HashMethod; // C++20


  // ECDH
  /**
   * @brief Generate ECDH key pair
   * @return ECDH key pair
   */
  static EVP_PKEY* genKey();

  /**
   * @brief Free key memory
   * @param key
   */
  static void freeKey(EVP_PKEY* key);

  /**
   * @brief Extract public key from key pair
   * @param key_pair
   * @return ByteArray with public key
   */
  static binom::ByteArray extractPublicKey(EVP_PKEY* key_pair);

  /**
   * @brief Extract private key from key pair
   * @param key_pair
   * @return ByteArray with private key
   */
  static binom::ByteArray extractPrivateKey(EVP_PKEY* key_pair);

  /**
   * @brief Conver ByteArrays to key pair
   * @param priv_key_raw
   * @param pub_key_raw
   * @return ECDH key pair
   */
  static EVP_PKEY* getKeyPair(binom::ByteArray priv_key_raw, binom::ByteArray pub_key_raw);

  /**
   * @brief Get AES256 key and initialization vector from ECDH keys
   * @param peer_key - public key from other side
   * @param key_pair - private key from this side
   * @return AES256 key and initialization vector
   */
  static AES_t getSecret(binom::ByteArray peer_key, EVP_PKEY* key_pair);

  // AES256
  /**
   * @brief Encrypt message
   * @param plain_text
   * @param aes_struct
   * @return Cyphertext
   */
  static binom::ByteArray encrypt(binom::ByteArray plain_text, AES_t aes_struct);
  /**
   * @brief Decrypt message
   * @param ciphertext
   * @param aes_struct
   * @return plain_text
   */
  static binom::ByteArray decrypt(binom::ByteArray ciphertext, AES_t aes_struct);

  /**
   * @brief Create AES256 key and initialization vector from data
   * @param data
   * @return
   */
  static AES_t createAES256Key(binom::ByteArray data);

  // Other
  /**
   * @brief Get hash of data
   * @param data
   * @param hash_method
   * @return
   */
  static binom::ByteArray getHashOf(binom::ByteArray data, HashMethod hash_method = HashMethod::sha3_512);

  /**
   * @brief Get hash with salt of data
   * @param data
   * @param salt
   * @param hash_method
   * @return
   */
  static binom::ByteArray getHashOf(binom::ByteArray data, binom::ByteArray salt, HashMethod hash_method = HashMethod::sha3_512);

  /**
   * @brief Encode data with base64
   * @param decoded
   * @return
   */
  static binom::ByteArray encodeBase64(binom::ByteArray decoded);

  /**
   * @brief Decode base64 data
   * @param encoded
   * @return
   */
  static binom::ByteArray decodeBase64(binom::ByteArray encoded);
};


#include <random>
#include <sstream>

class uuid {
    static inline std::random_device              rd;
    static inline std::mt19937                    gen{rd()};
    static inline std::uniform_int_distribution<> dis{0, 15};
    static inline std::uniform_int_distribution<> dis2{8, 11};
public:
    static std::string generateUUIDv4();
};

#endif // SECURITY_H
