#ifndef _BITMASK_HPP
#define _BITMASK_HPP

/*! \file bitrange.hpp

  \brief Definition of the ::bitrange_t class

*/

//---------------------------------------------------------------------------
/// This class manages the offset and size of a value that occupies
/// a number of contiguous bits within some container - generally a byte array.
/// A special state - empty range (both offset and size are zeroes) - determines
/// the value as all bits of the container.
class bitrange_t
{
public:
  /// Constructor
  explicit bitrange_t(uint16 bit_ofs = 0, uint16 size_in_bits = 0);
  /// Initialize offset and size to given values
  inline void init(uint16 bit_ofs, uint16 size_in_bits);

  /// Make the bitrange empty
  inline void reset();
  /// Is the bitrange empty?
  inline bool empty() const;

  /// Get offset of 1st bit
  inline uint bitoff() const;
  /// Get size of the value in bits
  inline uint bitsize() const;
  /// Size of the value in bytes
  inline uint bytesize() const;
  /// Convert to mask of 64 bits
  inline uint64 mask64() const;

  /// Does have common bits with another bitrange?
  inline bool has_common(const bitrange_t &r) const;

  /// Apply mask to a bitrange
  /// \param subrange range *inside* the main bitrange to keep
  /// After this operation the main bitrange will be truncated
  /// to have only the bits that are specified by subrange.
  /// Example: [off=8,nbits=4], subrange[off=1,nbits=2] => [off=9,nbits=2]
  /// \return success
  inline bool apply_mask(const bitrange_t &subrange);

  /// Intersect two ranges
  inline void intersect(const bitrange_t &r);

  /// Create union of 2 ranges including the hole between them
  inline void create_union(const bitrange_t &r);

  /// Subtract a bitrange
  inline bool sub(const bitrange_t &r);

  /// Shift range down (left)
  inline void shift_down(uint cnt);
  /// Shift range up (right)
  inline void shift_up(uint cnt);

  /// Initialize bitrange_t with offset/size defined by given mask
  template <class T> inline void assign_max_nonzero(T mask);

  /// \name Extract
  /// Extract a value from 'src' according to the bitrange
  /// \param dst       vector the extracted value will be stored to
  /// \param src       source buffer
  /// \param src_size  size of source buffer
  /// \param is_mf     is Msb First? (TRUE-big endian, FALSE-little endian)
  ///@{
  inline bool extract(
        bytevec_t *dst,
        const void *src,
        size_t src_size,
        bool is_mf) const;
  inline bool extract(
        void *dst,
        size_t dst_size,
        const void *src,
        size_t src_size,
        bool is_mf) const;
  ///@}

  /// \name Inject
  /// Inject a value into 'dst' according to the bitrange
  /// \param dst       a buffer the value will be injected into
  /// \param dst_size  size of buffer
  /// \param src       source value
  /// \param is_mf     is Msb First? (TRUE-big endian, FALSE-little endian)
  ///@{
  inline bool inject(
        void *dst,
        size_t dst_size,
        const void *src,
        size_t src_size,
        bool is_mf) const;
  inline bool inject(
        void *dst,
        size_t dst_size,
        const bytevec_t &src,
        bool is_mf) const;
  ///@}

  DECLARE_COMPARISONS(bitrange_t);

private:
  uint16 offset;    ///< offset of 1st bit starting with the lowest bit
  uint16 nbits;     ///< size of the value in bits
};

//---------------------------------------------------------------------------
inline bitrange_t::bitrange_t(uint16 bit_ofs, uint16 size_in_bits)
  : offset(bit_ofs), nbits(size_in_bits)
{
}

//---------------------------------------------------------------------------
inline void bitrange_t::init(uint16 bit_ofs, uint16 size_in_bits)
{
  offset = bit_ofs;
  nbits  = size_in_bits;
}

//---------------------------------------------------------------------------
inline void bitrange_t::reset()
{
  init(0, 0);
}

//---------------------------------------------------------------------------
inline bool bitrange_t::empty() const
{
  return nbits == 0;
}

//---------------------------------------------------------------------------
inline uint bitrange_t::bitoff() const
{
  return offset;
}

//---------------------------------------------------------------------------
inline uint bitrange_t::bitsize() const
{
  return nbits;
}

//---------------------------------------------------------------------------
inline uint bitrange_t::bytesize() const
{
  return (nbits + 8-1) / 8;
}

//--------------------------------------------------------------------------
inline uint64 bitrange_t::mask64() const
{
  return empty() ? 0 : (left_shift(uint64(1), nbits)-1) << offset;
}

//--------------------------------------------------------------------------
inline bool bitrange_t::apply_mask(const bitrange_t &submask)
{
  if ( submask.bitoff() + submask.bitsize() > bitsize() )
    return false;
  init(bitoff() + submask.bitoff(), submask.bitsize());
  return true;
}

