public static fromJsonTree(json: IVP): VpDocument {
        if (!json) {
            throw new Error('JSON Object is empty');
        }

        const result = new VpDocument();
        if (json[VpDocument.ID])
            result.id = json[VpDocument.ID];
        if (json[VpDocument.TYPE])
            result.type = json[VpDocument.TYPE];
        if (json[VpDocument.ISSUER])
            result.issuer = Issuer.fromJsonTree(json[VcDocument.ISSUER]);
        if (json[VpDocument.ISSUANCE_DATE])
            result.issuanceDate = TimestampUtils.fromJson(json[VcDocument.ISSUANCE_DATE]);

        const jsonVerifiableCredential = json[VpDocument.VERIFIABLE_CREDENTIAL];
        if (jsonVerifiableCredential) {
            if (Array.isArray(jsonVerifiableCredential)) {
                for (let i = 0; i < jsonVerifiableCredential.length; i++) {
                    const item = jsonVerifiableCredential[i];
                    const credential = VcDocument.fromJsonTree(item);
                    result.addVerifiableCredential(credential);
                }
            } else {
                const credential = VcDocument.fromJsonTree(jsonVerifiableCredential);
                result.addVerifiableCredential(credential);
            }
        }
        const context = json[VcDocument.CONTEXT];
        if (context) {
            if (Array.isArray(context)) {
                for (let i = 0; i < context.length; i++) {
                    const item = context[i];
                    result.addContext(item);
                }
            } else {
                result.addContext(context);
            }

            result.context = context.slice();
        }

        result.proof = json[VpDocument.PROOF] || null;

        return result;
    }


public toCredentialHash(): string {
        const map = {};
        map[VpDocument.ID] = this.id;
        map[VpDocument.TYPE] = this.type;
        if (this.credentials) {
            map[VpDocument.VERIFIABLE_CREDENTIAL] = this.credentials.map(e => e.toCredentialHash());
        }
        const json: string = JSON.stringify(map);
        const hash: Uint8Array = Hashing.sha256.digest(json);
        return Hashing.base58.encode(hash);
    }


public proofFromJson(json: any): void {
        this.setProof(json[VpDocument.PROOF]);
    }


public getDocument(): IVP {
        return this.toJsonTree();
    }


async () => {
    await i18next.changeLanguage('en');

    if (keyring.getAccounts().length === 0) {
      keyring.loadAll({ isDevelopment: true, store: new MemoryStore() });
    }
  }


() => {
    accountsPage = new AccountsPage();
    accountsPage.clearAccounts();
  }


() => {
    beforeEach(() => {
      accountsPage.render([]);
    });

    it('shows sort-by controls', async () => {
      await accountsPage.reverseSortingOrder();
    });

    it('shows a table', async () => {
      const accountsTable = await accountsPage.getTable();

      expect(accountsTable).not.toBeNull();
    });

    it('the accounts table contains no account rows', async () => {
      const accountRows = await accountsPage.getAccountRows();

      expect(accountRows).toHaveLength(0);
    });

    it('the accounts table contains a message about no accounts available', async () => {
      const noAccountsMessage = 'You don\'t have any accounts. Some features are currently hidden and will only become available once you have accounts.';
      const accountsTable = await accountsPage.getTable();

      await accountsTable.assertText(noAccountsMessage);
    });

    it('no summary is displayed', () => {
      const summaries = screen.queryAllByTestId(/card-summary:total \w+/i);

      expect(summaries).toHaveLength(0);
    });
  }


() => {
      accountsPage.render([]);
    }


async () => {
      await accountsPage.reverseSortingOrder();
    }


async () => {
      const accountsTable = await accountsPage.getTable();

      expect(accountsTable).not.toBeNull();
    }


async () => {
      const accountRows = await accountsPage.getAccountRows();

      expect(accountRows).toHaveLength(0);
    }


async () => {
      const noAccountsMessage = 'You don\'t have any accounts. Some features are currently hidden and will only become available once you have accounts.';
      const accountsTable = await accountsPage.getTable();

      await accountsTable.assertText(noAccountsMessage);
    }


() => {
      const summaries = screen.queryAllByTestId(/card-summary:total \w+/i);

      expect(summaries).toHaveLength(0);
    }


async () => {
      accountsPage.renderDefaultAccounts(2);
      const accountRows = await accountsPage.getAccountRows();

      expect(accountRows).toHaveLength(2);
    }


async () => {
      accountsPage.renderAccountsWithDefaultAddresses(
        anAccountWithBalance({ freeBalance: balance(500) }),
        anAccountWithBalance({ freeBalance: balance(200), reservedBalance: balance(150) })
      );

      const rows = await accountsPage.getAccountRows();

      await rows[0].assertBalancesTotal(balance(500));
      await rows[1].assertBalancesTotal(balance(350));
    }


async () => {
      accountsPage.renderAccountsWithDefaultAddresses(
        anAccountWithBalance({ freeBalance: balance(500), lockedBalance: balance(30) }),
        anAccountWithBalance({ availableBalance: balance(50), freeBalance: balance(200), reservedBalance: balance(150) })
      );

      const rows = await accountsPage.getAccountRows();

      await rows[0].assertBalancesDetails([
        { amount: balance(0), name: 'transferrable' },
        { amount: balance(30), name: 'locked' }]);
      await rows[1].assertBalancesDetails([
        { amount: balance(50), name: 'transferrable' },
        { amount: balance(150), name: 'reserved' }]);
    }


async () => {
      accountsPage.renderAccountsWithDefaultAddresses(
        anAccountWithMeta({ isInjected: true, name: 'ALICE', whenCreated: 200 }),
        anAccountWithMeta({ name: 'ALICE_CHILD', parentAddress: alice, whenCreated: 300 })
      );

      const accountRows = await accountsPage.getAccountRows();

      expect(accountRows).toHaveLength(2);
      await accountRows[1].assertParentAccountName('ALICE');
    }


async () => {
      accountsPage.renderDefaultAccounts(1);
      const accountsTable = await accountsPage.getTable();

      accountsTable.assertColumnNotExist('parent');
      accountsTable.assertColumnExists('type');
    }


async () => {
      accountsPage.renderAccountsForAddresses(
        alice
      );
      const accountRows = await accountsPage.getAccountRows();

      expect(accountRows).toHaveLength(1);
      const aliceShortAddress = toShortAddress(alice);

      await accountRows[0].assertShortAddress(aliceShortAddress);
    }


async () => {
      accountsPage.renderDefaultAccounts(1);
      const rows = await accountsPage.getAccountRows();

      await rows[0].assertTags('no tags');
    }


async () => {
      accountsPage.renderAccountsWithDefaultAddresses(
        anAccountWithInfo({ tags: ['my tag', 'Super Tag'] })
      );

      const rows = await accountsPage.getAccountRows();

      await rows[0].assertTags('my tagSuper Tag');
    }


async () => {
      accountsPage.renderDefaultAccounts(1);
      const row = (await accountsPage.getAccountRows())[0];

      expect(row.detailsRow).toHaveClass('isCollapsed');

      await row.expand();

      expect(row.detailsRow).toHaveClass('isExpanded');
    }


() => {
      accountsPage.renderAccountsWithDefaultAddresses(
        anAccountWithBalance({ freeBalance: balance(500) }),
        anAccountWithBalance({ freeBalance: balance(200), reservedBalance: balance(150) })
      );

      const summaries = screen.queryAllByTestId(/card-summary:total \w+/i);

      expect(summaries).not.toHaveLength(0);
    }


async () => {
      accountsPage.renderAccountsWithDefaultAddresses(
        anAccountWithBalance({ freeBalance: balance(500) }),
        anAccountWithBalance({ freeBalance: balance(200), reservedBalance: balance(150) })
      );

      const summary = await screen.findByTestId(/card-summary:(total )?balance/i);

      expect(summary).toHaveTextContent(showBalance(500 + 200 + 150));
    }


async () => {
      accountsPage.renderAccountsWithDefaultAddresses(
        anAccountWithBalance({ availableBalance: balance(400) }),
        anAccountWithBalance({ availableBalance: balance(600) })
      );

      const summary = await screen.findByTestId(/card-summary:(total )?transferrable/i);

      expect(summary).toHaveTextContent(showBalance(400 + 600));
    }


async () => {
      accountsPage.renderAccountsWithDefaultAddresses(
        anAccountWithBalance({ lockedBalance: balance(400) }),
        anAccountWithBalance({ lockedBalance: balance(600) })
      );

      const summary = await screen.findByTestId(/card-summary:(total )?locked/i);

      expect(summary).toHaveTextContent(showBalance(400 + 600));
    }


async () => {
      accountsPage.renderAccountsWithDefaultAddresses(
        anAccountWithStaking({ stakingLedger: ledger(balance(70)) }),
        anAccountWithStaking({ stakingLedger: ledger(balance(20)) })
      );

      const summary = await screen.findByTestId(/card-summary:(total )?bonded/i);

      expect(summary).toHaveTextContent(showBalance(70 + 20));
    }


async () => {
      accountsPage.renderAccountsWithDefaultAddresses(
        anAccountWithStaking({
          unlocking: [
            {
              remainingEras: new BN('1000000000'),
              value: balance(200)
            },
            {
              remainingEras: new BN('2000000000'),
              value: balance(300)
            },
            {
              remainingEras: new BN('3000000000'),
              value: balance(400)
            }
          ]
        }),
        anAccountWithStaking({
          unlocking: [
            {
              remainingEras: new BN('1000000000'),
              value: balance(100)
            },
            {
              remainingEras: new BN('2000000000'),
              value: balance(200)
            },
            {
              remainingEras: new BN('3000000000'),
              value: balance(300)
            }
          ]
        })
      );

      const summary = await screen.findByTestId(/card-summary:(total )?unbonding/i);

      expect(summary).toHaveTextContent(showBalance(200 + 300 + 400 + 100 + 200 + 300));
    }


async () => {
      accountsPage.renderAccountsWithDefaultAddresses(
        anAccountWithStaking({ redeemable: balance(4000) }),
        anAccountWithStaking({ redeemable: balance(5000) })
      );

      const summary = await screen.findByTestId(/card-summary:(total )?redeemable/i);

      expect(summary).toHaveTextContent(showBalance(4000 + 5000));
    }


async () => {
      accountsPage.renderAccountsWithDefaultAddresses(
        anAccountWithBalanceAndMeta({ freeBalance: balance(1) }, { whenCreated: 200 }),
        anAccountWithBalanceAndMeta({ freeBalance: balance(2) }, { whenCreated: 300 }),
        anAccountWithBalanceAndMeta({ freeBalance: balance(3) }, { whenCreated: 100 })
      );
      expect(await accountsPage.getCurrentSortCategory()).toHaveTextContent('date');

      const accountsTable = await accountsPage.getTable();

      await accountsTable.assertRowsOrder([3, 1, 2]);
    }


