//*************************************************************************
/// Decode from Base64 from and to pointer/length
//*************************************************************************///\file

/******************************************************************************
The MIT License(MIT)
Embedded Template Library.
https://github.com/ETLCPP/etl
https://www.etlcpp.com
Copyright(c) 2024 John Wellbelove
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files(the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and / or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions :
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
******************************************************************************/

#ifndef ETL_BASE64_DECODER_INCLUDED
#define ETL_BASE64_DECODER_INCLUDED

#include "platform.h"
#include "algorithm.h"
#include "binary.h"
#include "delegate.h"
#include "enum_type.h"
#include "error_handler.h"
#include "integral_limits.h"
#include "iterator.h"
#include "span.h"
#include "static_assert.h"
#include "type_traits.h"

#include "base64.h"

#include <stdint.h>

#if ETL_USING_STL
  #include <iterator>
#endif

#define ETL_IS_8_BIT_INTEGRAL(Type) \
  (etl::is_integral<typename etl::remove_cv<Type>::type>::value && (etl::integral_limits<typename etl::remove_cv<Type>::type>::bits == 8U))

#define ETL_IS_ITERATOR_TYPE_8_BIT_INTEGRAL(Type)                                                            \
  (etl::is_integral< typename etl::iterator_traits< typename etl::remove_cv<Type>::type>::value_type>::value \
   && (etl::integral_limits< typename etl::iterator_traits< typename etl::remove_cv<Type>::type>::value_type>::bits == 8U))

namespace etl
{
  //*************************************************************************
  /// Base64 Decoder
  //*************************************************************************
  class ibase64_decoder : public base64
  {
  public:

    typedef etl::span<const unsigned char>        span_type;
    typedef etl::delegate<void(const span_type&)> callback_type;

    //*************************************************************************
    /// Decode to Base64
    //*************************************************************************
    template <typename T>
    ETL_CONSTEXPR14 bool decode(T value)
    {
      ETL_STATIC_ASSERT(ETL_IS_8_BIT_INTEGRAL(T), "Input type must be an 8 bit integral");

      push_to_input_buffer(value);

      if (input_buffer_is_full())
      {
        if (decode_block())
        {
          if (callback.is_valid())
          {
            if (output_buffer_is_full())
            {
              callback(span());
              reset_output_buffer();
            }
          }
        }

        reset_input_buffer();
      }

      return !error();
    }

    //*************************************************************************
    /// Decode from Base64
    //*************************************************************************
    template <typename TInputIterator>
    ETL_CONSTEXPR14 bool decode(TInputIterator input_begin, TInputIterator input_end)
    {
      ETL_STATIC_ASSERT(ETL_IS_ITERATOR_TYPE_8_BIT_INTEGRAL(TInputIterator), "Input type must be an 8 bit integral");

      while (input_begin != input_end)
      {
        if (!decode(*input_begin++))
        {
          return false;
        }
      }

      return true;
    }

    //*************************************************************************
    /// Decode from Base64
    //*************************************************************************
    template <typename TInputIterator>
    ETL_CONSTEXPR14 bool decode(TInputIterator input_begin, size_t input_length)
    {
      ETL_STATIC_ASSERT(ETL_IS_ITERATOR_TYPE_8_BIT_INTEGRAL(TInputIterator), "Input type must be an 8 bit integral");

      while (input_length-- != 0)
      {
        if (!decode(*input_begin++))
        {
          return false;
        }
      }

      return true;
    }

    //*************************************************************************
    /// Decode from Base64
    //*************************************************************************
    template <typename TInputIterator>
    ETL_CONSTEXPR14 bool decode_final(TInputIterator input_begin, TInputIterator input_end)
    {
      return decode(input_begin, input_end) && flush();
    }

    //*************************************************************************
    /// Decode from Base64
    //*************************************************************************
    template <typename TInputIterator>
    ETL_CONSTEXPR14 bool decode_final(TInputIterator input_begin, size_t input_length)
    {
      return decode(input_begin, input_length) && flush();
    }

