#include <iostream>
#include <fstream>
#include <cstring>
#include <openssl/sha.h>
#include <openssl/hmac.h>
#include <openssl/evp.h>
#include <openssl/bio.h>
#include <openssl/buffer.h>

#include "stlencoders/base64.hpp"
#include "boost/date_time/posix_time/posix_time_types.hpp"

template <typename Impl>
void run(Impl impl, const char* input, std::size_t in_size, char* out, std::size_t out_size)
{
  boost::posix_time::ptime start = boost::posix_time::microsec_clock::local_time();

  impl(input, in_size, out, out_size);

  boost::posix_time::ptime end = boost::posix_time::microsec_clock::local_time();

  unsigned long int duration = (end - start).total_microseconds();

  std::cout << "duration: " << duration / 1000 << " ms (" << in_size / (1024*1024*double(duration)/1000000) << " Mb/s)" << std::endl;
}

inline std::size_t openssl_base64(const char *input, std::size_t length, char* out, std::size_t out_size)
{
  BIO* bmem = BIO_new(BIO_s_mem());
  BIO* b64 = BIO_new(BIO_f_base64());

  BUF_MEM bptr;

  bptr.data = out;
  bptr.length = 0;
  bptr.max = out_size;

  BIO_set_mem_buf(bmem, &bptr, BIO_NOCLOSE);
  BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL);
  b64 = BIO_push(b64, bmem);

  BIO_write(b64, input, length);
  BIO_flush(b64);

  BIO_free_all(b64);

  // We're using our own bptr and it must not trigger a BUF_MEM_grow
  assert(bptr.max = out_size);

  return bptr.length;
}

std::size_t stlencoders_base64(const char* input, std::size_t in_size, char* out, std::size_t out_size)
{
  stlencoders::base64<char>::encode(input, input + in_size, out);
}

int main(int argc, const char* argv[])
{
  if (argc != 2)
    return 1;

  std::ifstream in(argv[1], std::ios::binary);

  if (not in.is_open())
    return 1;

  in.seekg (0, std::ios::end);
  const std::size_t in_size = in.tellg();
  in.seekg (0, std::ios::beg);

  char* input = new char[in_size];
  in.read(input, in_size);

  std::size_t out_size = in_size * 2; // Conservative guess
  char* out1 = new char[out_size];
  char* out2 = new char[out_size];
  std::memset(out1, 0, out_size);
  std::memset(out2, 0, out_size);

  run(openssl_base64, input, in_size, out1, out_size);
  run(stlencoders_base64, input, in_size, out2, out_size);

  std::cout << "memcmp(out1, out2): " << std::memcmp(out1, out2, out_size) << std::endl;

  return 0;
}