() => {
      let accountsTable: Table;

      beforeEach(async () => {
        accountsPage.renderAccountsWithDefaultAddresses(
          anAccountWithBalanceAndMeta({ freeBalance: balance(1) }, { isInjected: true, name: 'bbb', whenCreated: 200 }),
          anAccountWithBalanceAndMeta({ freeBalance: balance(2) }, {
            hardwareType: 'ledger',
            isHardware: true,
            name: 'bb',
            parentAddress: alice,
            whenCreated: 300
          }),
          anAccountWithBalanceAndMeta({ freeBalance: balance(3) }, { isInjected: true, name: 'aaa', whenCreated: 100 })
        );

        accountsTable = await accountsPage.getTable();
      });

      it('changes default dropdown value', async () => {
        await accountsPage.sortBy('balances');
        expect(await accountsPage.getCurrentSortCategory())
          .toHaveTextContent('balances');
      });

      it('sorts by parent if asked', async () => {
        await accountsPage.sortBy('parent');
        await accountsTable.assertRowsOrder([3, 1, 2]);
      });

      it('sorts by name if asked', async () => {
        await accountsPage.sortBy('name');
        await accountsTable.assertRowsOrder([3, 2, 1]);
      });

      it('sorts by date if asked', async () => {
        await accountsPage.sortBy('date');
        await accountsTable.assertRowsOrder([3, 1, 2]);
      });

      it('sorts by balances if asked', async () => {
        await accountsPage.sortBy('balances');
        await accountsTable.assertRowsOrder([1, 2, 3]);
      });

      it('sorts by type if asked', async () => {
        await accountsPage.sortBy('type');
        await accountsTable.assertRowsOrder([3, 1, 2]);
      });

      it('implements stable sort', async () => {
        // Notice that sorting by 'type' results in different order
        // depending on the previous state.
        await accountsPage.sortBy('name');
        await accountsTable.assertRowsOrder([3, 2, 1]);
        await accountsPage.sortBy('type');
        await accountsTable.assertRowsOrder([3, 1, 2]);
        await accountsPage.sortBy('balances');
        await accountsTable.assertRowsOrder([1, 2, 3]);
        await accountsPage.sortBy('type');
        await accountsTable.assertRowsOrder([1, 3, 2]);
      });

      it('respects reverse button', async () => {
        await accountsPage.sortBy('name');
        await accountsTable.assertRowsOrder([3, 2, 1]);
        await accountsPage.sortBy('balances');
        await accountsTable.assertRowsOrder([1, 2, 3]);
        await accountsPage.reverseSortingOrder();
        await accountsTable.assertRowsOrder([3, 2, 1]);
        await accountsPage.sortBy('name');
        await accountsTable.assertRowsOrder([1, 2, 3]);
      });
    }


async () => {
        accountsPage.renderAccountsWithDefaultAddresses(
          anAccountWithBalanceAndMeta({ freeBalance: balance(1) }, { isInjected: true, name: 'bbb', whenCreated: 200 }),
          anAccountWithBalanceAndMeta({ freeBalance: balance(2) }, {
            hardwareType: 'ledger',
            isHardware: true,
            name: 'bb',
            parentAddress: alice,
            whenCreated: 300
          }),
          anAccountWithBalanceAndMeta({ freeBalance: balance(3) }, { isInjected: true, name: 'aaa', whenCreated: 100 })
        );

        accountsTable = await accountsPage.getTable();
      }


async () => {
        await accountsPage.sortBy('balances');
        expect(await accountsPage.getCurrentSortCategory())
          .toHaveTextContent('balances');
      }


async () => {
        await accountsPage.sortBy('parent');
        await accountsTable.assertRowsOrder([3, 1, 2]);
      }


async () => {
        await accountsPage.sortBy('name');
        await accountsTable.assertRowsOrder([3, 2, 1]);
      }


async () => {
        await accountsPage.sortBy('date');
        await accountsTable.assertRowsOrder([3, 1, 2]);
      }


async () => {
        await accountsPage.sortBy('balances');
        await accountsTable.assertRowsOrder([1, 2, 3]);
      }


async () => {
        await accountsPage.sortBy('type');
        await accountsTable.assertRowsOrder([3, 1, 2]);
      }


async () => {
        // Notice that sorting by 'type' results in different order
        // depending on the previous state.
        await accountsPage.sortBy('name');
        await accountsTable.assertRowsOrder([3, 2, 1]);
        await accountsPage.sortBy('type');
        await accountsTable.assertRowsOrder([3, 1, 2]);
        await accountsPage.sortBy('balances');
        await accountsTable.assertRowsOrder([1, 2, 3]);
        await accountsPage.sortBy('type');
        await accountsTable.assertRowsOrder([1, 3, 2]);
      }


async () => {
        await accountsPage.sortBy('name');
        await accountsTable.assertRowsOrder([3, 2, 1]);
        await accountsPage.sortBy('balances');
        await accountsTable.assertRowsOrder([1, 2, 3]);
        await accountsPage.reverseSortingOrder();
        await accountsTable.assertRowsOrder([3, 2, 1]);
        await accountsPage.sortBy('name');
        await accountsTable.assertRowsOrder([1, 2, 3]);
      }


(control: AbstractControl) => {
            const ctrlValue: string = control.value;
            if (ctrlValue) {
                return validationMethods.isValidIp(ctrlValue) ?
                    null : { [ValidatorEnum.VALID_IP]: true };
            }
            return null;
        }


(control: AbstractControl) => {
            const ctrlValue: string = control.value;
            if (ctrlValue) {
                if (ctrlValue.length !== ctrlValue.toString().trim().length) {
                    return {
                        [ValidatorEnum.WHITESPACE]: true
                    };
                }
            }
            return null;
        }


(control: AbstractControl) => {
            const ctrlValue: string = control.value;
            if (ctrlValue) {
                if (ctrlValue.substr(ctrlValue.length - 1) === '/') {
                    return {
                        [ValidatorEnum.TRAILING_SLASH]: true
                    };
                }
            }
            return null;
        }


(control: AbstractControl) => {
            const ctrlValue: string = control.value;
            if (ctrlValue) {
                const ips: Array<string> = ctrlValue.split(',');
                return ips
                    .map(ipStr => validationMethods.isValidIp(ipStr))
                    .reduce((a, b) => a && b, true) ? null : { [ValidatorEnum.VALID_IP]: true };
            }
            return null;
        }


ipStr => validationMethods.isValidIp(ipStr)


(a, b) => a && b


(control: AbstractControl) => {
            const ctrlValue: string = control.value;
            if (ctrlValue) {
                return validationMethods.isValidFqdn(ctrlValue) ?
                    null : { [ValidatorEnum.VALID_FQDN]: true };
            }
            return null;
        }


(control: AbstractControl) => {
            const ctrlValue: string = control.value;
            if (ctrlValue) {
                if (validationMethods.isValidIp(ctrlValue) ||
                    validationMethods.isValidFqdn(ctrlValue)) {
                    return null;
                }

                return {
                    [ValidatorEnum.VALID_IP_OR_FQDN]: true
                };
            }
        }


(control: AbstractControl) => {
            const ctrlValue: string = control.value;
            if (ctrlValue) {
                if (isIp.v6(ctrlValue) ||
                    validationMethods.isValidFqdn(ctrlValue)) {
                    return null;
                }

                return {
                    [ValidatorEnum.VALID_IP_OR_FQDN]: true
                };
            }
        }


(control: AbstractControl) => {
            const ctrlValue: string = control.value;
            if (ctrlValue) {
                if (validationMethods.isValidIpWithHttpsProtocol(ctrlValue) ||
                    validationMethods.isValidFqdnWithHttpsProtocol(ctrlValue)) {
                    return null;
                }

                return {
                    [ValidatorEnum.VALID_IP_OR_FQDN]: true
                };
            }
        }


(control: AbstractControl) => {
            const ctrlValue: string = control.value;
            if (ctrlValue) {
                if (validationMethods.isValidIpv6WithHttpsProtocol(ctrlValue) ||
                    validationMethods.isValidFqdnWithHttpsProtocol(ctrlValue)) {
                    return null;
                }

                return {
                    [ValidatorEnum.VALID_IP_OR_FQDN]: true
                };
            }
        }


(control: AbstractControl) => {
            const ctrlValue: string = control.value;
            if (ctrlValue) {
                if (validationMethods.isStringWithoutUrlFragment(ctrlValue)) {
                    return {
                        [ValidatorEnum.INCLUDES_URL_FRAGMENT]: true
                    };
                }
                return null;
            }
        }


(control: AbstractControl) => {
            const ctrlValue: string = control.value;
            if (ctrlValue) {
                if (validationMethods.isStringWithoutQueryParams(ctrlValue)) {
                    return {
                        [ValidatorEnum.INCLUDES_QUERY_PARAMS]: true
                    };
                }
                return null;
            }
        }


(control: AbstractControl) => {
            const ctrlValue: string = control.value;
            if (ctrlValue) {
                if (validationMethods.isNumericOnly(ctrlValue)) {
                    return null;
                }

                return {
                    [ValidatorEnum.VALID_PORT]: true
                };
            }
            return null;
        }


(control: AbstractControl) => {
            const ctrlValue: string = control.value;
            if (ctrlValue) {
                if (validationMethods.isValidIp(ctrlValue)) {
                    const ipLong = validationMethods.ipAddrToLong(ctrlValue);
                    const subnetMask = subnetMaskCtrl.value;
                    if (validationMethods.isValidIp(subnetMask)) {
                        const subnetMaskLong = validationMethods.ipAddrToLong(subnetMask);

                        if (validationMethods.subnetMaskInRange(ipLong, subnetMaskLong)) {
                            return null;
                        }

                        return {
                            [ValidatorEnum.SUBNET_IN_RANGE]: true
                        };
                    }
                    return null;
                }
                return {
                    [ValidatorEnum.VALID_IP]: true
                };
            }
            return null;
        }


(control: AbstractControl) => {
            const ctrlValue: string = control.value;
            if (ctrlValue) {
                if (ctrlValue.length !== ctrlValue.toString().trim().length) {
                    return {
                        [ValidatorEnum.WHITESPACE]: true
                    };
                }
                return validationMethods.isValidClustername(ctrlValue) ?
                    null : { [ValidatorEnum.VALID_CLUSTER_NAME]: true };
            }
            return null;
        }


(control: AbstractControl) => {
            const ctrlValue: string = control.value;
            if (ctrlValue) {
                if (ctrlValue.length !== ctrlValue.toString().trim().length) {
                    return {
                        [ValidatorEnum.WHITESPACE]: true
                    };
                }
                if (ctrlValue.length > 63) {
                    return {
                        [ValidatorEnum.MAX_LEN]: true
                    }
                }
                return validationMethods.isValidLabelOrAnnotation(ctrlValue) ?
                    null : { [ValidatorEnum.VALID_CLUSTER_NAME]: true };
            }
            return null;
        }


(control: AbstractControl) => {
            const ctrlValue: string = control.value;
            if (ctrlValue) {
                if (validationMethods.isValidIp(ctrlValue)) {
                    const ipLong = validationMethods.ipAddrToLong(ctrlValue);
                    const subnetMask = subnetMaskCtrl.value;
                    const subnet = subnetCtrl.value;
                    if (validationMethods.isValidIp(subnet) &&
                        validationMethods.isValidIp(subnetMask)) {
                        const subnetLong = validationMethods.ipAddrToLong(subnet);
                        const subnetMaskLong = validationMethods.ipAddrToLong(subnetMask);

                        if (validationMethods.isIpInSubnet(ipLong, subnetLong, subnetMaskLong)) {
                            return null;
                        }

                        return {
                            [ValidatorEnum.IP_IN_SUBNET_RANGE]: true
                        };
                    }
                    return null;
                }
                return {
                    [ValidatorEnum.VALID_IP]: true
                };
            }
            return null;
        }


(control: AbstractControl) => {
            const ipv4: string = control.value;
            if (ipv4) {
                if (validationMethods.isValidIp(ipv4)) {
                    let netmask = null;
                    try {
                        netmask = new Netmask(cidr);
                    } catch (e) {
                        // the netmask may not have been initialized yet, we don't validate
                        return null;
                    }

                    if (!netmask.contains(ipv4)) {
                        return {
                            [ValidatorEnum.IP_IN_SUBNET_RANGE]: true
                        };
                    }

                    return null;
                }
                return {
                    [ValidatorEnum.VALID_IP]: true
                };
            }
            return null;
        }