    //*************************************************************************
    /// Flush any remaining data to the output.
    //*************************************************************************
    ETL_CONSTEXPR14 bool flush()
    {
      // Encode any remaining input data.
      bool success = decode_block();

      reset_input_buffer();

      if (success)
      {
        if (callback.is_valid())
        {
          // Send any remaining data.
          if (size() != 0)
          {
            callback(span());
          }

          // Indicate this was the final block.
          callback(span_type());

          reset_output_buffer();
        }
      }

      padding_received = false;

      return success;
    }

    //*************************************************************************
    /// Reset the encoder.
    //*************************************************************************
    ETL_CONSTEXPR14 void restart()
    {
      reset_input_buffer();
      reset_output_buffer();
      overflow_detected     = false;
      invalid_data_detected = false;
      padding_received      = false;
    }

    //*************************************************************************
    /// Returns the beginning of the output buffer.
    //*************************************************************************
    ETL_NODISCARD ETL_CONSTEXPR14 const unsigned char* begin() const
    {
      return p_output_buffer;
    }

    //*************************************************************************
    /// This only returns a useful value if a callback has not been set or
    /// called.
    //*************************************************************************
    ETL_NODISCARD ETL_CONSTEXPR14 const unsigned char* end() const
    {
      return p_output_buffer + output_buffer_length;
    }

    //*************************************************************************
    /// Returns the beginning of the output buffer.
    //*************************************************************************
    ETL_NODISCARD ETL_CONSTEXPR14 const unsigned char* cbegin() const
    {
      return p_output_buffer;
    }

    //*************************************************************************
    /// This only returns a useful value if a callback has not been set or
    /// called.
    //*************************************************************************
    ETL_NODISCARD ETL_CONSTEXPR14 const unsigned char* cend() const
    {
      return p_output_buffer + output_buffer_length;
    }

    //*************************************************************************
    /// Returns the size of the output buffer.
    /// This only returns a useful value if a callback has not been set or
    /// called.
    //*************************************************************************
    ETL_NODISCARD ETL_CONSTEXPR14 size_t size() const
    {
      return output_buffer_length;
    }

    //*************************************************************************
    /// Returns the maximum size of the output buffer.
    //*************************************************************************
    ETL_NODISCARD ETL_CONSTEXPR14 size_t buffer_size() const
    {
      return output_buffer_max_size;
    }

    //*************************************************************************
    /// Get a span of the output data.
    /// This only returns a useful span if a callback has not been set or
    /// called.
    //*************************************************************************
    ETL_NODISCARD ETL_CONSTEXPR14 span_type span() const
    {
      return span_type(begin(), end());
    }

    //*************************************************************************
    /// Returns true if the output buffer has overflowed
    //*************************************************************************
    ETL_NODISCARD ETL_CONSTEXPR14 bool overflow() const
    {
      return overflow_detected;
    }

    //*************************************************************************
    /// Returns true if an invalid character was detected.
    //*************************************************************************
    ETL_NODISCARD ETL_CONSTEXPR14 bool invalid_data() const
    {
      return invalid_data_detected;
    }

    //*************************************************************************
    /// Returns true if an error was detected.
    //*************************************************************************
    ETL_NODISCARD ETL_CONSTEXPR14 bool error() const
    {
      return overflow() || invalid_data();
    }

  protected:

    //*************************************************************************
    /// Constructor
    //*************************************************************************
    ETL_CONSTEXPR14 ibase64_decoder(const char* encoder_table_, bool use_padding_, unsigned char* p_output_buffer_, size_t ouput_buffer_max_size_,
                              callback_type callback_)
      : base64(encoder_table_, use_padding_)
      , input_buffer()
      , input_buffer_length(0)
      , p_output_buffer(p_output_buffer_)
      , output_buffer_length(0)
      , output_buffer_max_size(ouput_buffer_max_size_)
      , callback(callback_)
      , overflow_detected(false)
      , invalid_data_detected(false)
      , padding_received(false)
    {
    }

