/**
 *  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
    );

}