(control: AbstractControl) => {
            const ctrlValue: string = control.value;
            if (ctrlValue) {
                if (validationMethods.isValidIp(ctrlValue)) {
                    const ipLong = validationMethods.ipAddrToLong(ctrlValue);
                    const subnetMask = subnetMaskCtrl.value;
                    const subnet = subnetCtrl.value;
                    if (validationMethods.isValidIp(subnet) &&
                        validationMethods.isValidIp(subnetMask)) {
                        const subnetLong = validationMethods.ipAddrToLong(subnet);
                        const subnetMaskLong = validationMethods.ipAddrToLong(subnetMask);

                        if (validationMethods.ipOverlapSubnet(ipLong, subnetLong, subnetMaskLong)) {
                            return {
                                [ValidatorEnum.IP_NOT_IN_SUBNET_RANGE]: true
                            };
                        }
                    }

                    return null;
                }
                return {
                    [ValidatorEnum.VALID_IP]: true
                };
            }
            return null;
        }


(control: AbstractControl) => {
            const ctrlValue: string = control.value;
            const compareValue: string = compareCidrCtrl.value;
            if (ctrlValue) {
                if (validationMethods.isValidCidr(ctrlValue) && validationMethods.isValidCidr(compareValue)) {
                    if (validationMethods.isCidrOverlapCidr(ctrlValue, compareValue)) {
                        return {
                            [ValidatorEnum.CIDR_OVERLAP_CIDR]: true
                        };
                    }

                    return null;
                }

            }
            return null;
        }


(control: AbstractControl) => {
            const ctrlValue: string = control.value;
            const compareValue: string = compareCidrCtrl.value;
            if (ctrlValue) {
                if (validationMethods.isValidCidr(ctrlValue) && validationMethods.isValidCidr(compareValue)) {
                    if (!validationMethods.isCidrWithinCidr(ctrlValue, compareValue)) {
                        return {
                            [ValidatorEnum.CIDR_WITHIN_CIDR]: true
                        };
                    }
                    return null;
                }

            }
            return null;
        }


(control: AbstractControl) => {
            const ctrlValue: string = control.value;
            if (ctrlValue) {
                const ctrlValueList = ctrlValue.split('/');
                if (ctrlValueList.length === 2) {
                    if (!validationMethods.isValidIp(ctrlValueList[0])) {
                        return {
                            [ValidatorEnum.VALID_IP]: true
                        }
                    }
                    return ctrlValueList[1] && +ctrlValueList[1] >= 0 && +ctrlValueList[1] < 32 ? null : {
                        [ValidatorEnum.VALID_IP]: true
                    }
                } else {
                    return {
                        [ValidatorEnum.VALID_IP]: true
                    };
                }
            }
            return null;
        }


(control: AbstractControl) => {
            const ctrlValue: string = control.value;
            if (ctrlValue) {
                const ctrlValueList = ctrlValue.split('/');
                if (ctrlValueList.length === 2) {
                    if (!isIp.v6(ctrlValueList[0])) {
                        return {
                            [ValidatorEnum.VALID_IP]: true
                        }
                    }
                    return ctrlValueList[1] && +ctrlValueList[1] >= 0 && +ctrlValueList[1] < 128 ? null : {
                        [ValidatorEnum.VALID_IP]: true
                    }
                } else {
                    return {
                        [ValidatorEnum.VALID_IP]: true
                    };
                }
            }
            return null;
        }


(control: AbstractControl) => {
            if (control.value) {
                const currentControlIp = control.value;
                for (const ipAddr of otherControls) {
                    if (currentControlIp === ipAddr.value) {
                        return { [ValidatorEnum.NETWORKING_IP_UNIQUE]: true };
                    }
                }
            }
        }


(control: AbstractControl) => {
            const ctrlValue: string = control.value;
            const passwordValue = passwordCtrl.value;
            if (passwordCtrl) {
                if (ctrlValue) {
                    if (ctrlValue !== passwordValue) {
                        return {
                            [ValidatorEnum.CONFIRM_PASSWORD]: true
                        };
                    } else {
                        return null;
                    }
                } else {
                    return {
                        [ValidatorEnum.REQUIRED]: true
                    };
                }
            }
            return null;
        }


(control: AbstractControl) => {
            const ctrlValue: string = control.value;
            if (ctrlValue) {
                if (validationMethods.isNumericOnly(ctrlValue)) {
                    return null;
                }
                return { [ValidatorEnum.NUMERIC_ONLY]: true };
            }
            return null;
        }


ip => validationMethods.isValidIp(ip) || validationMethods.isValidFqdn(ip)


(control: AbstractControl) => {
            const ctrlValue: string = control.value;
            if (ctrlValue) {
                if (!this.commaSeparatedIpOrFqdn(ctrlValue)) {
                    return { [ValidatorEnum.VALID_IP_OR_FQDN]: true };
                }
            }
            return null;
        }


(control: AbstractControl) => {
            const ctrlValue: number = control.value;
            if (ctrlValue === null) {
                return null;
            }
            if (typeof ctrlValue !== 'number' || ctrlValue < 1) {
                return { [ValidatorEnum.GREATER_THAN_ZERO]: true };
            }
            return null;
        }


(control: AbstractControl) => {
            if (control.value) {
                const currentAz = control.value;
                for (const az of otherControls) {
                    if (currentAz === az.value) {
                        return { [ValidatorEnum.AVAILABILITY_ZONE_UNIQUE]: true };
                    }
                }
            }
        }


(control: AbstractControl) => {
            const ctrlValue: string = control.value;
            if (ctrlValue && !XRegExp('^[\\pL-_.()\\w]+$').test(ctrlValue)) {
                return { [ValidatorEnum.VALID_RESOURCE_GROUP_NAME]: true };
            }
            return null;
        }


(control: AbstractControl) => {
            const ctrlValue: string = control.value;
            if (ctrlValue && resourceGroups.find(x => x.name === ctrlValue) != null) {
                return { [ValidatorEnum.UNIQUE_RESOURCE_GROUP_NAME]: true };
            }
            return null;
        }


x => x.name === ctrlValue


(control: AbstractControl) => {
            const ctrlValue: string = control.value;
            if (ctrlValue) {
                if (!XRegExp('^(((\\w+)(,\\w+)+)|(\\w+))$').test(ctrlValue)) {
                    return {
                        [ValidatorEnum.COMMA_SEPARATED_WORDS]: true
                    };
                }
            }
        }


(control: AbstractControl) => {
            const ctrlValue: string = control.value;
            if (ctrlValue && !XRegExp('^https?:\/\/').test(ctrlValue)) {
                return { [ValidatorEnum.HTTP_OR_HTTPS]: true };
            }
            return null;
        }


(control: AbstractControl) => {
            const inputVal = ipCtrl.value;
            if (inputVal) {
                if (!validationMethods.isValidIp(inputVal) && !(validationMethods.isValidFqdn(inputVal))) {
                    return { [ValidatorEnum.VALID_IP_OR_FQDN]: true };
                }
            } else {
                return { [ValidatorEnum.REQUIRED]: true };
            }

            if (control.value) {
                if (!validationMethods.isNumericOnly(control.value)) {
                    return { [ValidatorEnum.VALID_PORT]: true };
                }
                return null;
            }
            return { [ValidatorEnum.REQUIRED]: true };
        }


(control: AbstractControl) => {
            const inputVal = ipCtrl.value;
            if (inputVal) {
                if (!isIp.v6(inputVal) && !(validationMethods.isValidFqdn(inputVal))) {
                    return { [ValidatorEnum.VALID_IP_OR_FQDN]: true };
                }
            } else {
                return { [ValidatorEnum.REQUIRED]: true };
            }
            if (control.value) {
                if (!validationMethods.isNumericOnly(control.value)) {
                    return { [ValidatorEnum.VALID_PORT]: true };
                }
                return null;
            }
            return { [ValidatorEnum.REQUIRED]: true };
        }


(control: AbstractControl) => {
            const ctrlValue: string = control.value;
            if (list.indexOf(ctrlValue) === -1) {
                return { [ValidatorEnum.NOT_IN_DATALIST]: true };
            }
            return null;
        }


(control: AbstractControl) => {
            const ctrlValue: boolean = control.value;
            if (ctrlValue === true) {
                 return { [ValidatorEnum.TRUE]: true };
            }
            return null;
        }


(control: AbstractControl) => {
            const ctrlValue: string = control.value;
            for (const key of keys) {
                if (ctrlValue !== '' && ctrlValue === formGroup.value[key] && key !== name) {
                    return { [ValidatorEnum.LABEL_UNIQUE]: true}
                }
            }
            return null;
        }


getSimpleValidator(requested: SimpleValidator): (control: AbstractControl) => any {
        return this.simpleValidatorMap.get(requested);
    }


/**
     * @method isValidIp
     * NOTE: if string is empty, does not yield error
     */
isValidIp(): any {
        return (control: AbstractControl) => {
            const ctrlValue: string = control.value;
            if (ctrlValue) {
                return validationMethods.isValidIp(ctrlValue) ?
                    null : { [ValidatorEnum.VALID_IP]: true };
            }
            return null;
        }
    }


/**
     * @method noWhitespaceOnEnds check if string has leading/trailing whitespsaces
     */
noWhitespaceOnEnds(): any {
        return (control: AbstractControl) => {
            const ctrlValue: string = control.value;
            if (ctrlValue) {
                if (ctrlValue.length !== ctrlValue.toString().trim().length) {
                    return {
                        [ValidatorEnum.WHITESPACE]: true
                    };
                }
            }
            return null;
        }
    }


/**
     * @method noTrailingSlash check if string has trailing slash (IE https://test.net/)
     */
noTrailingSlash(): any {
        return (control: AbstractControl) => {
            const ctrlValue: string = control.value;
            if (ctrlValue) {
                if (ctrlValue.substr(ctrlValue.length - 1) === '/') {
                    return {
                        [ValidatorEnum.TRAILING_SLASH]: true
                    };
                }
            }
            return null;
        }
    }


/**
     * @method isValidIps
     * NOTE: if string is empty, does not yield error
     */
isValidIps(): any {
        return (control: AbstractControl) => {
            const ctrlValue: string = control.value;
            if (ctrlValue) {
                const ips: Array<string> = ctrlValue.split(',');
                return ips
                    .map(ipStr => validationMethods.isValidIp(ipStr))
                    .reduce((a, b) => a && b, true) ? null : { [ValidatorEnum.VALID_IP]: true };
            }
            return null;
        }
    }


/**
     * @method isValidFqdn
     * NOTE: if string is empty, does not yield error
     */
isValidFqdn(): any {
        return (control: AbstractControl) => {
            const ctrlValue: string = control.value;
            if (ctrlValue) {
                return validationMethods.isValidFqdn(ctrlValue) ?
                    null : { [ValidatorEnum.VALID_FQDN]: true };
            }
            return null;
        }
    }


/**
     * @method isValidIpOrFqdn validator to check if input is valid IP or FQDN
     */
isValidIpOrFqdn(): any {
        return (control: AbstractControl) => {
            const ctrlValue: string = control.value;
            if (ctrlValue) {
                if (validationMethods.isValidIp(ctrlValue) ||
                    validationMethods.isValidFqdn(ctrlValue)) {
                    return null;
                }

                return {
                    [ValidatorEnum.VALID_IP_OR_FQDN]: true
                };
            }
        }
    }


/**
     * @method isValidIpOrFqdn validator to check if input is valid IP or FQDN
     */
isValidIpv6OrFqdn(): any {
        return (control: AbstractControl) => {
            const ctrlValue: string = control.value;
            if (ctrlValue) {
                if (isIp.v6(ctrlValue) ||
                    validationMethods.isValidFqdn(ctrlValue)) {
                    return null;
                }

                return {
                    [ValidatorEnum.VALID_IP_OR_FQDN]: true
                };
            }
        }
    }


/**
     * @method isValidIpOrFqdnWithProtocol validator to check if input is valid IP or FQDN
     * with protocol prefix
     */