    //*************************************************************************
    /// Calculates the minimum buffer size required to decode from Base64
    //*************************************************************************
    ETL_NODISCARD
    static ETL_CONSTEXPR14 size_t decoded_size_from_valid_input_length(size_t input_length)
    {
      return input_length - (input_length / 4U);
    }

  private:

    //*************************************************************************
    // Translates a sextet into an index
    //*************************************************************************
    template <typename T>
    ETL_CONSTEXPR14 uint32_t get_index_from_sextet(T sextet)
    {
      const char* encoder_table_end = encoder_table + 64;
      const char* p_sextet          = etl::find(encoder_table, encoder_table_end, static_cast<char>(sextet));

      if (p_sextet != encoder_table_end)
      {
        return static_cast<uint32_t>(etl::distance(encoder_table, p_sextet));
      }
      else
      {
        invalid_data_detected = true;
        return 0;
      }
    }

    //*************************************************************************
    /// Gets the padding character
    //*************************************************************************
    template <typename T>
    ETL_NODISCARD
    static
      ETL_CONSTEXPR14 T padding()
    {
      return static_cast<T>('=');
    }

    //*************************************************************************
    /// Decode one block of data.
    //*************************************************************************
    ETL_CONSTEXPR14 bool decode_block()
    {
      switch (input_buffer_length)
      {
        // Only triggered on call to flush().
        case 2:
          {
            uint32_t sextets = (get_index_from_sextet(input_buffer[0]) << 6);
            sextets          = sextets | (get_index_from_sextet(input_buffer[1]));
            push_to_output_buffer((sextets >> 4) & 0xFF);
            break;
          }

        // Only triggered on call to flush().
        case 3:
          {
            uint32_t sextets = (get_index_from_sextet(input_buffer[0]) << 12);
            sextets          = sextets | (get_index_from_sextet(input_buffer[1]) << 6);
            sextets          = sextets | (get_index_from_sextet(input_buffer[2]));
            push_to_output_buffer((sextets >> 10) & 0xFF);
            push_to_output_buffer((sextets >> 2) & 0xFF);
            break;
          }

        // Only triggered on call to decode().
        case 4:
          {
            // Read in four sextets
            uint32_t sextets = (get_index_from_sextet(input_buffer[0]) << 18);
            sextets          = sextets | (get_index_from_sextet(input_buffer[1]) << 12);
            sextets          = sextets | (get_index_from_sextet(input_buffer[2]) << 6);
            sextets          = sextets | (get_index_from_sextet(input_buffer[3]));

            // Write out three octets
            push_to_output_buffer((sextets >> 16) & 0xFF);
            push_to_output_buffer((sextets >> 8) & 0xFF);
            push_to_output_buffer((sextets >> 0) & 0xFF);
            break;
          }

        default:
          {
            break;
          }
      }

      ETL_ASSERT(!invalid_data_detected, ETL_ERROR(etl::base64_invalid_data));
      ETL_ASSERT(!overflow_detected, ETL_ERROR(etl::base64_overflow));

      return (!invalid_data_detected && !overflow_detected);
    }

    //*************************************************************************
    // Push to the output buffer.
    //*************************************************************************
    ETL_CONSTEXPR14 void push_to_output_buffer(unsigned char c)
    {
      if (output_buffer_length < output_buffer_max_size)
      {
        p_output_buffer[output_buffer_length++] = c;
      }
      else
      {
        overflow_detected = true;
      }
    }

    //*************************************************************************
    //
    //*************************************************************************
    ETL_CONSTEXPR14 bool output_buffer_is_full() const
    {
      return output_buffer_length == output_buffer_max_size;
    }

