#ifndef D868UVCALLSIGNDB_HH
#define D868UVCALLSIGNDB_HH

#include "callsigndb.hh"
#include "userdatabase.hh"
#include "d868uv_codeplug.hh"

/** Represents and encodes the binary format for the call-sign database within a AnyTone AT-D868UV
 * radio.
 *
 * <table>
 *  <tr><th colspan="3">Callsign database</th></tr>
 *  <tr><th>Start</th>    <th>Size</th>        <th>Content</th></tr>
 *  <tr><td>04000000</td> <td>max. 186a00</th> <td>Index of callsign entries. Follows the same
 *   weird format as @c D868UVCodeplug::contact_map_t. Sorted by ID. Empty entries set to
 *   0xffffffffffffffff.</td></tr>
 *  <tr><td>044c0000</td> <td>unknown</td>     <td>Database limits, see @c limits_t.</td></tr>
 *  <tr><td>04500000</td> <td>unknown</td>     <td>The actual DB entries, each entry is of
 *   variable size but shares the same header, see @c entry_t. Order arbitrary.
 *   Filled with 0x00.</td></tr>
 * </table>
 *
 * @ingroup d868uv */
class D868UVCallsignDB : public CallsignDB
{
  Q_OBJECT

public:
  /** Represents the header of an entry in the callsign database.
   * Each entry is of variable size. That is, every entry has a common header containing the
   * number, call-type etc. All strings, that is contact name, city, callsign, state, country and
   * comment are 0x00 terminated strings in a lists.
   *
   * Max length for name is 16, city is 15, callsign is 8, state is 16, country is 16 and
   * comment is 16, excluding terminating 0x00.
   */
  class EntryElement: public Codeplug::Element
  {
  public:
    /** Notification tones for callsign entry. */
    enum class RingTone {
      Off = 0,
      Tone = 1,
      Online = 2
    };
    /** Possible call types. */
    enum class CallType {
      Private = 0, Group = 1, All = 2
    };

  protected:
    /** Hidden constructor. */
    EntryElement(uint8_t *ptr, unsigned size);

  public:
    /** Constructor. */
    explicit EntryElement(uint8_t *ptr);

    void clear();

    /** Sets the call type. */
    virtual void setCallType(DMRContact::Type type);
    /** Sets the DMR ID number. */
    virtual void setNumber(unsigned num);
    /** Set/clear friend flag. */
    virtual void setFriendFlag(bool set);
    /** Sets the ring tone. */
    virtual void setRingTone(RingTone tone);
    /** Sets the entry content. */
    virtual void setContent(const QString &name, const QString &city, const QString &call,
                            const QString &state, const QString &country, const QString &comment);

    /** Constructs a database entry from the given user.
     * @returns The size of the entry. */
    virtual unsigned fromUser(const UserDatabase::User &user);

    /** Computes the size of the database entry for the given user. */
    static unsigned size(const UserDatabase::User &user);

  public:
    /** Some limits for the entry. */
    struct Limit: Element::Limit {
      static constexpr unsigned int headerLength()  { return  6; } ///< Header length (fixed).
      static constexpr unsigned int nameLength()    { return 16; } ///< Maximum name length.
      static constexpr unsigned int cityLength()    { return 15; } ///< Maximum city length.
      static constexpr unsigned int callLength()    { return  8; } ///< Maximum call length.
      static constexpr unsigned int stateLength()   { return 16; } ///< Maximum state length.
      static constexpr unsigned int countryLength() { return 16; } ///< Maximum country length.
      static constexpr unsigned int commentLength() { return  0; } ///< Maximum comment length.
      /** Maximum entry size. */
      static constexpr unsigned int totalLength()   {
        return headerLength() + nameLength()+1 + cityLength()+1 + callLength()+1 + stateLength()+1
            + countryLength()+1 + commentLength()+1; }
    };


