#ifndef __BS_WIDE_DATA_H__
#define __BS_WIDE_DATA_H__

/* NOTE: The WideData class is a C++-only feature, used internally
 * but not part of the external C API.
 *
 * C code uses arrays of unsigned ints and must account for
 * bit widths in an ad-hoc manner.
 */
#include "bs_target.h"
#include "bs_mem_defines.h"

#define NUM_WORDS(b) (unsigned int)((b+WORD_SIZE-1)/WORD_SIZE)

// forward declarations
class WideData;
extern void init_val(WideData& v, unsigned int width);
extern void init_val(unsigned long long /* unused */,
                     unsigned int /* unused */);
extern void dump_val(unsigned long long v, unsigned int width);
extern void dump_val(const WideData& v, unsigned int /* unused */);
extern void write_undet(bool* pValue, unsigned int width);
extern void write_undet(unsigned char* pValue, unsigned int width);
extern void write_undet(unsigned int* pValue, unsigned int width);
extern void write_undet(unsigned long long* pValue, unsigned int width);
extern void write_undet(WideData* pValue, unsigned int /* unused */);
extern void mask_high_bits(bool* pValue, unsigned int width);
extern void mask_high_bits(unsigned char* pValue, unsigned int width);
extern void mask_high_bits(unsigned int* pValue, unsigned int width);
extern void mask_high_bits(unsigned long long* pValue, unsigned int width);
extern void mask_high_bits(WideData* pValue, unsigned int /* unused */);
extern bool is_zero(unsigned char  v);
extern bool is_zero(unsigned int v);
extern bool is_zero(unsigned long long  v);
extern bool is_zero(const WideData& v);
extern bool is_bit_set(unsigned char v, unsigned int n);
extern bool is_bit_set(unsigned int v, unsigned int n);
extern bool is_bit_set(unsigned long long v, unsigned int n);
extern bool is_bit_set(const WideData& v, unsigned int n);
extern bool chunks_eq(unsigned char x, unsigned char y, unsigned int n, unsigned int width);
extern bool chunks_eq(unsigned int x, unsigned int y, unsigned int n, unsigned int width);
extern bool chunks_eq(unsigned long long x, unsigned long long y, unsigned int n, unsigned int width);
extern bool chunks_eq(const WideData& x, const WideData& y, unsigned int n, unsigned int width);
extern void xfer_chunk(unsigned char* pDest, unsigned char src, unsigned int n, unsigned int width);
extern void xfer_chunk(unsigned int* pDest, unsigned int src, unsigned int n, unsigned int width);
extern void xfer_chunk(unsigned long long* pDest, unsigned long long src, unsigned int n, unsigned int width);
extern void xfer_chunk(const WideData* pDest, const WideData& src, unsigned int n, unsigned int width);
extern WideData bs_wide_tmp(unsigned int width);

// The actual wide data implementation

class WideData
{
 private:
  unsigned int  nBits;
  unsigned int  nWords;

 // This is public only for the purpose of passing values to foreign C functions.
 // Please don't abuse it!
 public:
  unsigned int* data;

 public:
  // default constructor used when making arrays of WideData values.
  // The object is constructed with no data, use setSize() to allocate.
  WideData();

  // basic sized constructor -- initializes to 0xAAA...AAA if init is set
  explicit WideData(unsigned int bits, bool init = true);

  // special constructor used for literal values
  WideData(unsigned int bits, const unsigned int array[]);

  // special constructor for static-wide-data -- no initialization
  //  WideData(unsigned int * data, unsigned int bits);

  // special constructors used to promote non-wide values
  WideData(unsigned int bits, unsigned int value);
  WideData(unsigned int bits, unsigned long long value);

  // special constructor used to represent strings
  WideData(unsigned int bits, const char* str);

  // copy constructor
  WideData(const WideData& v);

  // copy assignment
  WideData& operator=(const WideData& v);

  // destructor
  ~WideData();