    //*************************************************************************
    //
    //*************************************************************************
    ETL_CONSTEXPR14 bool output_buffer_is_empty() const
    {
      return output_buffer_length == 0;
    }

    //*************************************************************************
    //
    //*************************************************************************
    ETL_CONSTEXPR14 void reset_output_buffer()
    {
      output_buffer_length = 0;
    }

    //*************************************************************************
    // Push to the input buffer.
    //*************************************************************************
    template <typename T>
    ETL_CONSTEXPR14 void push_to_input_buffer(T value)
    {
      if (value == padding<T>())
      {
        padding_received = true;
      }
      else
      {
        if (padding_received)
        {
          ETL_ASSERT_FAIL(ETL_ERROR(etl::base64_invalid_data));
          invalid_data_detected = true;
        }
        else
        {
          input_buffer[input_buffer_length++] = static_cast<char>(value);
        }
      }
    }

    //*************************************************************************
    //
    //*************************************************************************
    ETL_CONSTEXPR14 bool input_buffer_is_full() const
    {
      return input_buffer_length == 4U;
    }

    //*************************************************************************
    //
    //*************************************************************************
    ETL_CONSTEXPR14 void reset_input_buffer()
    {
      input_buffer_length = 0;
    }

    char   input_buffer[4];
    size_t input_buffer_length;

    unsigned char* p_output_buffer;
    size_t         output_buffer_length;
    const size_t   output_buffer_max_size;

    callback_type callback;

    bool overflow_detected;
    bool invalid_data_detected;
    bool padding_received;
  };

  //*************************************************************************
  /// Base64 RFC-2152 Decoder
  //*************************************************************************
  template <size_t Buffer_Size = etl::base64::Min_Decode_Buffer_Size>
  class base64_rfc2152_decoder : public ibase64_decoder
  {
  public:

    ETL_STATIC_ASSERT((Buffer_Size >= etl::base64::Min_Decode_Buffer_Size), "Buffer size must be greater than etl::base64::Min_Decode_Buffer_Size");

    //*************************************************************************
    /// Base64 RFC-2152 constructor.
    //*************************************************************************
    ETL_CONSTEXPR14 base64_rfc2152_decoder()
      : ibase64_decoder(etl::base64::character_set_1(), etl::base64::Padding::No_Padding, output_buffer, Buffer_Size, callback_type())
      , output_buffer()
    {
    }

    //*************************************************************************
    /// Base64 RFC-2152 constructor.
    //*************************************************************************
    ETL_CONSTEXPR14 base64_rfc2152_decoder(callback_type callback_)
      : ibase64_decoder(etl::base64::character_set_1(), etl::base64::Padding::No_Padding, output_buffer, Buffer_Size, callback_)
      , output_buffer()
    {
    }

    //*************************************************************************
    /// Calculate the required output encode buffer size.
    //*************************************************************************
    ETL_NODISCARD
    static
      ETL_CONSTEXPR14 size_t safe_output_buffer_size(size_t input_length)
    {
      return ibase64_decoder::decoded_size_from_valid_input_length(input_length);
    }

  private:

    /// The internal output buffer.
    unsigned char output_buffer[Buffer_Size];
  };

  //*************************************************************************
  /// Base64 RFC-3501 Decoder
  //*************************************************************************
  template <size_t Buffer_Size = etl::base64::Min_Decode_Buffer_Size>
  class base64_rfc3501_decoder : public ibase64_decoder
  {
  public:

    ETL_STATIC_ASSERT((Buffer_Size >= etl::base64::Min_Decode_Buffer_Size), "Buffer size must be greater than etl::base64::Min_Decode_Buffer_Size");

    //*************************************************************************
    /// Base64 RFC-3501 constructor.
    //*************************************************************************
    ETL_CONSTEXPR14 base64_rfc3501_decoder()
      : ibase64_decoder(etl::base64::character_set_3(), etl::base64::Padding::No_Padding, output_buffer, Buffer_Size, callback_type())
      , output_buffer()
    {
    }

