///\file

/******************************************************************************
The MIT License(MIT)

Embedded Template Library.
https://github.com/ETLCPP/etl
https://www.etlcpp.com

Copyright(c) 2026 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_MANCHESTER_INCLUDED
#define ETL_MANCHESTER_INCLUDED

#include "platform.h"
#include "span.h"
#include "static_assert.h"

///\defgroup manchester manchester
/// Manchester encoding and decoding
///\ingroup utilities

namespace etl
{
  namespace private_manchester
  {
    //*************************************************************************
    /// Type trait to determine if a type is supported as an encode input.
    ///\tparam TChunk The input type to check.
    //*************************************************************************
    template <typename TChunk>
    struct is_encodable
    {
      static const bool value =
#if ETL_USING_8BIT_TYPES
        etl::is_same<TChunk, uint8_t>::value ||
#endif
        etl::is_same<TChunk, uint16_t>::value
#if ETL_USING_64BIT_TYPES
        || etl::is_same<TChunk, uint32_t>::value
#endif
        ;
    };

    //*************************************************************************
    /// Type trait to determine if a type is supported as a decode input.
    ///\tparam TChunk The input type to check.
    //*************************************************************************
    template <typename TChunk>
    struct is_decodable
    {
      static const bool value =
#if ETL_USING_8BIT_TYPES
        etl::is_same<TChunk, uint16_t>::value ||
#endif
        etl::is_same<TChunk, uint32_t>::value
#if ETL_USING_64BIT_TYPES
        || etl::is_same<TChunk, uint64_t>::value
#endif
        ;
    };

    //*************************************************************************
    /// Type trait to determine the encoded type for a given decoded type.
    /// Encoding doubles the bit width: uint8_t->uint16_t, uint16_t->uint32_t,
    /// etc.
    ///\tparam T The decoded type.
    //*************************************************************************
    template <typename T>
    struct encoded
    {
      ETL_STATIC_ASSERT(sizeof(T) == 0, "Manchester encoding type should be one of [uint8_t, uint16_t, uint32_t]");
    };

#if ETL_USING_8BIT_TYPES
    template <>
    struct encoded<uint8_t>
    {
      typedef uint16_t type;
    };
#endif

    template <>
    struct encoded<uint16_t>
    {
      typedef uint32_t type;
    };

#if ETL_USING_64BIT_TYPES
    template <>
    struct encoded<uint32_t>
    {
      typedef uint64_t type;
    };
#endif

    //*************************************************************************
    /// Type trait to determine the decoded type for a given encoded type.
    /// Decoding halves the bit width: uint16_t->uint8_t, uint32_t->uint16_t,
    /// etc.
    ///\tparam T The encoded type.
    //*************************************************************************
    template <typename T>
    struct decoded
    {
      ETL_STATIC_ASSERT(sizeof(T) == 0, "Manchester decoding type should be one of [uint16_t, uint32_t, uint64_t]");
    };

#if ETL_USING_64BIT_TYPES
    template <>
    struct decoded<uint16_t>
    {
      typedef uint8_t type;
    };
#endif

    template <>
    struct decoded<uint32_t>
    {
      typedef uint16_t type;
    };

#if ETL_USING_64BIT_TYPES
    template <>
    struct decoded<uint64_t>
    {
      typedef uint32_t type;
    };
#endif

    //*************************************************************************
    /// Normal Manchester encoding type (no inversion).
    //*************************************************************************
    struct manchester_type_normal
    {
#if ETL_USING_64BIT_TYPES
      static const uint64_t inversion_mask = 0x0000000000000000ULL;
#else
      static const uint32_t inversion_mask = 0x00000000UL;
#endif
    };

    //*************************************************************************
    /// Inverted Manchester encoding type.
    //*************************************************************************
    struct manchester_type_inverted
    {
#if ETL_USING_64BIT_TYPES
      static const uint64_t inversion_mask = 0xFFFFFFFFFFFFFFFFULL;
#else
      static const uint32_t inversion_mask = 0xFFFFFFFFUL;
#endif
    };

    //*************************************************************************
    /// Read a multi-byte value from a span in little-endian byte order.
    ///\tparam T The type to read.
    ///\param bytes The span to read from.
    ///\param index The starting index in the span.
    ///\return The value read from the span.
    //*************************************************************************
    template <typename T>
    static ETL_CONSTEXPR14 T read_little_endian(etl::span<const uint_least8_t> bytes, size_t index)
    {
      T value = 0;
      for (size_t j = 0; j < sizeof(T); ++j)
      {
        value |= static_cast<T>(bytes[index + j]) << (j * CHAR_BIT);
      }
      return value;
    }

    //*************************************************************************
    /// Write a multi-byte value to a span in little-endian byte order.
    ///\tparam T The type to write.
    ///\param bytes The span to write to.
    ///\param index The starting index in the span.
    ///\param value The value to write.
    //*************************************************************************
    template <typename T>
    static ETL_CONSTEXPR14 void write_little_endian(etl::span<uint_least8_t> bytes, size_t index, T value)
    {
      for (size_t j = 0; j < sizeof(T); ++j)
      {
        bytes[index + j] = static_cast<uint_least8_t>(value >> (j * CHAR_BIT));
      }
    }
  } // namespace private_manchester

  //***************************************************************************
  /// Exception for Manchester.
  //***************************************************************************
  class manchester_exception : public etl::exception
  {
  public:

    manchester_exception(string_type reason_, string_type file_name_, numeric_type line_number_)
      : exception(reason_, file_name_, line_number_)
    {
    }
  };

  //***************************************************************************
  /// Invalid size exception for Manchester
  //***************************************************************************
  class manchester_invalid_size : public etl::manchester_exception
  {
  public:

    manchester_invalid_size(string_type file_name_, numeric_type line_number_)
      : etl::manchester_exception("manchester:size", file_name_, line_number_)
    {
    }
  };

  //***************************************************************************
  ///\ingroup manchester
  /// Base template class for Manchester encoding and decoding.
  ///\tparam TType The Manchester encoding type (normal or inverted).
  //***************************************************************************
  template <typename TManchesterType>
  struct manchester_base
  {
    ETL_STATIC_ASSERT((etl::is_same<TManchesterType, private_manchester::manchester_type_normal>::value
                       || etl::is_same<TManchesterType, private_manchester::manchester_type_inverted>::value),
                      "TManchesterType must be manchester_type_normal or manchester_type_inverted");

    ETL_STATIC_ASSERT(CHAR_BIT == etl::numeric_limits<uint_least8_t>::digits,
                      "Manchester requires uint_least8_t to have the same number of bits as CHAR (CHAR_BITS)");

    //*************************************************************************
    // Encoding functions
    //*************************************************************************

#if ETL_USING_8BIT_TYPES
    //*************************************************************************
    /// Encode a 8-bit unsigned value and return 16-bit result.
    ///\param decoded The value to encode.
    ///\return The Manchester encoded value.
    //*************************************************************************
    template <typename TDecoded>
    static
      ETL_CONSTEXPR14 typename etl::enable_if< etl::is_same<TDecoded, uint8_t>::value, typename private_manchester::encoded<TDecoded>::type>::type
      encode(TDecoded decoded)
    {
      typedef typename private_manchester::encoded<TDecoded>::type TEncoded;

      TEncoded encoded = decoded;

      encoded = static_cast<TEncoded>((static_cast<unsigned int>(encoded) | (static_cast<unsigned int>(encoded) << 4U)) & 0x0F0FU);
      encoded = static_cast<TEncoded>((static_cast<unsigned int>(encoded) | (static_cast<unsigned int>(encoded) << 2U)) & 0x3333U);
      encoded = static_cast<TEncoded>((static_cast<unsigned int>(encoded) | (static_cast<unsigned int>(encoded) << 1U)) & 0x5555U);
      encoded = static_cast<TEncoded>((static_cast<unsigned int>(encoded) | (static_cast<unsigned int>(encoded) << 1U))
                                      ^ (0xAAAAU ^ static_cast<unsigned int>(TManchesterType::inversion_mask)));
      return encoded;
    }
#endif

    //*************************************************************************
    /// Encode a 16-bit unsigned value and return the 32-bit result.
    ///\param decoded The value to encode.
    ///\return The Manchester encoded value.
    //*************************************************************************
    template <typename TDecoded>
    static
      ETL_CONSTEXPR14 typename etl::enable_if< etl::is_same<TDecoded, uint16_t>::value, typename private_manchester::encoded<TDecoded>::type>::type
      encode(TDecoded decoded)
    {
      typedef typename private_manchester::encoded<TDecoded>::type TEncoded;

      TEncoded encoded = decoded;

      encoded = (encoded | (encoded << 8U)) & 0x00FF00FFUL;
      encoded = (encoded | (encoded << 4U)) & 0x0F0F0F0FUL;
      encoded = (encoded | (encoded << 2U)) & 0x33333333UL;
      encoded = (encoded | (encoded << 1U)) & 0x55555555UL;
      encoded = (encoded | (encoded << 1U)) ^ (0xAAAAAAAAUL ^ static_cast<TEncoded>(TManchesterType::inversion_mask));
      return encoded;
    }

#if ETL_USING_64BIT_TYPES
    //*************************************************************************
    /// Encode a 32-bit unsigned value and return the 64-bit result.
    ///\param decoded The value to encode.
    ///\return The Manchester encoded value.
    //*************************************************************************
    template <typename TDecoded>
    static
      ETL_CONSTEXPR14 typename etl::enable_if< etl::is_same<TDecoded, uint32_t>::value, typename private_manchester::encoded<TDecoded>::type>::type
      encode(TDecoded decoded)
    {
      typedef typename private_manchester::encoded<TDecoded>::type TEncoded;

      TEncoded encoded = decoded;

      encoded = (encoded | (encoded << 16U)) & 0x0000FFFF0000FFFFULL;
      encoded = (encoded | (encoded << 8U)) & 0x00FF00FF00FF00FFULL;
      encoded = (encoded | (encoded << 4U)) & 0x0F0F0F0F0F0F0F0FULL;
      encoded = (encoded | (encoded << 2U)) & 0x3333333333333333ULL;
      encoded = (encoded | (encoded << 1U)) & 0x5555555555555555ULL;
      encoded = (encoded | (encoded << 1U)) ^ (0xAAAAAAAAAAAAAAAAULL ^ TManchesterType::inversion_mask);
      return encoded;
    }
#endif

    //*************************************************************************
    /// Encode a span of data with the specified chunk size.
    ///\param source      The source data to encode.
    ///\param destination The destination buffer for encoded data.
    ///\tparam TChunk     The chunk size for encoding (default: uint_least8_t).
    //*************************************************************************
    template <typename TChunk = uint_least8_t>
    static ETL_CONSTEXPR14 void encode(etl::span<const uint_least8_t> decoded, etl::span<uint_least8_t> encoded)
    {
      typedef TChunk                                                  TDecoded;
      typedef typename etl::private_manchester::encoded<TChunk>::type TEncoded;

      ETL_ASSERT(encoded.size() >= decoded.size() * 2, ETL_ERROR(manchester_invalid_size));
      ETL_ASSERT(decoded.size() % sizeof(TDecoded) == 0, ETL_ERROR(manchester_invalid_size));

      size_t dest_index   = 0;
      size_t source_index = 0;
      for (size_t i = 0; i < decoded.size() / sizeof(TDecoded); ++i)
      {
        const TDecoded decoded_value = private_manchester::read_little_endian<TDecoded>(decoded, source_index);
        const TEncoded encoded_value = encode(decoded_value);
        private_manchester::write_little_endian<TEncoded>(encoded, dest_index, encoded_value);

        source_index += sizeof(TDecoded);
        dest_index += sizeof(TEncoded);
      }
    }

    //*************************************************************************
    // Decoding functions
    //*************************************************************************

#if ETL_USING_8BIT_TYPES
    //*************************************************************************
    /// Decode a 16-bit value and return the 8-bit result.
    ///\param encoded The value to decode.
    ///\return The Manchester decoded value.
    //*************************************************************************
    template <typename TEncoded>
    static
      ETL_CONSTEXPR14 typename etl::enable_if< etl::is_same<TEncoded, uint16_t>::value, typename private_manchester::decoded<TEncoded>::type>::type
      decode(TEncoded encoded)
    {
      typedef typename private_manchester::decoded<TEncoded>::type TDecoded;

      encoded = static_cast<TEncoded>((static_cast<unsigned int>(encoded) ^ (0xAAAAU ^ static_cast<unsigned int>(TManchesterType::inversion_mask)))
                                      & 0x5555U);
      encoded = static_cast<TEncoded>((static_cast<unsigned int>(encoded) | (static_cast<unsigned int>(encoded) >> 1)) & 0x3333U);
      encoded = static_cast<TEncoded>((static_cast<unsigned int>(encoded) | (static_cast<unsigned int>(encoded) >> 2)) & 0x0F0FU);
      return static_cast<TDecoded>(static_cast<unsigned int>(encoded) | (static_cast<unsigned int>(encoded) >> 4U));
    }
#endif

    //*************************************************************************
    /// Decode a 32-bit value and return the 16-bit result.
    ///\param encoded The value to decode.
    ///\return The Manchester decoded value.
    //*************************************************************************
    template <typename TEncoded>
    static
      ETL_CONSTEXPR14 typename etl::enable_if< etl::is_same<TEncoded, uint32_t>::value, typename private_manchester::decoded<TEncoded>::type>::type
      decode(TEncoded encoded)
    {
      typedef typename private_manchester::decoded<TEncoded>::type TDecoded;

      encoded = (encoded ^ (0xAAAAAAAAUL ^ static_cast<TEncoded>(TManchesterType::inversion_mask))) & 0x55555555UL;
      encoded = (encoded | (encoded >> 1)) & 0x33333333UL;
      encoded = (encoded | (encoded >> 2)) & 0x0F0F0F0FUL;
      encoded = (encoded | (encoded >> 4)) & 0x00FF00FFUL;
      return static_cast<TDecoded>(encoded | (encoded >> 8U));
    }

#if ETL_USING_64BIT_TYPES
    //*************************************************************************
    /// Decode a 64-bit value and return the 32-bit result.
    ///\param encoded The value to decode.
    ///\return The Manchester decoded value.
    //*************************************************************************
    template <typename TEncoded>
    static
      ETL_CONSTEXPR14 typename etl::enable_if< etl::is_same<TEncoded, uint64_t>::value, typename private_manchester::decoded<TEncoded>::type>::type
      decode(TEncoded encoded)
    {
      typedef typename private_manchester::decoded<TEncoded>::type TDecoded;

      encoded = (encoded ^ (0xAAAAAAAAAAAAAAAAULL ^ TManchesterType::inversion_mask)) & 0x5555555555555555ULL;
      encoded = (encoded | (encoded >> 1)) & 0x3333333333333333ULL;
      encoded = (encoded | (encoded >> 2)) & 0x0F0F0F0F0F0F0F0FULL;
      encoded = (encoded | (encoded >> 4)) & 0x00FF00FF00FF00FFULL;
      encoded = (encoded | (encoded >> 8)) & 0x0000FFFF0000FFFFULL;
      return static_cast<TDecoded>(encoded | (encoded >> 16U));
    }
#endif

    //*************************************************************************
    /// Decode a span of data using the specified chunk type.
    ///\param source      The source encoded data to decode.
    ///\param destination The destination buffer for decoded data.
    ///\tparam TChunk     The chunk type for decoding (default: uint16_t).
    //*************************************************************************
    template <typename TChunk = typename private_manchester::encoded<uint_least8_t>::type>
    static ETL_CONSTEXPR14 void decode(etl::span<const uint_least8_t> encoded, etl::span<uint_least8_t> decoded)
    {
      typedef typename private_manchester::decoded<TChunk>::type TDecoded;
      typedef TChunk                                             TEncoded;

      ETL_ASSERT(decoded.size() * 2 >= encoded.size(), ETL_ERROR(manchester_invalid_size));
      ETL_ASSERT(encoded.size() % sizeof(TChunk) == 0, ETL_ERROR(manchester_invalid_size));

      size_t dest_index   = 0;
      size_t source_index = 0;
      for (size_t i = 0; i < encoded.size() / sizeof(TEncoded); ++i)
      {
        const TEncoded encoded_value = private_manchester::read_little_endian<TEncoded>(encoded, source_index);
        const TDecoded decoded_value = decode(encoded_value);
        private_manchester::write_little_endian<TDecoded>(decoded, dest_index, decoded_value);

        source_index += sizeof(TEncoded);
        dest_index += sizeof(TDecoded);
      }
    }

    //*************************************************************************
    // Validation functions
    //*************************************************************************

    //*************************************************************************
    /// Validate that a value contains valid Manchester encoded data.
    ///\param encoded The encoded value to validate.
    ///\return True if the value is valid Manchester encoding.
    //*************************************************************************
    template <typename TChunk>
    ETL_NODISCARD
    static ETL_CONSTEXPR14 typename etl::enable_if<private_manchester::is_decodable<TChunk>::value, bool>::type is_valid(TChunk encoded)
    {
      const TChunk mask = static_cast<TChunk>(0x5555555555555555ULL);
      return (((encoded ^ (encoded >> 1)) & mask) == mask);
    }

    //*************************************************************************
    /// Validate that a span contains valid Manchester encoded data.
    ///\param encoded The span of encoded data to validate.
    ///\return True if all data is valid Manchester encoding.
    //*************************************************************************
    ETL_NODISCARD
    static ETL_CONSTEXPR14 bool is_valid(etl::span<const uint_least8_t> encoded)
    {
      ETL_ASSERT(encoded.size() % sizeof(uint16_t) == 0, ETL_ERROR(manchester_invalid_size));

      for (size_t i = 0; i < encoded.size(); i += sizeof(uint16_t))
      {
        const uint16_t chunk = private_manchester::read_little_endian<uint16_t>(encoded, i);

        if (!is_valid<uint16_t>(chunk))
        {
          return false;
        }
      }

      return true;
    }
  };

  //***************************************************************************
  ///\ingroup manchester
  /// Manchester encoder using normal encoding (no inversion).
  //***************************************************************************
  typedef manchester_base<private_manchester::manchester_type_normal> manchester;

  //***************************************************************************
  ///\ingroup manchester
  /// Manchester encoder using inverted encoding.
  //***************************************************************************
  typedef manchester_base<private_manchester::manchester_type_inverted> manchester_inverted;

} // namespace etl

#endif