  protected:
    /** Some internal offsets. */
    struct Offset: Element::Offset {
      /// @cond DO_NOT_DOCUMENT
      static constexpr unsigned int callType()   { return 0x0000; }
      static constexpr unsigned int number()     { return 0x0001; }
      static constexpr Bit          friendFlag() { return {0x0005, 4}; }
      static constexpr Bit          ringTone()   { return { 0x0005, 0}; }
      static constexpr unsigned int name()       { return Limit::headerLength(); }
      /// @endcond
    };
  };


  /** Represents a bank of call-sign DB entries. */
  class EntryBankElement: public Codeplug::Element
  {
  protected:
    /** Hidden constructor. */
    EntryBankElement(uint8_t *ptr, unsigned size);

  public:
    /** Constructor. */
    explicit EntryBankElement(uint8_t *ptr);

    /** The size of the element. */
    static constexpr unsigned int size() { return 0x000186a0; }

    void clear();

    /** Returns the i-th element of the bank. */
    uint8_t *entry(unsigned int i) const;
  };


  /** Same index entry used by the codeplug to map normal digital contacts to an contact index. Here
   * it maps to the byte offset within the database entries. */
  typedef D868UVCodeplug::ContactMapElement IndexEntryElement;

  /** Represents a bank of index entries. */
  class IndexBankElement: public Codeplug::Element
  {
  protected:
    /** Hidden constructor. */
    IndexBankElement(uint8_t *ptr, unsigned size);

  public:
    /** Constructor. */
    explicit IndexBankElement(uint8_t *ptr);

    /** The size of the element. */
    static constexpr unsigned int size() { return 0x0001f400; }

    void clear();

    /** Returns the i-th element of the bank. */
    uint8_t *entry(unsigned int i) const;
  };


  /** Stores some basic limits of the callsign db. */
  class LimitsElement: public Codeplug::Element
  {
  protected:
    /** Hidden constructor. */
    LimitsElement(uint8_t *ptr, unsigned size);

  public:
    /** Constructor. */
    LimitsElement(uint8_t *ptr);

    /** Size of the element. */
    static constexpr unsigned int size() { return 0x0010; }

    /** Resets the limits. */
    void clear();

    /** Returns the number of entries in the DB. */
    virtual unsigned count() const;
    /** Sets the number of entries. */
    virtual void setCount(unsigned count);

    /** Returns the end-of-db address. */
    virtual unsigned endOfDB() const;
    /** Sets the end-of-db address. */
    virtual void setEndOfDB(unsigned addr);
    /** Sets the total size of the DB (updated end-of-db address). */
    virtual void setTotalSize(unsigned size);

  protected:
    /** Some internal offsets. */
    struct Offset: Element::Offset {
      /// @cond DO_NOT_DOCUMENT
      static constexpr unsigned int count()   { return 0x0000; }
      static constexpr unsigned int endOfDB() { return 0x0004; }
      /// @endcond
    };
  };


public:
  /** Constructor, does not allocate any memory yet. */
  explicit D868UVCallsignDB(QObject *parent=nullptr);

  /** Tries to encode as many entries of the given user-database. */
  bool encode(UserDatabase *db, const Flags &selection=Flags(),
              const ErrorStack &err=ErrorStack());

public:
  /** Some limits for the call-sign DB. */
  struct Limit {
    /// Specifies the max number of entries in the DB.
    static constexpr unsigned int entries() { return 200000; }
  };

protected:
  /** Some internal used offsets within the DB. */
  struct Offset {
    /// @cond DO_NOT_DOCUMENT
    static constexpr unsigned int index()                { return 0x04000000; }
    static constexpr unsigned int betweenIndexBanks()    { return 0x00040000; }
    static constexpr unsigned int callsigns()            { return 0x04500000; }
    static constexpr unsigned int betweenCallsignBanks() { return 0x00040000; }
    static constexpr unsigned int limits()               { return 0x044C0000; }
    /// @endcond
  };
};

#endif // D868UVCALLSIGNDB_HH
