/**
 *  Keys are stored detached from the blockchain for efficient lookup by client. Keys that never change for a group
 *  need no additional protection, the server can only omit them making the room unusable. Later, keys that change
 *  over time or by each new block in the config chain will need additional protection, to prevent a malicious server
 *  from rolling back to a previous key-configuration. Such keys or their fingerprints could be included in the
 *  related block to check by the relevant parties.
 *  Static key example: The pinned key does not need to be bound to the desired block because it never changes and it
 *  is encrypted exclusively by key-version (type) and resource-id.
 */
export class GroupConfigBlock {
  // All stored keys of block fields are short strings for optimal storage and cross-environment compatibility.
  public static TIMESTAMP = 'T'; // float
  public static RESOURCE_ID = 'R';
  public static PARENTS = 'P'; // map of parent block hashes and their block-id
  public static VERSION = 'V'; // reserved to support changing block data properties and cryptography
  public static REQUESTER = 'r'; // Id of the administrator who requested the block to be made. (None if the owner made it)
  // The block-id that the specific block will be/is stored under is incorporated into the block to act as a
  // tie-breaker in case a hash-collision occurs. These IDs are globally unique by backend-requirements, so they are
  // ideal for the job.
  public static BLOCK_ID = 'B';
  // Membership changes - the member assertions are the primary information the block-chain tracks. Any property
  // grant is only valid if the account is granted membership. This means revoking membership implicitly revokes
  // all granted properties. Membership is granted to account-ids which means the authenticity of these connections
  // rely on the trusted state of their root key.
  public static MEMBER_FLAGS = 'M';
  // Group-wide policy changes - not member but general policy settings are also stored as flags but in this separate
  // structure.
  public static GROUP_FLAGS = 'G';
  // aggregation
  public static REPLACES = 'X'; // Field in a new block that hold a replaces block.
  public static REPLACED_HASH = 'Y'; // Field only present in a replaces block.

  // These fields are never stored, they are used for caching and building the chain. To differentiate them from
  // stored filed they are prefixed with a low-dash.
  public static OWN_RAW = '_R';
  public static OWN_HASH = '_H';
  // chain processing
  public static CURRENT_PATH = '_P';
  public static PROC_MEMBER_FLAGS = '_M';
  public static PROC_GROUP_FLAGS = '_G';

  public static FLAG_MASK_SIZE = 15;
  public static FLAG_VALID_MASK = (1 << (GroupConfigBlock.FLAG_MASK_SIZE << 1)) - 1;
  public static FLAG_POSITIVE_MASK = (1 << GroupConfigBlock.FLAG_MASK_SIZE) - 1;
  public static FLAG_NEGATIVE_MASK =
    GroupConfigBlock.FLAG_VALID_MASK - GroupConfigBlock.FLAG_POSITIVE_MASK;
  public static OWN_SIGNATURE = '_S';

  /**
     * All flags have two bits, one asserting their addition and an other their clearing. Both shall not be set in
        a processed state to have a clear meaning and ease the usage. (use finalize_flags_*())
        To support the same operations in browser environment only 32-bit integers, and even in those the lowest
        30 bits shall be used.
     */
  public static create_flag_masks(index: number): [number, number] {
    // assert 0 <= index < GroupConfigBlock.FLAG_MASK_SIZE
    return [1 << index, 1 << (GroupConfigBlock.FLAG_MASK_SIZE + index)];
  }

  /**
     * Revocation is stronger than granting when blocks are being merged. To produce a stable and clear state, the
        grant-bits of all flags that have their revoke bit set are cleared.
     */
  public static finalize_flags_merge(flags: number): number {
    const negative_bits = flags & GroupConfigBlock.FLAG_NEGATIVE_MASK;
    return (
      negative_bits |
      (flags &
        GroupConfigBlock.FLAG_POSITIVE_MASK &
        ~(negative_bits >> GroupConfigBlock.FLAG_MASK_SIZE))
    );
  }

  /**
   * Merge an explicit flag-set onto an existing one.
   */
  public static finalize_flags_explicit(flags: number, previous_flags: number = 0): number {
    // explicit grant clears revocations
    previous_flags &= ~(
      (flags & GroupConfigBlock.FLAG_POSITIVE_MASK) <<
      GroupConfigBlock.FLAG_MASK_SIZE
    );
    // apply grants and new revokes
    previous_flags |= flags;
    return GroupConfigBlock.finalize_flags_merge(previous_flags);
  }
}

export class GroupConfigBlockFlags {
  // Control if the Nano is willing to serve anonymous requests aside from ad-hoc type URLs.
  public static ALLOW_ANONYMOUS = GroupConfigBlock.create_flag_masks(0)[0];
  public static ALLOW_ANONYMOUS_NEGATE = GroupConfigBlock.create_flag_masks(0)[1];

  // this mask will be used to validate flag assertions
  public static USED_MASK =
    GroupConfigBlockFlags.ALLOW_ANONYMOUS | GroupConfigBlockFlags.ALLOW_ANONYMOUS_NEGATE;
}

export class GroupConfigBlockMemberFlags {
  // Membership status - if this is revoked all other flags are not applicable.
  // Revocation must use the full FLAG_NEGATIVE_MASK and not only this flag's negate bit.
  public static ACTIVE = GroupConfigBlock.create_flag_masks(0)[0];
  public static ACTIVE_NEGATE = GroupConfigBlock.create_flag_masks(0)[1];

  // Can create new blocks in the current chain (by asking the owner's Nano).
  public static ADMINISTRATOR = GroupConfigBlock.create_flag_masks(1)[0];
  public static ADMINISTRATOR_NEGATE = GroupConfigBlock.create_flag_masks(1)[1];

  // Drive-server related flags.
  // create, overwrite, copy, rename, replace or delete any item
  public static DRIVE_WRITE = GroupConfigBlock.create_flag_masks(2)[0];
  public static DRIVE_WRITE_NEGATE = GroupConfigBlock.create_flag_masks(2)[1];
  // create or delete ad-hoc read-only share URLs for specific items
  public static DRIVE_ADHOC = GroupConfigBlock.create_flag_masks(3)[0];
  public static DRIVE_ADHOC_NEGATE = GroupConfigBlock.create_flag_masks(3)[1];
  // create new items
  public static DRIVE_UPLOAD = GroupConfigBlock.create_flag_masks(4)[0];
  public static DRIVE_UPLOAD_NEGATE = GroupConfigBlock.create_flag_masks(4)[1];

  // this mask will be used to validate flag assertions
  public static USED_MASK =
    GroupConfigBlockMemberFlags.ACTIVE |
    GroupConfigBlockMemberFlags.ACTIVE_NEGATE |
    GroupConfigBlockMemberFlags.ADMINISTRATOR |
    GroupConfigBlockMemberFlags.ADMINISTRATOR_NEGATE |
    GroupConfigBlockMemberFlags.DRIVE_WRITE |
    GroupConfigBlockMemberFlags.DRIVE_WRITE_NEGATE |
    GroupConfigBlockMemberFlags.DRIVE_ADHOC |
    GroupConfigBlockMemberFlags.DRIVE_ADHOC_NEGATE |
    GroupConfigBlockMemberFlags.DRIVE_UPLOAD |
    GroupConfigBlockMemberFlags.DRIVE_UPLOAD_NEGATE;
}