 public:
  void setSize(unsigned int width);
  unsigned int size() const { return nBits; }
  unsigned int numWords() const { return nWords; }
  unsigned int& operator[](unsigned int idx) const { return data[idx]; }
  unsigned char getByte(unsigned int byte_num) const;
  void setByte(unsigned int byte_num, unsigned char val);
  bool extract1(unsigned int idx) const;
  unsigned char extract8(unsigned int hi, unsigned int lo) const;
  unsigned int extract32(unsigned int hi, unsigned int lo) const;
  unsigned long long extract64(unsigned int hi, unsigned int lo) const;
  WideData extractWide(unsigned int hi, unsigned int lo) const;
  void wop_extractWide(unsigned int hi, unsigned int lo, WideData& result);
  void clear(unsigned int from = 0);
  void clear(unsigned int from, unsigned int to);
  void set(unsigned int from = 0);
  void set(unsigned int from, unsigned int to);
  void copy(const WideData& src);
  void copy(const WideData& src, unsigned int to);
  void write(unsigned int* buf, unsigned int size, unsigned int to);
  bool is_clear(unsigned int from = 0) const;
  bool is_clear(unsigned int from, unsigned int to) const;
  WideData& set_whole_word(unsigned int value, unsigned int word_idx);
  WideData& set_bits_in_word(unsigned char value, unsigned int word_idx, unsigned int offset, unsigned int nbits);
  WideData& set_bits_in_word(int value, unsigned int word_idx, unsigned int offset, unsigned int nbits);
  WideData& set_bits_in_word(unsigned int value, unsigned int word_idx, unsigned int offset, unsigned int nbits);
  WideData& build_concat(bool src, unsigned int loc, unsigned int nbits);
  WideData& build_concat(unsigned char src, unsigned int loc, unsigned int nbits);
  WideData& build_concat(int src, unsigned int loc, unsigned int nbits);
  WideData& build_concat(unsigned int src, unsigned int loc, unsigned int nbits);
  WideData& build_concat(unsigned long long src, unsigned int loc, unsigned int nbits);
  WideData& build_concat(const WideData& src, unsigned int loc, unsigned int nbits);
  unsigned int get_whole_word(unsigned int word_idx) const;
  unsigned char get_bits_in_word8(unsigned int word_idx, unsigned int offset, unsigned int nbits) const;
  unsigned int get_bits_in_word32(unsigned int word_idx, unsigned int offset, unsigned int nbits) const;

 public:
  void print_binary(Target* dest, unsigned int field_width=0) const;
  void print_hex(Target* dest, unsigned int field_width=0) const;
  void print_octal(Target* dest, unsigned int field_width=0) const;
  static unsigned int max_decimal_digits(unsigned int bits, bool is_signed);
  void print_decimal(Target* dest, unsigned int field_width, bool is_signed) const;

 public:
  friend void write_undet(WideData* pValue, unsigned int width);

  friend WideData operator&(const WideData&, const WideData&);
  friend WideData operator|(const WideData&, const WideData&);
  friend WideData operator^(const WideData&, const WideData&);
  friend WideData operator~(const WideData&);

  friend WideData operator>>(const WideData&, unsigned int);
  friend WideData operator<<(const WideData&, unsigned int);

  friend WideData operator-(const WideData&);

  friend WideData operator+(const WideData&, const WideData&);
  friend WideData operator-(const WideData&, const WideData&);
  friend WideData operator*(const WideData&, const WideData&);
  friend WideData operator/(const WideData&, const WideData&);
  friend WideData operator%(const WideData&, const WideData&);

  friend bool operator==(const WideData&, const WideData&);
  friend bool operator!=(const WideData&, const WideData&);
  friend bool operator<(const WideData&, const WideData&);
  friend bool operator>(const WideData&, const WideData&);
  friend bool operator<=(const WideData&, const WideData&);
  friend bool operator>=(const WideData&, const WideData&);
};

// inline-able class methods

inline WideData& WideData::set_whole_word(unsigned int value, unsigned int word_idx)
{
  data[word_idx] = value;
  return (*this);
}