    //*************************************************************************
    /// Base64 RFC-3501 constructor.
    //*************************************************************************
    ETL_CONSTEXPR14 base64_rfc3501_decoder(callback_type callback_)
      : ibase64_decoder(etl::base64::character_set_3(), etl::base64::Padding::No_Padding, output_buffer, Buffer_Size, callback_)
      , output_buffer()
    {
    }

    //*************************************************************************
    /// Calculate the required output encode buffer size.
    //*************************************************************************
    ETL_NODISCARD
    static
      ETL_CONSTEXPR14 size_t safe_output_buffer_size(size_t input_length)
    {
      return ibase64_decoder::decoded_size_from_valid_input_length(input_length);
    }

  private:

    /// The internal output buffer.
    unsigned char output_buffer[Buffer_Size];
  };

  //*************************************************************************
  /// Base64 RFC-4648 Decoder
  //*************************************************************************
  template <size_t Buffer_Size = etl::base64::Min_Decode_Buffer_Size>
  class base64_rfc4648_decoder : public ibase64_decoder
  {
  public:

    ETL_STATIC_ASSERT((Buffer_Size >= etl::base64::Min_Decode_Buffer_Size), "Buffer size must be greater than etl::base64::Min_Decode_Buffer_Size");

    //*************************************************************************
    /// Base64 RFC-4648 constructor.
    //*************************************************************************
    ETL_CONSTEXPR14 base64_rfc4648_decoder()
      : ibase64_decoder(etl::base64::character_set_1(), etl::base64::Padding::No_Padding, output_buffer, Buffer_Size, callback_type())
      , output_buffer()
    {
    }

    //*************************************************************************
    /// Base64 RFC-4648 constructor.
    //*************************************************************************
    ETL_CONSTEXPR14 base64_rfc4648_decoder(callback_type callback_)
      : ibase64_decoder(etl::base64::character_set_1(), etl::base64::Padding::No_Padding, output_buffer, Buffer_Size, callback_)
      , output_buffer()
    {
    }

    //*************************************************************************
    /// Calculate the required output encode buffer size.
    //*************************************************************************
    ETL_NODISCARD
    static
      ETL_CONSTEXPR14 size_t safe_output_buffer_size(size_t input_length)
    {
      return ibase64_decoder::decoded_size_from_valid_input_length(input_length);
    }

  private:

    /// The internal output buffer.
    unsigned char output_buffer[Buffer_Size];
  };

  //*************************************************************************
  /// Base64 RFC-4648-Padding Decoder
  //*************************************************************************
  template <size_t Buffer_Size = etl::base64::Min_Decode_Buffer_Size>
  class base64_rfc4648_padding_decoder : public ibase64_decoder
  {
  public:

    ETL_STATIC_ASSERT((Buffer_Size >= etl::base64::Min_Decode_Buffer_Size), "Buffer size must be greater than etl::base64::Min_Decode_Buffer_Size");

    //*************************************************************************
    /// Base64 RFC-4648-Padding constructor.
    //*************************************************************************
    ETL_CONSTEXPR14 base64_rfc4648_padding_decoder()
      : ibase64_decoder(etl::base64::character_set_1(), etl::base64::Padding::Use_Padding, output_buffer, Buffer_Size, callback_type())
      , output_buffer()
    {
    }

    //*************************************************************************
    /// Base64 RFC-4648-Padding constructor.
    //*************************************************************************
    ETL_CONSTEXPR14 base64_rfc4648_padding_decoder(callback_type callback_)
      : ibase64_decoder(etl::base64::character_set_1(), etl::base64::Padding::Use_Padding, output_buffer, Buffer_Size, callback_)
      , output_buffer()
    {
    }

