#pragma once
#include "AftrConfig.h"
#include <string>
#include <vector>
#include <algorithm>

#ifdef AFTR_CONFIG_USE_BOOST
#include <boost/archive/iterators/base64_from_binary.hpp>
#include <boost/archive/iterators/binary_from_base64.hpp>
#include <boost/archive/iterators/transform_width.hpp>
#include <boost/archive/iterators/insert_linebreaks.hpp>
#include <boost/archive/iterators/remove_whitespace.hpp>
#include <boost/algorithm/string.hpp>
#endif

namespace Aftr
{
   namespace AftrUtil
   {
      /**
         Converts in passed data to standard 72 column base 64 lettering.
      */
      std::string encodeBinaryToBase64( const std::vector<unsigned char>& data );

      template< typename Iter >
      std::string encodeBinaryToBase64( Iter begin, Iter end )
      {
         using namespace boost::archive::iterators;
         auto length = std::distance( begin, end );
         using It = base64_from_binary<transform_width<Iter, 6, 8>>;
         auto tmp = std::string( It( begin ), It( end ) );
         return tmp.append( (3 - length % 3) % 3, '=' );

         return tmp;
      }

      template< typename Iter, typename IterOut >
      int encodeBinaryToBase64( Iter begin, Iter end, IterOut dst, bool addNullTerminatorToDst = false )
      {
         using namespace boost::archive::iterators;
         auto length = std::distance( begin, end );
         //the required size of dst buffer is (8/6)(inputsize)+numPads+optional null terminator
         using It = base64_from_binary<transform_width<Iter, 6, 8>>;
         auto dstEnd = std::copy( It(begin), It(end), dst );

         //Now we have to append the '=' padding char, up to 3 of them
         int numPads = (3 - length % 3) % 3;
         std::fill_n( dstEnd, numPads, '=' );
         if( addNullTerminatorToDst )
            std::fill_n( dstEnd + numPads, 1, '\0' ); //add terminating null on this string.

         auto numBytes = std::distance( dst, dstEnd );
         if( addNullTerminatorToDst )
            return static_cast<int>(numBytes) + numPads + 1;

         return static_cast<int>(numBytes) + numPads;
      }

      int getDstBufferSizeRequired_toEncodeBinaryToBase64( int numBinaryBytes, bool addNullTerminatorToDst = false );

      /**
         Any extra padding at the end "==" or "=" will be ignored when converting to binary.
      */
      std::vector< unsigned char > encodeBase64ToBinary( const std::string& base64Str );

      /**
         Converts in passed ascii string to binary data. Since each character is 8 bits
         but must reside in the range [0-9A-Za-z+/]+[=]*, (which is 0-63), we only need
         6 bits to encode the value. Therefore going to binary from base64 ascii will
         reduce size by 25%.
         This version does not allocate
      */
      template< typename Iter >
      int encodeBase64ToBinary( const std::string& base64Str, Iter begin, Iter end )
      {
         using namespace boost::archive::iterators;

         std::string baseStr = base64Str;
         boost::trim( baseStr ); //remove any leading or trailing whitespace

         using it_binary_t = transform_width< binary_from_base64<remove_whitespace< std::string::const_iterator > >, 8, 6 >;

         // Decode
         std::string::difference_type paddChars = std::count( baseStr.begin(), baseStr.end(), '=' );
         std::replace( baseStr.begin(), baseStr.end(), '=', 'A' ); // replace '=' by base64 encoding of '\0'
         auto endIt = std::copy( it_binary_t( base64Str.begin() ), it_binary_t( base64Str.end() - paddChars ), begin );
         return static_cast<int>(std::distance( begin, endIt ));
      }

   }
}