#define BOOST_TEST_MODULE BASE64 Test
#include <boost/config/warning_disable.hpp>
#include <boost/test/unit_test.hpp>
#include <boost/network/utils/base64/encode.hpp>
#include <boost/network/utils/base64/encode-io.hpp>
#include <algorithm>
#include <iterator>
#include <string>
#include <vector>
#include <sstream>
#include <array>

using namespace boost::network::utils;

// proves that all public functions are compilable; the result check
// is very minimum here, so that the test doesn't look so stupid ;-)
BOOST_AUTO_TEST_CASE(interface_test) {
  std::string result;
  base64::state<char> state;

  // check string literal
  BOOST_CHECK_EQUAL(base64::encode<char>("abc"), "YWJj");

  base64::encode("abc", std::back_inserter(result));
  BOOST_CHECK_EQUAL(result, "YWJj");

  result.clear();
  base64::encode("abc", std::back_inserter(result), state);
  BOOST_CHECK_EQUAL(result, "YWJj");

  // check std::string
  std::string input("abc");

  BOOST_CHECK_EQUAL(base64::encode<char>(input), "YWJj");

  result.clear();
  base64::encode(input, std::back_inserter(result));
  BOOST_CHECK_EQUAL(result, "YWJj");

  result.clear();
  base64::encode(input.begin(), input.end(), std::back_inserter(result));
  BOOST_CHECK_EQUAL(result, "YWJj");

  result.clear();
  base64::encode(input, std::back_inserter(result), state);
  BOOST_CHECK_EQUAL(result, "YWJj");

  result.clear();
  base64::encode(input.begin(), input.end(), std::back_inserter(result), state);
  BOOST_CHECK_EQUAL(result, "YWJj");

  // check array of chars
  char char_array[] = {'a', 'b', 'c', 0};

  BOOST_CHECK_EQUAL(base64::encode<char>(char_array), "YWJj");

  // check boost::array of chars
  std::array<char, 3> char_boost_array = {{'a', 'b', 'c'}};

  BOOST_CHECK_EQUAL(base64::encode<char>(char_boost_array), "YWJj");

  // check std::vector of chars
  std::vector<char> char_vector(char_array, char_array + 3);

  BOOST_CHECK_EQUAL(base64::encode<char>(char_vector), "YWJj");

  // check array of ints
  int int_array[] = {'a', 'b', 'c'};

  BOOST_CHECK_EQUAL(base64::encode<char>(int_array), "YWJj");

  // check boost::array of ints
  boost::array<int, 3> int_boost_array = {{'a', 'b', 'c'}};

  BOOST_CHECK_EQUAL(base64::encode<char>(int_boost_array), "YWJj");

  // check std::vector of ints
  std::vector<int> int_vector(int_array, int_array + 3);

  BOOST_CHECK_EQUAL(base64::encode<char>(int_vector), "YWJj");

  // check that base64::encode_rest is compilable and callable
  result.clear();
  base64::encode_rest(std::back_inserter(result), state);
  BOOST_CHECK_EQUAL(result, "");

  // check that the iostream interface is compilable and callable
  std::ostringstream output;
  output << base64::io::encode("abc")
         << base64::io::encode(input.begin(), input.end())
         << base64::io::encode(int_array) << base64::io::encode(int_boost_array)
         << base64::io::encode(char_array)
         << base64::io::encode(char_boost_array)
         << base64::io::encode(char_vector) << base64::io::encode_rest<char>;
  BOOST_CHECK_EQUAL(output.str(), "YWJjYWJjYWJjYWJjYWJjYWJjYWJj");
}

// checks that functions encoding a single chunk append the correct padding
// if the input byte count is not divisible by 3
BOOST_AUTO_TEST_CASE(padding_test) {
  BOOST_CHECK_EQUAL(base64::encode<char>(""), "");
  BOOST_CHECK_EQUAL(base64::encode<char>("a"), "YQ==");
  BOOST_CHECK_EQUAL(base64::encode<char>("aa"), "YWE=");
  BOOST_CHECK_EQUAL(base64::encode<char>("aaa"), "YWFh");
}