inline WideData& WideData::set_bits_in_word(unsigned char value, unsigned int word_idx, unsigned int offset, unsigned int nbits)
{
  unsigned int mask = ((1 << nbits) - 1) << offset;
  data[word_idx] = (data[word_idx] & ~mask) | (((unsigned int)value << offset) & mask);
  return (*this);
}

inline WideData& WideData::set_bits_in_word(int value, unsigned int word_idx, unsigned int offset, unsigned int nbits)
{
  unsigned int mask = ((1 << nbits) - 1) << offset;
  data[word_idx] = (data[word_idx] & ~mask) | ((value << offset) & mask);
  return (*this);
}

inline WideData& WideData::set_bits_in_word(unsigned int value, unsigned int word_idx, unsigned int offset, unsigned int nbits)
{
  unsigned int mask = ((1 << nbits) - 1) << offset;
  data[word_idx] = (data[word_idx] & ~mask) | ((value << offset) & mask);
  return (*this);
}

inline unsigned int WideData::get_whole_word(unsigned int word_idx) const
{
  return data[word_idx];
}

inline unsigned char WideData::get_bits_in_word8(unsigned int word_idx, unsigned int offset, unsigned int nbits) const
{
  unsigned int mask = (1 << nbits) - 1;
  return (unsigned char)((data[word_idx] >> offset) & mask);
}

inline unsigned int WideData::get_bits_in_word32(unsigned int word_idx, unsigned int offset, unsigned int nbits) const
{
  unsigned int mask = (1 << nbits) - 1;
  return ((data[word_idx] >> offset) & mask);
}

// convenience typedef
typedef WideData tUWide;

// operators overloaded to work with WideData
WideData operator&(const WideData& v1, const WideData& v2);
WideData operator|(const WideData& v1, const WideData& v2);
WideData operator^(const WideData& v1, const WideData& v2);
WideData operator~(const WideData& v);

WideData operator>>(const WideData& value, unsigned int shift);
WideData operator<<(const WideData& value, unsigned int shift);

WideData operator-(const WideData& v);

WideData operator+(const WideData& v1, const WideData& v2);
WideData operator-(const WideData& v1, const WideData& v2);
WideData operator*(const WideData& v1, const WideData& v2);
WideData operator/(const WideData& v1, const WideData& v2);
WideData operator%(const WideData& v1, const WideData& v2);

bool operator==(const WideData& v1, const WideData& v2);
bool operator!=(const WideData& v1, const WideData& v2);
bool operator<(const WideData& v1, const WideData& v2);
bool operator>(const WideData& v1, const WideData& v2);
bool operator<=(const WideData& v1, const WideData& v2);
bool operator>=(const WideData& v1, const WideData& v2);

//operators for wide-data
void wop_and(const WideData& a, const WideData& b, WideData& c);// c = a & b
void wop_or(const WideData& a, const WideData& b, WideData& c); // c = a | b
void wop_xor(const WideData& a, const WideData& b, WideData& c);// c = a ^ b
void wop_inv(const WideData& a, WideData& c);                   // c = ~a
void wop_neg(const WideData& v, WideData& result);              // c = -a
void wop_srl(const WideData& a, unsigned int x, WideData& c);   // c = a >> x
void wop_sl(const WideData& a, unsigned int x, WideData& c);    // c = a << x
void wop_add(const WideData& a, const WideData& b, WideData& c);//c = a + b
void wop_sub(const WideData& a, const WideData& b, WideData& c);//c = a - b
void wop_mul(const WideData& a, const WideData& b, WideData& c);//c = a * b
void wop_quot(const WideData& a, const WideData& b, WideData& c);// c = a / b
void wop_rem(const WideData& a, const WideData& b, WideData& c);// c = a % b

//may-useless function calls
void wop_maskWide(unsigned int n, const WideData& val, WideData& result);

#endif /* __BS_WIDE_DATA_H__ */