isValidIpOrFqdnWithHttpsProtocol(): any {
        return (control: AbstractControl) => {
            const ctrlValue: string = control.value;
            if (ctrlValue) {
                if (validationMethods.isValidIpWithHttpsProtocol(ctrlValue) ||
                    validationMethods.isValidFqdnWithHttpsProtocol(ctrlValue)) {
                    return null;
                }

                return {
                    [ValidatorEnum.VALID_IP_OR_FQDN]: true
                };
            }
        }
    }


/**
     * @method isValidIpv6OrFqdnWithHttpsProtocol validator to check if input is valid IP or FQDN
     * with protocol prefix
     */
isValidIpv6OrFqdnWithHttpsProtocol(): any {
        return (control: AbstractControl) => {
            const ctrlValue: string = control.value;
            if (ctrlValue) {
                if (validationMethods.isValidIpv6WithHttpsProtocol(ctrlValue) ||
                    validationMethods.isValidFqdnWithHttpsProtocol(ctrlValue)) {
                    return null;
                }

                return {
                    [ValidatorEnum.VALID_IP_OR_FQDN]: true
                };
            }
        }
    }


/**
     * @method isStringWithoutUrlFragment validator to check if input includes URL fragment
     */
isStringWithoutUrlFragment(): any {
        return (control: AbstractControl) => {
            const ctrlValue: string = control.value;
            if (ctrlValue) {
                if (validationMethods.isStringWithoutUrlFragment(ctrlValue)) {
                    return {
                        [ValidatorEnum.INCLUDES_URL_FRAGMENT]: true
                    };
                }
                return null;
            }
        }
    }


/**
     * @method isStringWithoutQueryParams validator to check if input includes URL query params
     */
isStringWithoutQueryParams(): any {
        return (control: AbstractControl) => {
            const ctrlValue: string = control.value;
            if (ctrlValue) {
                if (validationMethods.isStringWithoutQueryParams(ctrlValue)) {
                    return {
                        [ValidatorEnum.INCLUDES_QUERY_PARAMS]: true
                    };
                }
                return null;
            }
        }
    }


/**
     * @method isValidPort validator to check if input is valid port
     */
isValidPort(): any {
        return (control: AbstractControl) => {
            const ctrlValue: string = control.value;
            if (ctrlValue) {
                if (validationMethods.isNumericOnly(ctrlValue)) {
                    return null;
                }

                return {
                    [ValidatorEnum.VALID_PORT]: true
                };
            }
            return null;
        }
    }


/**
     * @method isValidSubnet validator to check if input subnet is valid
     *  - valid ip
     *  - within subnet mask range
     */
isValidSubnet(subnetMaskCtrl: AbstractControl): any {
        return (control: AbstractControl) => {
            const ctrlValue: string = control.value;
            if (ctrlValue) {
                if (validationMethods.isValidIp(ctrlValue)) {
                    const ipLong = validationMethods.ipAddrToLong(ctrlValue);
                    const subnetMask = subnetMaskCtrl.value;
                    if (validationMethods.isValidIp(subnetMask)) {
                        const subnetMaskLong = validationMethods.ipAddrToLong(subnetMask);

                        if (validationMethods.subnetMaskInRange(ipLong, subnetMaskLong)) {
                            return null;
                        }

                        return {
                            [ValidatorEnum.SUBNET_IN_RANGE]: true
                        };
                    }
                    return null;
                }
                return {
                    [ValidatorEnum.VALID_IP]: true
                };
            }
            return null;
        }
    }


/**
     * @method isValidClusterName
     * NOTE: if string start and end with a letter, and can contain only
     * lowercase letters, numbers, and hyphens.
     */
isValidClusterName(): any {
        return (control: AbstractControl) => {
            const ctrlValue: string = control.value;
            if (ctrlValue) {
                if (ctrlValue.length !== ctrlValue.toString().trim().length) {
                    return {
                        [ValidatorEnum.WHITESPACE]: true
                    };
                }
                return validationMethods.isValidClustername(ctrlValue) ?
                    null : { [ValidatorEnum.VALID_CLUSTER_NAME]: true };
            }
            return null;
        }
    }


/**
     * @method isValidLabelOrAnnotation
     * NOTE: if string start and end with a letter, and can contain only
     * lowercase letters, numbers, hyphens, underscores, dots, and have
     * max length of 63.
     */
isValidLabelOrAnnotation(): any {
        return (control: AbstractControl) => {
            const ctrlValue: string = control.value;
            if (ctrlValue) {
                if (ctrlValue.length !== ctrlValue.toString().trim().length) {
                    return {
                        [ValidatorEnum.WHITESPACE]: true
                    };
                }
                if (ctrlValue.length > 63) {
                    return {
                        [ValidatorEnum.MAX_LEN]: true
                    }
                }
                return validationMethods.isValidLabelOrAnnotation(ctrlValue) ?
                    null : { [ValidatorEnum.VALID_CLUSTER_NAME]: true };
            }
            return null;
        }
    }


/**
     * @method isIpInSubnet validator to check if input is within subnet range
     *  - non-empty
     *  - valid ip
     *  - within subnet range
     */
isIpInSubnet(subnetCtrl: AbstractControl, subnetMaskCtrl: AbstractControl): any {
        return (control: AbstractControl) => {
            const ctrlValue: string = control.value;
            if (ctrlValue) {
                if (validationMethods.isValidIp(ctrlValue)) {
                    const ipLong = validationMethods.ipAddrToLong(ctrlValue);
                    const subnetMask = subnetMaskCtrl.value;
                    const subnet = subnetCtrl.value;
                    if (validationMethods.isValidIp(subnet) &&
                        validationMethods.isValidIp(subnetMask)) {
                        const subnetLong = validationMethods.ipAddrToLong(subnet);
                        const subnetMaskLong = validationMethods.ipAddrToLong(subnetMask);

                        if (validationMethods.isIpInSubnet(ipLong, subnetLong, subnetMaskLong)) {
                            return null;
                        }

                        return {
                            [ValidatorEnum.IP_IN_SUBNET_RANGE]: true
                        };
                    }
                    return null;
                }
                return {
                    [ValidatorEnum.VALID_IP]: true
                };
            }
            return null;
        }
    }


/**
     * @method isIpInSubnet2 validator to check if input is within subnet range
     * @param cidrControlName the name of the CIDR, assumming format IPv4/length, e.g: 192.167.0.0/16
     */
isIpInSubnet2(cidr: string): any {
        return (control: AbstractControl) => {
            const ipv4: string = control.value;
            if (ipv4) {
                if (validationMethods.isValidIp(ipv4)) {
                    let netmask = null;
                    try {
                        netmask = new Netmask(cidr);
                    } catch (e) {
                        // the netmask may not have been initialized yet, we don't validate
                        return null;
                    }

                    if (!netmask.contains(ipv4)) {
                        return {
                            [ValidatorEnum.IP_IN_SUBNET_RANGE]: true
                        };
                    }

                    return null;
                }
                return {
                    [ValidatorEnum.VALID_IP]: true
                };
            }
            return null;
        }
    }


/**
     * @method ipOverlapSubnet validator to check if input is overlapping with subnet
     *  - non-empty
     *  - valid ip
     *  - not overlapping with subnet
     *
     *  NOTE: non-exhaustive check
     */
ipOverlapSubnet(subnetCtrl: AbstractControl, subnetMaskCtrl: AbstractControl): any {
        return (control: AbstractControl) => {
            const ctrlValue: string = control.value;
            if (ctrlValue) {
                if (validationMethods.isValidIp(ctrlValue)) {
                    const ipLong = validationMethods.ipAddrToLong(ctrlValue);
                    const subnetMask = subnetMaskCtrl.value;
                    const subnet = subnetCtrl.value;
                    if (validationMethods.isValidIp(subnet) &&
                        validationMethods.isValidIp(subnetMask)) {
                        const subnetLong = validationMethods.ipAddrToLong(subnet);
                        const subnetMaskLong = validationMethods.ipAddrToLong(subnetMask);

                        if (validationMethods.ipOverlapSubnet(ipLong, subnetLong, subnetMaskLong)) {
                            return {
                                [ValidatorEnum.IP_NOT_IN_SUBNET_RANGE]: true
                            };
                        }
                    }

                    return null;
                }
                return {
                    [ValidatorEnum.VALID_IP]: true
                };
            }
            return null;
        }
    }


/**
     * @method cidrOverlapCidr validator to check if input is overlapping with another cidr
     */
cidrOverlapCidr(compareCidrCtrl: AbstractControl): any {
        return (control: AbstractControl) => {
            const ctrlValue: string = control.value;
            const compareValue: string = compareCidrCtrl.value;
            if (ctrlValue) {
                if (validationMethods.isValidCidr(ctrlValue) && validationMethods.isValidCidr(compareValue)) {
                    if (validationMethods.isCidrOverlapCidr(ctrlValue, compareValue)) {
                        return {
                            [ValidatorEnum.CIDR_OVERLAP_CIDR]: true
                        };
                    }

                    return null;
                }

            }
            return null;
        }
    }


/**
     * @method cidrWithinCidr validator to check if input is within another cidr range
     */
cidrWithinCidr(compareCidrCtrl: AbstractControl): any {
        return (control: AbstractControl) => {
            const ctrlValue: string = control.value;
            const compareValue: string = compareCidrCtrl.value;
            if (ctrlValue) {
                if (validationMethods.isValidCidr(ctrlValue) && validationMethods.isValidCidr(compareValue)) {
                    if (!validationMethods.isCidrWithinCidr(ctrlValue, compareValue)) {
                        return {
                            [ValidatorEnum.CIDR_WITHIN_CIDR]: true
                        };
                    }
                    return null;
                }

            }
            return null;
        }
    }


/**
     * @method isValidIpNetworkSegment
     * xxx.xxx.xxx.xx/xx
     */
isValidIpNetworkSegment(): any {
        return (control: AbstractControl) => {
            const ctrlValue: string = control.value;
            if (ctrlValue) {
                const ctrlValueList = ctrlValue.split('/');
                if (ctrlValueList.length === 2) {
                    if (!validationMethods.isValidIp(ctrlValueList[0])) {
                        return {
                            [ValidatorEnum.VALID_IP]: true
                        }
                    }
                    return ctrlValueList[1] && +ctrlValueList[1] >= 0 && +ctrlValueList[1] < 32 ? null : {
                        [ValidatorEnum.VALID_IP]: true
                    }
                } else {
                    return {
                        [ValidatorEnum.VALID_IP]: true
                    };
                }
            }
            return null;
        }
    }


/**
     * @method isValidIpv6NetworkSegment
     * xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx/xxx
     */
isValidIpv6NetworkSegment(): any {
        return (control: AbstractControl) => {
            const ctrlValue: string = control.value;
            if (ctrlValue) {
                const ctrlValueList = ctrlValue.split('/');
                if (ctrlValueList.length === 2) {
                    if (!isIp.v6(ctrlValueList[0])) {
                        return {
                            [ValidatorEnum.VALID_IP]: true
                        }
                    }
                    return ctrlValueList[1] && +ctrlValueList[1] >= 0 && +ctrlValueList[1] < 128 ? null : {
                        [ValidatorEnum.VALID_IP]: true
                    }
                } else {
                    return {
                        [ValidatorEnum.VALID_IP]: true
                    };
                }
            }
            return null;
        }
    }


/**
     * @method isIpUnique
     * @param otherControls - array of IP controls to pass in; checks all IP's to confirm they are unique
     * @returns {function(AbstractControl): {}}
     */
isIpUnique(otherControls: Array<AbstractControl>) {
        return (control: AbstractControl) => {
            if (control.value) {
                const currentControlIp = control.value;
                for (const ipAddr of otherControls) {
                    if (currentControlIp === ipAddr.value) {
                        return { [ValidatorEnum.NETWORKING_IP_UNIQUE]: true };
                    }
                }
            }
        }
    }


/**
     * @method confirmPassword check if password and confirm password match
     */