// check that functions using encoding state interrupt and resume encoding
// correcly if the byte count of the partial input is not divisible by 3
BOOST_AUTO_TEST_CASE(state_test) {
  base64::state<char> state;
  std::string result;

  // check encoding empty input; including the state value
  base64::encode("", std::back_inserter(result), state);
  BOOST_CHECK_EQUAL(result, "");
  BOOST_CHECK(state.empty());
  result.clear();
  state.clear();

  // check one third of quantum which needs two character padding;
  // including how the state develops when encoded by single character
  base64::encode("a", std::back_inserter(result), state);
  BOOST_CHECK_EQUAL(result, "Y");
  BOOST_CHECK(!state.empty());
  base64::encode_rest(std::back_inserter(result), state);
  BOOST_CHECK_EQUAL(result, "YQ==");
  BOOST_CHECK(state.empty());
  result.clear();
  state.clear();

  // check two thirds of quantum which needs one character padding;
  // including how the state develops when encoded by single character
  base64::encode("a", std::back_inserter(result), state);
  BOOST_CHECK_EQUAL(result, "Y");
  BOOST_CHECK(!state.empty());
  base64::encode("a", std::back_inserter(result), state);
  BOOST_CHECK_EQUAL(result, "YW");
  BOOST_CHECK(!state.empty());
  base64::encode_rest(std::back_inserter(result), state);
  BOOST_CHECK_EQUAL(result, "YWE=");
  BOOST_CHECK(state.empty());
  result.clear();
  state.clear();

  // check a complete quantum which needs no padding; including
  // how the state develops when encoded by single character
  base64::encode("a", std::back_inserter(result), state);
  BOOST_CHECK_EQUAL(result, "Y");
  BOOST_CHECK(!state.empty());
  base64::encode("a", std::back_inserter(result), state);
  BOOST_CHECK_EQUAL(result, "YW");
  BOOST_CHECK(!state.empty());
  base64::encode("a", std::back_inserter(result), state);
  BOOST_CHECK_EQUAL(result, "YWFh");
  BOOST_CHECK(state.empty());
  base64::encode_rest(std::back_inserter(result), state);
  BOOST_CHECK_EQUAL(result, "YWFh");
  BOOST_CHECK(state.empty());
}

// checks that the base64 output can be returned as wchar_t too
BOOST_AUTO_TEST_CASE(wide_character_test) {
  BOOST_CHECK(base64::encode<wchar_t>("abc") == L"YWJj");
  BOOST_CHECK(base64::encode<wchar_t>(std::string("abc")) == L"YWJj");

  std::wostringstream output;
  output << base64::io::encode("abc") << base64::io::encode_rest<char>;
  BOOST_CHECK(output.str() == L"YWJj");
}

// checks that the base64-io manipulators are compilable and work
BOOST_AUTO_TEST_CASE(io_test) {
  // check complete quantum where no state has to be remembered
  std::ostringstream output;
  output << base64::io::encode("abc") << base64::io::encode_rest<char>;
  BOOST_CHECK_EQUAL(output.str(), "YWJj");

  // check that encode_rest clears the state
  output.str("");
  output << base64::io::encode("a");
  BOOST_CHECK(!base64::io::empty_state<char>(output));
  output << base64::io::encode_rest<char>;
  BOOST_CHECK(base64::io::empty_state<char>(output));

  // check that forced clearing the state works
  output.str("");
  output << base64::io::encode("a");
  BOOST_CHECK(!base64::io::empty_state<char>(output));
  output << base64::io::clear_state<char>;
  BOOST_CHECK(base64::io::empty_state<char>(output));

  // check one third of quantum which has to be remembered in state
  output.str("");
  output << base64::io::encode("a") << base64::io::encode("bc")
         << base64::io::encode_rest<char>;
  BOOST_CHECK_EQUAL(output.str(), "YWJj");

  // check two thirds of quantum which have to be remembered in state.
  output.str("");
  output << base64::io::encode("ab") << base64::io::encode("c")
         << base64::io::encode_rest<char>;
  BOOST_CHECK_EQUAL(output.str(), "YWJj");
}