    //*************************************************************************
    /// Calculate the required output encode buffer size.
    //*************************************************************************
    ETL_NODISCARD
    static
      ETL_CONSTEXPR14 size_t safe_output_buffer_size(size_t input_length)
    {
      return ibase64_decoder::decoded_size_from_valid_input_length(input_length);
    }

  private:

    /// The internal output buffer.
    unsigned char output_buffer[Buffer_Size];
  };

  //*************************************************************************
  /// Base64 RFC-4648-URL Decoder
  //*************************************************************************
  template <size_t Buffer_Size = etl::base64::Min_Decode_Buffer_Size>
  class base64_rfc4648_url_decoder : public ibase64_decoder
  {
  public:

    ETL_STATIC_ASSERT((Buffer_Size >= etl::base64::Min_Decode_Buffer_Size), "Buffer size must be greater than etl::base64::Min_Decode_Buffer_Size");

    //*************************************************************************
    /// Base64 RFC-4648-Padding constructor.
    //*************************************************************************
    ETL_CONSTEXPR14 base64_rfc4648_url_decoder()
      : ibase64_decoder(etl::base64::character_set_2(), etl::base64::Padding::No_Padding, output_buffer, Buffer_Size, callback_type())
      , output_buffer()
    {
    }

    //*************************************************************************
    /// Base64 RFC-4648-Padding constructor.
    //*************************************************************************
    ETL_CONSTEXPR14 base64_rfc4648_url_decoder(callback_type callback_)
      : ibase64_decoder(etl::base64::character_set_2(), etl::base64::Padding::No_Padding, output_buffer, Buffer_Size, callback_)
      , output_buffer()
    {
    }

    //*************************************************************************
    /// Calculate the required output encode buffer size.
    //*************************************************************************
    ETL_NODISCARD
    static
      ETL_CONSTEXPR14 size_t safe_output_buffer_size(size_t input_length)
    {
      return ibase64_decoder::decoded_size_from_valid_input_length(input_length);
    }

  private:

    /// The internal output buffer.
    unsigned char output_buffer[Buffer_Size];
  };

  //*************************************************************************
  /// Base64 RFC-4648-URL-Padding Decoder
  //*************************************************************************
  template <size_t Buffer_Size = etl::base64::Min_Decode_Buffer_Size>
  class base64_rfc4648_url_padding_decoder : public ibase64_decoder
  {
  public:

    ETL_STATIC_ASSERT((Buffer_Size >= etl::base64::Min_Decode_Buffer_Size), "Buffer size must be greater than etl::base64::Min_Decode_Buffer_Size");

    //*************************************************************************
    /// Base64 RFC-4648-Padding constructor.
    //*************************************************************************
    ETL_CONSTEXPR14 base64_rfc4648_url_padding_decoder()
      : ibase64_decoder(etl::base64::character_set_2(), etl::base64::Padding::Use_Padding, output_buffer, Buffer_Size, callback_type())
      , output_buffer()
    {
    }

    //*************************************************************************
    /// Base64 RFC-4648-Padding constructor.
    //*************************************************************************
    ETL_CONSTEXPR14 base64_rfc4648_url_padding_decoder(callback_type callback_)
      : ibase64_decoder(etl::base64::character_set_2(), etl::base64::Padding::Use_Padding, output_buffer, Buffer_Size, callback_)
      , output_buffer()
    {
    }

    //*************************************************************************
    /// Calculate the required output encode buffer size.
    //*************************************************************************
    ETL_NODISCARD
    static
      ETL_CONSTEXPR14 size_t safe_output_buffer_size(size_t input_length)
    {
      return ibase64_decoder::decoded_size_from_valid_input_length(input_length);
    }

  private:

    /// The internal output buffer.
    unsigned char output_buffer[Buffer_Size];
  };
} // namespace etl

#undef ETL_IS_TYPE_8_BIT_INTEGRAL
#undef ETL_IS_ITERATOR_TYPE_8_BIT_INTEGRAL

#endif