confirmPassword(passwordCtrl: AbstractControl): any {
        return (control: AbstractControl) => {
            const ctrlValue: string = control.value;
            const passwordValue = passwordCtrl.value;
            if (passwordCtrl) {
                if (ctrlValue) {
                    if (ctrlValue !== passwordValue) {
                        return {
                            [ValidatorEnum.CONFIRM_PASSWORD]: true
                        };
                    } else {
                        return null;
                    }
                } else {
                    return {
                        [ValidatorEnum.REQUIRED]: true
                    };
                }
            }
            return null;
        }
    }


/**
     * @method isNumericOnly to check if input is numeric only
     */
isNumericOnly(): any {
        return (control: AbstractControl) => {
            const ctrlValue: string = control.value;
            if (ctrlValue) {
                if (validationMethods.isNumericOnly(ctrlValue)) {
                    return null;
                }
                return { [ValidatorEnum.NUMERIC_ONLY]: true };
            }
            return null;
        }
    }


/**
    * @method commaSeparatedIpOrFqdn
    * @param {string} arg input string
    * @return {boolean}
    */
commaSeparatedIpOrFqdn(arg: string): any {
        const ips = arg.split(',');
        return ips.map(ip => validationMethods.isValidIp(ip) || validationMethods.isValidFqdn(ip)).reduce((a, b) => a && b, true);
    }


/**
     * @method isCommaSeparatedIpsOrFqdn
     */
isCommaSeparatedIpsOrFqdn(): any {
        return (control: AbstractControl) => {
            const ctrlValue: string = control.value;
            if (ctrlValue) {
                if (!this.commaSeparatedIpOrFqdn(ctrlValue)) {
                    return { [ValidatorEnum.VALID_IP_OR_FQDN]: true };
                }
            }
            return null;
        }
    }


/**
     * @method isNumberGreaterThanZero
     */
isNumberGreaterThanZero(): any {
        return (control: AbstractControl) => {
            const ctrlValue: number = control.value;
            if (ctrlValue === null) {
                return null;
            }
            if (typeof ctrlValue !== 'number' || ctrlValue < 1) {
                return { [ValidatorEnum.GREATER_THAN_ZERO]: true };
            }
            return null;
        }
    }


isUniqueAz(otherControls: Array<AbstractControl>): any {
        return (control: AbstractControl) => {
            if (control.value) {
                const currentAz = control.value;
                for (const az of otherControls) {
                    if (currentAz === az.value) {
                        return { [ValidatorEnum.AVAILABILITY_ZONE_UNIQUE]: true };
                    }
                }
            }
        }
    }


isValidResourceGroupName(): any {
        return (control: AbstractControl) => {
            const ctrlValue: string = control.value;
            if (ctrlValue && !XRegExp('^[\\pL-_.()\\w]+$').test(ctrlValue)) {
                return { [ValidatorEnum.VALID_RESOURCE_GROUP_NAME]: true };
            }
            return null;
        }
    }


isUniqueResourceGroupName(resourceGroups): any {
        return (control: AbstractControl) => {
            const ctrlValue: string = control.value;
            if (ctrlValue && resourceGroups.find(x => x.name === ctrlValue) != null) {
                return { [ValidatorEnum.UNIQUE_RESOURCE_GROUP_NAME]: true };
            }
            return null;
        }
    }


/**
     * @method isCommaSeperatedList
     */
isCommaSeperatedList(): any {
        return (control: AbstractControl) => {
            const ctrlValue: string = control.value;
            if (ctrlValue) {
                if (!XRegExp('^(((\\w+)(,\\w+)+)|(\\w+))$').test(ctrlValue)) {
                    return {
                        [ValidatorEnum.COMMA_SEPARATED_WORDS]: true
                    };
                }
            }
        }
    }


isHttpOrHttps(): any {
        return (control: AbstractControl) => {
            const ctrlValue: string = control.value;
            if (ctrlValue && !XRegExp('^https?:\/\/').test(ctrlValue)) {
                return { [ValidatorEnum.HTTP_OR_HTTPS]: true };
            }
            return null;
        }
    }


/**
     * @method isValidLdap
     *  - non-empty
     *  - valid IP or FQDN
     *  - valid port
     *
     * @param {AbstractControl} ipCtrl
     */
isValidLdap(ipCtrl: AbstractControl): any {
        return (control: AbstractControl) => {
            const inputVal = ipCtrl.value;
            if (inputVal) {
                if (!validationMethods.isValidIp(inputVal) && !(validationMethods.isValidFqdn(inputVal))) {
                    return { [ValidatorEnum.VALID_IP_OR_FQDN]: true };
                }
            } else {
                return { [ValidatorEnum.REQUIRED]: true };
            }

            if (control.value) {
                if (!validationMethods.isNumericOnly(control.value)) {
                    return { [ValidatorEnum.VALID_PORT]: true };
                }
                return null;
            }
            return { [ValidatorEnum.REQUIRED]: true };
        }
    }


/**
     * @method isValidIpv6Ldap
     *  - non-empty
     *  - valid IP or FQDN
     *  - valid port
     *
     * @param {AbstractControl} ipCtrl
     */
isValidIpv6Ldap(ipCtrl: AbstractControl): any {
        return (control: AbstractControl) => {
            const inputVal = ipCtrl.value;
            if (inputVal) {
                if (!isIp.v6(inputVal) && !(validationMethods.isValidFqdn(inputVal))) {
                    return { [ValidatorEnum.VALID_IP_OR_FQDN]: true };
                }
            } else {
                return { [ValidatorEnum.REQUIRED]: true };
            }
            if (control.value) {
                if (!validationMethods.isNumericOnly(control.value)) {
                    return { [ValidatorEnum.VALID_PORT]: true };
                }
                return null;
            }
            return { [ValidatorEnum.REQUIRED]: true };
        }
    }


isValidNameInList(list: Array<String>): any {
        return (control: AbstractControl) => {
            const ctrlValue: string = control.value;
            if (list.indexOf(ctrlValue) === -1) {
                return { [ValidatorEnum.NOT_IN_DATALIST]: true };
            }
            return null;
        }
    }


isTrue(): any {
        return (control: AbstractControl) => {
            const ctrlValue: boolean = control.value;
            if (ctrlValue === true) {
                 return { [ValidatorEnum.TRUE]: true };
            }
            return null;
        }
    }


isUniqueLabel(formGroup: FormGroup, keys: Set<string>, name: string): any {
        return (control: AbstractControl) => {
            const ctrlValue: string = control.value;
            for (const key of keys) {
                if (ctrlValue !== '' && ctrlValue === formGroup.value[key] && key !== name) {
                    return { [ValidatorEnum.LABEL_UNIQUE]: true}
                }
            }
            return null;
        }
    }


/**
 * Creates a compatible type mapping
 * @internal
 **/
function compatType (compatTypes: TypeSpec[], _type: Text | string): number {
  const type = _type.toString();
  const index = compatTypes.findIndex(({ def }) =>
    def.HistoricMetaCompat === type
  );

  if (index !== -1) {
    return index;
  }

  return compatTypes.push({
    def: {
      HistoricMetaCompat: type
    }
  }) - 1;
}


function makeTupleType (compatTypes: TypeSpec[], entries: number[]): number {
  return compatTypes.push({
    def: {
      Tuple: entries
    }
  }) - 1;
}


function makeVariantType (modName: Text, variantType: string, compatTypes: TypeSpec[], variants: SiVariant[]): number {
  return compatTypes.push({
    def: {
      Variant: { variants }
    },
    path: [`pallet_${modName.toString()}`, 'pallet', variantType]
  }) - 1;
}


/**
 * @internal
 * generate & register the OriginCaller type
 **/
function registerOriginCaller (registry: Registry, modules: ModuleMetadataV13[], metaVersion: number): void {
  registry.register({
    OriginCaller: {
      _enum: modules
        .map((mod, index): [string, number] => [
          mod.name.toString(),
          metaVersion >= 12
            ? mod.index.toNumber()
            : index
        ])
        .sort((a, b) => a[1] - b[1])
        .reduce((result: Record<string, string>, [name, index]): Record<string, string> => {
          for (let i = Object.keys(result).length; i < index; i++) {
            result[`Empty${i}`] = 'Null';
          }

          result[name] = knownOrigins[name] || 'Null';

          return result;
        }, {})
    }
  });
}


/**
 * Find and apply the correct type override
 * @internal
 **/
function setTypeOverride (sectionTypes: OverrideModuleType, types: Type[]): void {
  types.forEach((type): void => {
    const override = Object.keys(sectionTypes).find((aliased) => type.eq(aliased));

    if (override) {
      type.setOverride(sectionTypes[override]);
    } else {
      // FIXME: NOT happy with this approach, but gets over the initial hump cased by (Vec<Announcement>,BalanceOf)
      const orig = type.toString();
      const alias = Object
        .entries(sectionTypes)
        .reduce((result: string, [src, dst]) =>
          BOXES.reduce((result, [a, z]) =>
            result.replace(`${a}${src}${z}`, `${a}${dst}${z}`), result), orig);

      if (orig !== alias) {
        type.setOverride(alias);
      }
    }
  });
}


/**
 * Apply module-specific type overrides (always be done as part of toV14)
 * @internal
 **/
function convertCalls (compatTypes: TypeSpec[], registry: Registry, modName: Text, calls: FunctionMetadataV13[], sectionTypes: OverrideModuleType): PalletCallMetadataV14 {
  const variants = calls.map(({ args, docs, name }, index): SiVariant => {
    setTypeOverride(sectionTypes, args.map(({ type }) => type));

    return registry.createType('SiVariant', {
      docs,
      fields: args.map(({ name, type }) =>
        registry.createType('SiField', { name, type: compatType(compatTypes, type) })
      ),
      index,
      name
    });
  });

  return registry.createType('PalletCallMetadataV14', {
    type: makeVariantType(modName, 'Call', compatTypes, variants)
  });
}


/**
 * Apply module-specific type overrides (always be done as part of toV14)
 * @internal
 */
function convertConstants (compatTypes: TypeSpec[], registry: Registry, constants: ModuleConstantMetadataV13[], sectionTypes: OverrideModuleType): PalletConstantMetadataV14[] {
  return constants.map(({ docs, name, type, value }): PalletConstantMetadataV14 => {
    setTypeOverride(sectionTypes, [type]);

    return registry.createType('PalletConstantMetadataV14', {
      docs,
      name,
      type: compatType(compatTypes, type),
      value
    });
  });
}


/**
 * Apply module-specific type overrides (always be done as part of toV14)
 * @internal
 */
// eslint-disable-next-line @typescript-eslint/no-unused-vars
function convertErrors (compatTypes: TypeSpec[], registry: Registry, modName: Text, errors: ErrorMetadataV13[], _sectionTypes: OverrideModuleType): PalletErrorMetadataV14 {
  const variants = errors.map(({ docs, name }, index): SiVariant =>
    registry.createType('SiVariant', {
      docs,
      fields: [],
      index,
      name
    })
  );

  return registry.createType('PalletErrorMetadataV14', {
    type: makeVariantType(modName, 'Error', compatTypes, variants)
  });
}


/**
 * Apply module-specific type overrides (always be done as part of toV14)
 * @internal
 **/
function convertEvents (compatTypes: TypeSpec[], registry: Registry, modName: Text, events: EventMetadataV13[], sectionTypes: OverrideModuleType): PalletEventMetadataV14 {
  const variants = events.map(({ args, docs, name }, index): SiVariant => {
    setTypeOverride(sectionTypes, args);

    return registry.createType('SiVariant', {
      docs,
      fields: args.map((t) =>
        registry.createType('SiField', { type: compatType(compatTypes, t) })
      ),
      index,
      name
    });
  });

  return registry.createType('PalletEventMetadataV14', {
    type: makeVariantType(modName, 'Event', compatTypes, variants)
  });
}