//--------------------------------------------------------------------------
inline void bitrange_t::intersect(const bitrange_t &r)
{
  uint16 e1 = offset + nbits;
  uint16 e2 = r.offset + r.nbits;
  uint16 e = qmin(e1, e2);
  uint16 s = qmax(offset, r.offset);
  if ( s > e )
  {
    s = 0;
    e = 0;
  }
  init(s, e-s);
}

//--------------------------------------------------------------------------
inline bool bitrange_t::has_common(const bitrange_t &r) const
{
  return interval::overlap(offset, nbits, r.offset, r.nbits);
}

//--------------------------------------------------------------------------
inline void bitrange_t::create_union(const bitrange_t &r)
{
  uint16 e1 = offset + nbits;
  uint16 e2 = r.offset + r.nbits;
  uint16 e = qmax(e1, e2);
  uint16 s = qmin(offset, r.offset);
  init(s, e-s);
}

//--------------------------------------------------------------------------
inline bool bitrange_t::sub(const bitrange_t &r)
{
  // r is in the middle of our bitrange, cannot handle this case
  // because it would require 2 bitranges :/
  uint16 end = offset + nbits;
  uint16 rend = r.offset + r.nbits;
  if ( r.offset > offset && rend < end )
    return false;
  if ( r.offset <= offset )
  {
    if ( rend > end )
    {
      reset();
    }
    else if ( rend > offset )
    {
      offset = rend;
      nbits = end - offset;
    }
  }
  else if ( r.offset < end )
  {
    nbits = r.offset - offset;
  }
  return true;
}

//--------------------------------------------------------------------------
inline int bitrange_t::compare(const bitrange_t &r) const
{
  if ( offset != r.offset )
    return offset < r.offset ? -1 : 1;
  if ( nbits != r.nbits )
    return nbits < r.nbits ? -1 : 1;
  return 0;
}

//--------------------------------------------------------------------------
inline void bitrange_t::shift_down(uint cnt)
{
  if ( cnt > offset )
  {
    cnt -= offset;
    offset = 0;
    if ( cnt > nbits )
      nbits = 0;
    else
      nbits -= cnt;
  }
  else
  {
    offset -= cnt;
  }
}

//--------------------------------------------------------------------------
inline void bitrange_t::shift_up(uint cnt)
{
  offset += cnt;
}

//--------------------------------------------------------------------------
template <class T> inline void bitrange_t::assign_max_nonzero(T mask)
{
  if ( mask == T(0) )
  {
    reset();
    return;
  }
  int i = 0;
  T bit = T(1);
  for ( i=0; i < sizeof(T)*8; ++i, bit <<= 1 )
    if ( (mask & bit) != 0 )
      break;
  offset = i;
  i = sizeof(T)*8 - 1;
  bit = left_shift(T(1), i);
  while ( i >= offset )
  {
    if ( (mask & bit) != 0 )
      break;
    --i;
    bit >>= 1;
  }
  nbits = i - offset + 1;
}

//--------------------------------------------------------------------------
#ifndef SWIG
/// \name Helper functions
/// Should not be called directly!
///@{
idaman bool ida_export bitrange_t_extract_using_bitrange(const bitrange_t *bm, void *dst, size_t dst_size, const void *src, size_t src_size, bool is_mf);
idaman bool ida_export bitrange_t_inject_using_bitrange(const bitrange_t *bm, void *dst, size_t dst_size, const void *src, size_t src_size, bool is_mf);
///@}
#else
#endif // SWIG

//--------------------------------------------------------------------------
inline bool bitrange_t::extract(
        void *dst,
        size_t dst_size,
        const void *src,
        size_t src_size,
        bool is_mf) const
{
  return bitrange_t_extract_using_bitrange(this, dst, dst_size, src, src_size, is_mf);
}

//--------------------------------------------------------------------------
inline bool bitrange_t::extract(
        bytevec_t *dst,
        const void *src,
        size_t src_size,
        bool is_mf) const
{
  size_t dst_size = empty() ? src_size : bytesize();
  dst->resize(dst_size);
  return bitrange_t_extract_using_bitrange(this,
                                           dst->begin(), dst_size,
                                           src, src_size,
                                           is_mf);
}

//--------------------------------------------------------------------------
inline bool bitrange_t::inject(
        void *dst,
        size_t dst_size,
        const void *src,
        size_t src_size,
        bool is_mf) const
{
  return bitrange_t_inject_using_bitrange(this, dst, dst_size, src, src_size, is_mf);
}

//--------------------------------------------------------------------------
inline bool bitrange_t::inject(
        void *dst,
        size_t dst_size,
        const bytevec_t &src,
        bool is_mf) const
{
  return bitrange_t_inject_using_bitrange(this,
                                          dst, dst_size,
                                          src.begin(), src.size(),
                                          is_mf);
}

#endif // define _BITMASK_HPP