function createMapEntry (compatTypes: TypeSpec[], registry: Registry, sectionTypes: OverrideModuleType, { hashers, keys, value }: MapDef): StorageEntryTypeV14 {
  setTypeOverride(sectionTypes, [value, ...(Array.isArray(keys) ? keys : [keys])]);

  return registry.createType('StorageEntryTypeV14', {
    Map: {
      hashers,
      key: hashers.length === 1
        ? compatType(compatTypes, keys[0])
        : makeTupleType(compatTypes, keys.map((t) => compatType(compatTypes, t))),
      value: compatType(compatTypes, value)
    }
  });
}


/**
 * Apply module-specific storage type overrides (always part of toV14)
 * @internal
 **/
function convertStorage (compatTypes: TypeSpec[], registry: Registry, { items, prefix }: StorageMetadataV13, sectionTypes: OverrideModuleType): PalletStorageMetadataV14 {
  return registry.createType('PalletStorageMetadataV14', {
    items: items.map(({ docs, fallback, modifier, name, type }): StorageEntryMetadataV14 => {
      let entryType: StorageEntryTypeV14;

      if (type.isPlain) {
        const plain = type.asPlain;

        setTypeOverride(sectionTypes, [plain]);

        entryType = registry.createType('StorageEntryTypeV14', {
          Plain: compatType(compatTypes, plain)
        });
      } else if (type.isMap) {
        const map = type.asMap;

        entryType = createMapEntry(compatTypes, registry, sectionTypes, {
          hashers: [map.hasher],
          keys: [map.key],
          value: map.value
        });
      } else if (type.isDoubleMap) {
        const dm = type.asDoubleMap;

        entryType = createMapEntry(compatTypes, registry, sectionTypes, {
          hashers: [dm.hasher, dm.key2Hasher],
          keys: [dm.key1, dm.key2],
          value: dm.value
        });
      } else {
        const nm = type.asNMap;

        entryType = createMapEntry(compatTypes, registry, sectionTypes, {
          hashers: nm.hashers,
          keys: nm.keyVec,
          value: nm.value
        });
      }

      return registry.createType('StorageEntryMetadataV14', {
        docs,
        fallback,
        modifier,
        name,
        type: entryType
      });
    }),
    prefix
  });
}


/** @internal */
// eslint-disable-next-line @typescript-eslint/no-unused-vars
function convertExtrinsic (registry: Registry, { signedExtensions, version }: ExtrinsicMetadataV13): ExtrinsicMetadataV14 {
  return registry.createType('ExtrinsicMetadataV14', {
    signedExtensions: signedExtensions.map((identifier) => ({
      identifier,
      type: 0 // we don't map the fields at all
    })),
    type: 0, // Map to extrinsic like in v14?
    version: version
  });
}


/** @internal */
function createPallet (compatTypes: TypeSpec[], registry: Registry, mod: ModuleMetadataV13, { calls, constants, errors, events, storage }: { calls: FunctionMetadataV13[] | null, constants: ModuleConstantMetadataV13[], errors: ErrorMetadataV13[] | null, events: EventMetadataV13[] | null, storage: StorageMetadataV13 | null }): PalletMetadataV14 {
  const sectionTypes = getModuleTypes(registry, stringCamelCase(mod.name));

  return registry.createType('PalletMetadataV14', {
    calls: calls && convertCalls(compatTypes, registry, mod.name, calls, sectionTypes),
    constants: convertConstants(compatTypes, registry, constants, sectionTypes),
    errors: errors && convertErrors(compatTypes, registry, mod.name, errors, sectionTypes),
    events: events && convertEvents(compatTypes, registry, mod.name, events, sectionTypes),
    index: mod.index,
    name: mod.name,
    storage: storage && convertStorage(compatTypes, registry, storage, sectionTypes)
  });
}


/**
 * Convert the Metadata to v14
 * @internal
 **/
export function toV14 (registry: Registry, v13: MetadataV13, metaVersion: number): MetadataV14 {
  const compatTypes: TypeSpec[] = [];

  compatType(compatTypes, 'Null'); // position 0 always has Null
  registerOriginCaller(registry, v13.modules, metaVersion);

  const extrinsic = convertExtrinsic(registry, v13.extrinsic);
  const pallets = v13.modules.map((mod) =>
    createPallet(compatTypes, registry, mod, {
      calls: mod.calls.unwrapOr(null),
      constants: mod.constants,
      errors: mod.errors.length ? mod.errors : null,
      events: mod.events.unwrapOr(null),
      storage: mod.storage.unwrapOr(null)
    })
  );

  return registry.createType('MetadataV14', {
    extrinsic,
    lookup: {
      types: compatTypes.map((type, id) =>
        registry.createType('PortableType', { id, type })
      )
    },
    pallets
  });
}


({ def }) =>
    def.HistoricMetaCompat === type


(mod, index): [string, number] => [
          mod.name.toString(),
          metaVersion >= 12
            ? mod.index.toNumber()
            : index
        ]


(a, b) => a[1] - b[1]


(result: Record<string, string>, [name, index]): Record<string, string> => {
          for (let i = Object.keys(result).length; i < index; i++) {
            result[`Empty${i}`] = 'Null';
          }

          result[name] = knownOrigins[name] || 'Null';

          return result;
        }


(type): void => {
    const override = Object.keys(sectionTypes).find((aliased) => type.eq(aliased));

    if (override) {
      type.setOverride(sectionTypes[override]);
    } else {
      // FIXME: NOT happy with this approach, but gets over the initial hump cased by (Vec<Announcement>,BalanceOf)
      const orig = type.toString();
      const alias = Object
        .entries(sectionTypes)
        .reduce((result: string, [src, dst]) =>
          BOXES.reduce((result, [a, z]) =>
            result.replace(`${a}${src}${z}`, `${a}${dst}${z}`), result), orig);

      if (orig !== alias) {
        type.setOverride(alias);
      }
    }
  }


(aliased) => type.eq(aliased)


(result: string, [src, dst]) =>
          BOXES.reduce((result, [a, z]) =>
            result.replace(`${a}${src}${z}`, `${a}${dst}${z}`), result)


(result, [a, z]) =>
            result.replace(`${a}${src}${z}`, `${a}${dst}${z}`)


({ args, docs, name }, index): SiVariant => {
    setTypeOverride(sectionTypes, args.map(({ type }) => type));

    return registry.createType('SiVariant', {
      docs,
      fields: args.map(({ name, type }) =>
        registry.createType('SiField', { name, type: compatType(compatTypes, type) })
      ),
      index,
      name
    });
  }


({ type }) => type


({ name, type }) =>
        registry.createType('SiField', { name, type: compatType(compatTypes, type) })


({ docs, name, type, value }): PalletConstantMetadataV14 => {
    setTypeOverride(sectionTypes, [type]);

    return registry.createType('PalletConstantMetadataV14', {
      docs,
      name,
      type: compatType(compatTypes, type),
      value
    });
  }


({ docs, name }, index): SiVariant =>
    registry.createType('SiVariant', {
      docs,
      fields: [],
      index,
      name
    })


({ args, docs, name }, index): SiVariant => {
    setTypeOverride(sectionTypes, args);

    return registry.createType('SiVariant', {
      docs,
      fields: args.map((t) =>
        registry.createType('SiField', { type: compatType(compatTypes, t) })
      ),
      index,
      name
    });
  }


(t) =>
        registry.createType('SiField', { type: compatType(compatTypes, t) })


(t) => compatType(compatTypes, t)


({ docs, fallback, modifier, name, type }): StorageEntryMetadataV14 => {
      let entryType: StorageEntryTypeV14;

      if (type.isPlain) {
        const plain = type.asPlain;

        setTypeOverride(sectionTypes, [plain]);

        entryType = registry.createType('StorageEntryTypeV14', {
          Plain: compatType(compatTypes, plain)
        });
      } else if (type.isMap) {
        const map = type.asMap;

        entryType = createMapEntry(compatTypes, registry, sectionTypes, {
          hashers: [map.hasher],
          keys: [map.key],
          value: map.value
        });
      } else if (type.isDoubleMap) {
        const dm = type.asDoubleMap;

        entryType = createMapEntry(compatTypes, registry, sectionTypes, {
          hashers: [dm.hasher, dm.key2Hasher],
          keys: [dm.key1, dm.key2],
          value: dm.value
        });
      } else {
        const nm = type.asNMap;

        entryType = createMapEntry(compatTypes, registry, sectionTypes, {
          hashers: nm.hashers,
          keys: nm.keyVec,
          value: nm.value
        });
      }

      return registry.createType('StorageEntryMetadataV14', {
        docs,
        fallback,
        modifier,
        name,
        type: entryType
      });
    }


(identifier) => ({
      identifier,
      type: 0 // we don't map the fields at all
    })


(mod) =>
    createPallet(compatTypes, registry, mod, {
      calls: mod.calls.unwrapOr(null),
      constants: mod.constants,
      errors: mod.errors.length ? mod.errors : null,
      events: mod.events.unwrapOr(null),
      storage: mod.storage.unwrapOr(null)
    })


(type, id) =>
        registry.createType('PortableType', { id, type })


interface MapDef {
  hashers: StorageHasherV13[];
  keys: Type[];
  value: Type;
}


export interface TypeSpec {
  def: {
    HistoricMetaCompat?: string;
    Tuple?: number[];
    Variant?: {
      variants: SiVariant[];
    }
  },
  path?: string[];
}


function TradeReview({
  trade,
  allowedSlippage,
}: {
  trade: V2Trade<Currency, Currency, TradeType> | undefined
  allowedSlippage: any
}) {
  const { i18n } = useLingui()

  const showRoute = Boolean(trade && trade.route.path.length > 2)

  const { realizedLPFee, priceImpact } = useMemo(() => {
    if (!trade) return { realizedLPFee: undefined, priceImpact: undefined }

    const realizedLpFeePercent = computeRealizedLPFeePercent(trade)
    const realizedLPFee = trade.inputAmount.multiply(realizedLpFeePercent)
    const priceImpact = trade.priceImpact.subtract(realizedLpFeePercent)
    return { priceImpact, realizedLPFee }
  }, [trade])

  return (
    <>

Swap Review

      {trade ? (

              {i18n._(t`Minimum received`)}
              <QuestionHelper
                text={i18n._(
                  t`Your transaction will revert if there is a large, unfavorable price movement before it is confirmed.`
                )}
              />

              {`${trade.minimumAmountOut(allowedSlippage)?.toSignificant(4)} ${trade.outputAmount.currency.symbol}` ??
                '-'}

              {i18n._(t`Price Impact`)}
              <QuestionHelper
                text={i18n._(t`The difference between the market price and estimated price due to trade size.`)}
              />

              <FormattedPriceImpact priceImpact={priceImpact} />

              Liquidity Provider Fee
              <QuestionHelper
                text={i18n._(t`A portion of each trade (0.25%) goes to liquidity providers as a protocol incentive.`)}
              />

              {realizedLPFee ? `${realizedLPFee.toSignificant(4)} ${trade.inputAmount.currency.symbol}` : '-'}


() => {
    if (!trade) return { realizedLPFee: undefined, priceImpact: undefined }

    const realizedLpFeePercent = computeRealizedLPFeePercent(trade)
    const realizedLPFee = trade.inputAmount.multiply(realizedLpFeePercent)
    const priceImpact = trade.priceImpact.subtract(realizedLpFeePercent)
    return { priceImpact, realizedLPFee }
  }


/**
 * A simple EngineHost that uses a root with one directory per collection inside of it. The
 * collection declaration follows the same rules as the regular FileSystemEngineHostBase.
 */
export declare class FileSystemEngineHost extends FileSystemEngineHostBase {
    protected _root: string;
    constructor(_root: string);
    protected _resolveCollectionPath(name: string): string | null;
    protected _resolveReferenceString(refString: string, parentPath: string): {
        ref: RuleFactory<{}>;
        path: string;
    } | null;
    protected _transformCollectionDescription(_name: string, desc: Partial<FileSystemCollectionDesc>): CollectionDescription<FileSystemCollectionDescription> | null;
    protected _transformSchematicDescription(_name: string, _collection: FileSystemCollectionDesc, desc: Partial<FileSystemSchematicDesc>): SchematicDescription<FileSystemCollectionDescription, FileSystemSchematicDescription> | null;
}


/**
 * Used to simplify typings.
 */
export declare type FileSystemCollectionDesc = CollectionDescription<FileSystemCollectionDescription>;


export declare type FileSystemSchematicDesc = SchematicDescription<FileSystemCollectionDescription, FileSystemSchematicDescription>;


protected _resolveCollectionPath(name: string): string | null;


protected _resolveReferenceString(refString: string, parentPath: string): {
        ref: RuleFactory<{}>;
        path: string;
    } | null;


protected _transformCollectionDescription(_name: string, desc: Partial<FileSystemCollectionDesc>): CollectionDescription<FileSystemCollectionDescription> | null;


protected _transformSchematicDescription(_name: string, _collection: FileSystemCollectionDesc, desc: Partial<FileSystemSchematicDesc>): SchematicDescription<FileSystemCollectionDescription, FileSystemSchematicDescription> | null;


/**
 * Gets or sets the cookie affinity
 */
export type ApplicationGatewayCookieBasedAffinity = (typeof ApplicationGatewayCookieBasedAffinity)[keyof typeof ApplicationGatewayCookieBasedAffinity];


/**
 * Gets or sets the protocol
 */
export type ApplicationGatewayProtocol = (typeof ApplicationGatewayProtocol)[keyof typeof ApplicationGatewayProtocol];


/**
 * Gets or sets the rule type
 */
export type ApplicationGatewayRequestRoutingRuleType = (typeof ApplicationGatewayRequestRoutingRuleType)[keyof typeof ApplicationGatewayRequestRoutingRuleType];


/**
 * Gets or sets name of application gateway SKU
 */
export type ApplicationGatewaySkuName = (typeof ApplicationGatewaySkuName)[keyof typeof ApplicationGatewaySkuName];


/**
 * Gets or sets tier of application gateway
 */
export type ApplicationGatewayTier = (typeof ApplicationGatewayTier)[keyof typeof ApplicationGatewayTier];


/**
 * Gets or sets AuthorizationUseStatus
 */
export type AuthorizationUseStatus = (typeof AuthorizationUseStatus)[keyof typeof AuthorizationUseStatus];


/**
 * Gets or sets AdvertisedPublicPrefixState of the Peering resource
 */
export type ExpressRouteCircuitPeeringAdvertisedPublicPrefixState = (typeof ExpressRouteCircuitPeeringAdvertisedPublicPrefixState)[keyof typeof ExpressRouteCircuitPeeringAdvertisedPublicPrefixState];


/**
 * Gets or sets state of Peering
 */
export type ExpressRouteCircuitPeeringState = (typeof ExpressRouteCircuitPeeringState)[keyof typeof ExpressRouteCircuitPeeringState];


/**
 * Gets or sets PeeringType
 */
export type ExpressRouteCircuitPeeringType = (typeof ExpressRouteCircuitPeeringType)[keyof typeof ExpressRouteCircuitPeeringType];


/**
 * Gets or sets family of the sku.
 */
export type ExpressRouteCircuitSkuFamily = (typeof ExpressRouteCircuitSkuFamily)[keyof typeof ExpressRouteCircuitSkuFamily];


/**
 * Gets or sets tier of the sku.
 */
export type ExpressRouteCircuitSkuTier = (typeof ExpressRouteCircuitSkuTier)[keyof typeof ExpressRouteCircuitSkuTier];


/**
 * Gets or sets PublicIP allocation method (Static/Dynamic)
 */
export type IpAllocationMethod = (typeof IpAllocationMethod)[keyof typeof IpAllocationMethod];


/**
 * Gets or sets the load distribution policy for this rule
 */
export type LoadDistribution = (typeof LoadDistribution)[keyof typeof LoadDistribution];


/**
 * Gets or sets the protocol of the end point. Possible values are http pr Tcp. If Tcp is specified, a received ACK is required for the probe to be successful. If http is specified,a 200 OK response from the specifies URI is required for the probe to be successful
 */
export type ProbeProtocol = (typeof ProbeProtocol)[keyof typeof ProbeProtocol];


/**
 * Gets or sets the type of Azure hop the packet should be sent to.
 */
export type RouteNextHopType = (typeof RouteNextHopType)[keyof typeof RouteNextHopType];


/**
 * Gets or sets network traffic is allowed or denied. Possible values are 'Allow' and 'Deny'
 */
export type SecurityRuleAccess = (typeof SecurityRuleAccess)[keyof typeof SecurityRuleAccess];


/**
 * Gets or sets the direction of the rule.InBound or Outbound. The direction specifies if rule will be evaluated on incoming or outgoing traffic.
 */
export type SecurityRuleDirection = (typeof SecurityRuleDirection)[keyof typeof SecurityRuleDirection];


/**
 * Gets or sets Network protocol this rule applies to. Can be Tcp, Udp or All(*).
 */
export type SecurityRuleProtocol = (typeof SecurityRuleProtocol)[keyof typeof SecurityRuleProtocol];


/**
 * Gets or sets ServiceProviderProvisioningState state of the resource
 */
export type ServiceProviderProvisioningState = (typeof ServiceProviderProvisioningState)[keyof typeof ServiceProviderProvisioningState];


/**
 * Gets or sets the transport protocol for the external endpoint. Possible values are Udp or Tcp
 */
export type TransportProtocol = (typeof TransportProtocol)[keyof typeof TransportProtocol];


/**
* ResourcePolicyRule is a predicate that matches some resource requests, testing the request\'s verb and the target resource. A ResourcePolicyRule matches a resource request if and only if: (a) at least one member of verbs matches the request, (b) at least one member of apiGroups matches the request, (c) at least one member of resources matches the request, and (d) least one member of namespaces matches the request.
*/
export class V1beta1ResourcePolicyRule {
    /**
    * `apiGroups` is a list of matching API groups and may not be empty. \"*\" matches all API groups and, if present, must be the only entry. Required.
    */
    'apiGroups': Array<string>;
    /**
    * `clusterScope` indicates whether to match requests that do not specify a namespace (which happens either because the resource is not namespaced or the request targets all namespaces). If this field is omitted or false then the `namespaces` field must contain a non-empty list.
    */
    'clusterScope'?: boolean;
    /**
    * `namespaces` is a list of target namespaces that restricts matches.  A request that specifies a target namespace matches only if either (a) this list contains that target namespace or (b) this list contains \"*\".  Note that \"*\" matches any specified namespace but does not match a request that _does not specify_ a namespace (see the `clusterScope` field for that). This list may be empty, but only if `clusterScope` is true.
    */
    'namespaces'?: Array<string>;
    /**
    * `resources` is a list of matching resources (i.e., lowercase and plural) with, if desired, subresource.  For example, [ \"services\", \"nodes/status\" ].  This list may not be empty. \"*\" matches all resources and, if present, must be the only entry. Required.
    */
    'resources': Array<string>;
    /**
    * `verbs` is a list of matching verbs and may not be empty. \"*\" matches all verbs and, if present, must be the only entry. Required.
    */
    'verbs': Array<string>;

    static discriminator: string | undefined = undefined;

    static attributeTypeMap: Array<{name: string, baseName: string, type: string}> = [
        {
            "name": "apiGroups",
            "baseName": "apiGroups",
            "type": "Array<string>"
        },
        {
            "name": "clusterScope",
            "baseName": "clusterScope",
            "type": "boolean"
        },
        {
            "name": "namespaces",
            "baseName": "namespaces",
            "type": "Array<string>"
        },
        {
            "name": "resources",
            "baseName": "resources",
            "type": "Array<string>"
        },
        {
            "name": "verbs",
            "baseName": "verbs",
            "type": "Array<string>"
        }    ];

    static getAttributeTypeMap() {
        return V1beta1ResourcePolicyRule.attributeTypeMap;
    }
}


static getAttributeTypeMap() {
        return V1beta1ResourcePolicyRule.attributeTypeMap;
    }


(gitConfig: GitConfigurationModel) => {
      if (gitConfig) {
        this.gitlabConfigured = gitConfig.gitlabConfigured || false;
        this.githubConfigured = gitConfig.githubConfigured || false;
      }
    }


(res: any) => {
          this.openOutputModal(res);
          this.submitted = false;
        }


(error: any) => {
          console.error('Error generating the application.');
          console.error(error);
        }


(data: any) => this.downloadFile(data.body)


// eslint-disable-next-line no-console
(error: any) => console.log(error)


() => {
        // eslint-disable-next-line no-console
        console.log('Application downloaded');
        this.submitted = false;
      }


/**
   * get all the languages options supported by JHipster - copied from the generator.
   */
static getAllSupportedLanguageOptions(): any {
    return [
      { name: 'Albanian', value: 'al' },
      { name: 'Arabic (Libya)', value: 'ar-ly' },
      { name: 'Armenian', value: 'hy' },
      { name: 'Belorussian', value: 'by' },
      { name: 'Bengali', value: 'bn' },
      { name: 'Catalan', value: 'ca' },
      { name: 'Chinese (Simplified)', value: 'zh-cn' },
      { name: 'Chinese (Traditional)', value: 'zh-tw' },
      { name: 'Czech', value: 'cs' },
      { name: 'Danish', value: 'da' },
      { name: 'Dutch', value: 'nl' },
      { name: 'English', value: 'en' },
      { name: 'Estonian', value: 'et' },
      { name: 'Farsi', value: 'fa' },
      { name: 'Finnish', value: 'fi' },
      { name: 'French', value: 'fr' },
      { name: 'Galician', value: 'gl' },
      { name: 'German', value: 'de' },
      { name: 'Greek', value: 'el' },
      { name: 'Hindi', value: 'hi' },
      { name: 'Hungarian', value: 'hu' },
      { name: 'Indonesia', value: 'in' },
      { name: 'Italian', value: 'it' },
      { name: 'Japanese', value: 'ja' },
      { name: 'Korean', value: 'ko' },
      { name: 'Marathi', value: 'mr' },
      { name: 'Myanmar', value: 'my' },
      { name: 'Polish', value: 'pl' },
      { name: 'Portuguese (Brazilian)', value: 'pt-br' },
      { name: 'Portuguese', value: 'pt-pt' },
      { name: 'Romanian', value: 'ro' },
      { name: 'Russian', value: 'ru' },
      { name: 'Slovak', value: 'sk' },
      { name: 'Serbian', value: 'sr' },
      { name: 'Spanish', value: 'es' },
      { name: 'Swedish', value: 'sv' },
      { name: 'Turkish', value: 'tr' },
      { name: 'Tamil', value: 'ta' },
      { name: 'Telugu', value: 'te' },
      { name: 'Thai', value: 'th' },
      { name: 'Ukrainian', value: 'ua' },
      { name: 'Uzbek (Cyrillic)', value: 'uz-Cyrl-uz' },
      { name: 'Uzbek (Latin)', value: 'uz-Latn-uz' },
      { name: 'Vietnamese', value: 'vi' }
    ];
  }


ngOnInit(): void {
    this.newGenerator();
    this.languageOptions = GeneratorComponent.getAllSupportedLanguageOptions();
    this.gitConfig = this.gitConfigurationService.gitConfig;
    if (this.gitConfig) {
      this.gitlabConfigured = this.gitConfig.gitlabConfigured || false;
      this.githubConfigured = this.gitConfig.githubConfigured || false;
    }
    this.gitConfigurationService.sharedData.subscribe((gitConfig: GitConfigurationModel) => {
      if (gitConfig) {
        this.gitlabConfigured = gitConfig.gitlabConfigured || false;
        this.githubConfigured = gitConfig.githubConfigured || false;
      }
    });
  }


updateSharedData(data: any): void {
    this.selectedGitProvider = data.selectedGitProvider;
    this.selectedGitCompany = data.selectedGitCompany;
  }


checkModelBeforeSubmit(): void {
    this.submitted = true;

    if (this.model.cacheProvider === 'no') {
      this.model.enableHibernateCache = false;
    }
    if (this.model.websocket) {
      this.model.websocket = 'spring-websocket';
    }
    if (this.model.searchEngine) {
      this.model.searchEngine = 'elasticsearch';
    }
    if (this.model.enableSwaggerCodegen) {
      this.model.enableSwaggerCodegen = 'true';
    }
    if (this.model.messageBroker) {
      this.model.messageBroker = 'kafka';
    }
    if (this.model.enableTranslation && !this.model.languages.includes(this.model.nativeLanguage)) {
      this.model.languages.push(this.model.nativeLanguage);
    }
    this.model.jhiPrefix = 'jhi';
  }


onSubmit(): void {
    this.checkModelBeforeSubmit();

    if (this.selectedGitProvider && this.selectedGitCompany && this.repositoryName) {
      this.generatorService.generateOnGit(this.model, this.selectedGitProvider, this.selectedGitCompany, this.repositoryName).subscribe(
        (res: any) => {
          this.openOutputModal(res);
          this.submitted = false;
        },
        (error: any) => {
          console.error('Error generating the application.');
          console.error(error);
        }
      );
    }
  }


onSubmitDownload(): void {
    this.checkModelBeforeSubmit();
    this.generatorService.download(this.model).subscribe(
      (data: any) => this.downloadFile(data.body),
      // eslint-disable-next-line no-console
      (error: any) => console.log(error),
      () => {
        // eslint-disable-next-line no-console
        console.log('Application downloaded');
        this.submitted = false;
      }
    );
  }


openOutputModal(applicationId: string): void {
    const modalRef = this.modalService.open(GeneratorOutputDialogComponent, { size: 'lg', backdrop: 'static' }).componentInstance;

    modalRef.applicationId = applicationId;
    modalRef.selectedGitProvider = this.selectedGitProvider;
    modalRef.selectedGitCompany = this.selectedGitCompany;
    modalRef.repositoryName = this.repositoryName;
    modalRef.gitlabHost = this.gitConfig!.gitlabHost;
    modalRef.githubHost = this.gitConfig!.githubHost;
    modalRef.gitlabConfigured = this.gitConfig!.gitlabAvailable;
    modalRef.githubConfigured = this.gitConfig!.githubAvailable;
  }


downloadFile(blob: Blob): void {
    const a = document.createElement('a'),
      fileURL = URL.createObjectURL(blob);

    a.href = fileURL;
    a.download = this.model.baseName + '.zip';
    window.document.body.appendChild(a);
    a.click();
    window.document.body.removeChild(a);
    URL.revokeObjectURL(fileURL);
  }


newGenerator(): void {
    this.model = new JHipsterConfigurationModel();
    this.repositoryName = 'jhipster-sample-application';
  }


changeApplicationType(): void {
    // server port
    if (this.model.applicationType === 'microservice') {
      this.model.serverPort = 8081;
    } else if (this.model.applicationType === 'uaa') {
      this.model.serverPort = 9999;
    } else {
      this.model.serverPort = 8080;
    }
    // authentication
    if (this.model.applicationType !== 'microservice') {
      this.model.authenticationType = 'jwt';
    }
    // service discovery
    if (this.model.applicationType === 'gateway' || this.model.applicationType === 'microservice') {
      this.model.serviceDiscoveryType = 'eureka';
    }
    // database
    if (this.model.databaseType === 'no') {
      this.model.databaseType = 'sql';
      this.changeDatabaseType();
    }
    // cache
    if (this.model.applicationType === 'microservice') {
      this.model.cacheProvider = 'hazelcast';
      this.model.enableHibernateCache = true;
    }
  }


changePackageName(): void {
    this.model.packageFolder = this.model.packageName.replace(/\./g, '/');
  }


changeServiceDiscoveryType(): void {
    if (this.model.serviceDiscoveryType === 'eureka') {
      this.model.authenticationType = 'jwt';
    }
    if (this.model.serviceDiscoveryType === 'false') {
      this.model.serviceDiscoveryType = false;
    }
  }


changeAuthenticationType(): void {
    this.model.databaseType = 'sql';
    this.model.clientFramework = 'angularX';
    this.changeDatabaseType();
  }


changeDatabaseType(): void {
    if (this.model.databaseType === 'sql') {
      this.model.devDatabaseType = 'h2Disk';
      this.model.prodDatabaseType = 'mysql';
      this.model.cacheProvider = 'ehcache';
      this.model.enableHibernateCache = true;
    } else if (this.model.databaseType === 'mongodb') {
      this.model.devDatabaseType = 'mongodb';
      this.model.prodDatabaseType = 'mongodb';
      this.model.cacheProvider = 'no';
      this.model.enableHibernateCache = false;
    } else if (this.model.databaseType === 'cassandra') {
      this.model.devDatabaseType = 'cassandra';
      this.model.prodDatabaseType = 'cassandra';
      this.model.cacheProvider = 'no';
      this.model.enableHibernateCache = false;
      this.model.searchEngine = false;
    } else if (this.model.databaseType === 'couchbase') {
      this.model.devDatabaseType = 'couchbase';
      this.model.prodDatabaseType = 'couchbase';
      this.model.cacheProvider = 'no';
      this.model.enableHibernateCache = false;
      this.model.searchEngine = false;
    } else if (this.model.databaseType === 'no') {
      this.model.devDatabaseType = 'no';
      this.model.prodDatabaseType = 'no';
      this.model.cacheProvider = 'no';
      this.model.enableHibernateCache = false;
      this.model.searchEngine = false;
    }
  }


changeProdDatabaseType(): void {
    if (this.model.devDatabaseType === this.model.prodDatabaseType) {
      return;
    }
    if (this.model.databaseType === 'sql') {
      this.model.devDatabaseType = 'h2Disk';
    } else if (this.model.prodDatabaseType === 'mongodb') {
      this.model.devDatabaseType = 'mongodb';
      this.model.cacheProvider = 'no';
      this.model.enableHibernateCache = false;
    } else if (this.model.prodDatabaseType === 'cassandra') {
      this.model.devDatabaseType = 'cassandra';
      this.model.cacheProvider = 'no';
      this.model.enableHibernateCache = false;
    } else if (this.model.prodDatabaseType === 'no') {
      this.model.devDatabaseType = 'no';
      this.model.cacheProvider = 'no';
      this.model.enableHibernateCache = false;
    }
  }


() => {
  let component: NetworkAddDialog;
  let fixture: ComponentFixture<NetworkAddDialog>;

  beforeEach(async(() => {
    TestBed.configureTestingModule({
      declarations: [ NetworkAddDialog ]
    })
    .compileComponents();
  }));

  beforeEach(() => {
    fixture = TestBed.createComponent(NetworkAddDialog);
    component = fixture.componentInstance;
    fixture.detectChanges();
  });

  it('should create', () => {
    expect(component).toBeTruthy();
  });
}


() => {
    TestBed.configureTestingModule({
      declarations: [ NetworkAddDialog ]
    })
    .compileComponents();
  }


() => {
    fixture = TestBed.createComponent(NetworkAddDialog);
    component = fixture.componentInstance;
    fixture.detectChanges();
  }


(req: express.Request, res: express.Response) => {
  res.render("index", { title: "Express" });
}


@Entity({
    name: DefaultEntityTables.TABLE_LANGUAGES,
})
@ObjectType()
export class LanguagesModel extends DefaultUserLessEntityModel {

    @Field({ nullable: true })
    @Column()
    name: string;

    @Field({ nullable: true })
    @Column('longtext', { nullable: true })
    description: string;

    @Field({ nullable: true })
    @Column('boolean', { default: false })
    isActive: boolean;

    @Field({ nullable: true })
    @Column('boolean', { default: false, unique: true })
    isDefault: boolean;

    @Field({ nullable: true })
    @Column({ length: 2, nullable: true})
    code: string;

    @Field({ nullable: true })
    @Column({ unique: true, length: 5 })
    codeLong: string;

    @Field({ nullable: true })
    @Column({ length: 2, nullable: true })
    codeLocale: string;

    @Field({ nullable: true })
    @Column({ length: 6, nullable: true })
    codeCulture: string;

    @Field({ nullable: true })
    @Column({ length: 3, nullable: true })
    Iso639xValue: string;
}


({
  label,
  tooltip,
  labelWidth,
  inputWidth,
  inputEl,
  className,
  ...inputProps
}) => {
  return (
    <div className={cx('form-field', className)}>
      <FormLabel width={labelWidth} tooltip={tooltip}>
        {label}
      </FormLabel>
      {inputEl || (
        <input type="text" className={`gf-form-input ${inputWidth ? `width-${inputWidth}` : ''}`} {...inputProps} />


export interface Props extends InputHTMLAttributes<HTMLInputElement> {
  label: any;
  tooltip?: PopoverContent;
  labelWidth?: number;
  // If null no width will be specified not even default one
  inputWidth?: number | null;
  inputEl?: React.ReactNode;
}


cx('form-field', className)


export interface IPeopleApiPerson {
  personId: string;
  metadata: IPeopleApiPersonMetaData;
  name?: IPeopleApiPersonName[];
  photo?: IPeopleApiPersonPhoto[];
  email?: IPeopleApiPersonEmail[];
  organization?: IPeopleApiPersonOrganization[];
  location?: IPeopleApiPersonLocation[];
  socialConnection?: IPeopleApiPersonSocialConnection[];
  readOnlyProfileInfo?: IPeopleApiPersonReadOnlyProfileInfo[];
  profileUrlRepeated?: IPeopleApiPersonProfileUrlRepeated[];
}


export interface IPeopleApiResponseInnerResult {
  suggestion: string;
  objectType: string;
  person: IPeopleApiPerson;
}


export interface IPeopleApiAutoCompleteResponse {
  result: IPeopleApiResponseOuterResult;
  body: string;
  headers: IPeopleApiResponseHeaders;
  status: number;
  statusText?: string;
}


export interface IPeopleApiErrorResponse {
  code: number;
  message: string;
  status: string;
  details: IPeopleApiErrorDetail[];
}


interface IPeopleApiErrorDetail {
  '@type': string;
  detail: string;
}


interface IPeopleApiAffinity {
  loggingId: string;
}


interface IPeopleApiIdentityInfo {
  sourceIds: IPeopleApiSourceId[];
}


interface IPeopleApiPersonEmail {
  metaData: IPeopleApiPersonFieldMetaData;
  value: string;
}


interface IPeopleApiPersonFieldMetaData {
  encodedContainerId: string;
  containerType: string;
}


interface IPeopleApiPersonLocation {
  value: string;
}


interface IPeopleApiPersonName {
  metadata: IPeopleApiPersonFieldMetaData;
  displayName: string;
  givenName: string;
}


interface IPeopleApiPersonOrganization {
  metaData: IPeopleApiPersonFieldMetaData;
  title: string;
  stringType: string;
}


interface IPeopleApiPersonPhoto {
  metaData: IPeopleApiPersonFieldMetaData;
  url: string;
}


interface IPeopleApiPersonProfileUrlRepeated {
  metaData: IPeopleApiPersonFieldMetaData;
  url: string;
}


interface IPeopleApiPersonReadOnlyProfileInfo {
  ownerId: string;
  ownerUserType: string[];
  inViewerDomain: boolean;
}


interface IPeopleApiPersonSocialConnection {
  metaData: IPeopleApiPersonFieldMetaData;
  type: string[];
}