import { inject, TestBed } from '@angular/core/testing';
import {
  FormArray,
  FormControl,
  FormGroup,
  NG_ASYNC_VALIDATORS,
  NG_VALIDATORS,
  ReactiveFormsModule
} from '@angular/forms';
import {
  DynamicCheckboxGroupModel,
  DynamicCheckboxModel,
  DynamicColorPickerModel,
  DynamicDatePickerModel,
  DynamicEditorModel,
  DynamicFileUploadModel,
  DynamicFormArrayModel,
  DynamicFormControlModel,
  DynamicFormGroupModel, DynamicFormValidationService,
  DynamicFormValueControlModel,
  DynamicInputModel,
  DynamicRadioGroupModel,
  DynamicRatingModel,
  DynamicSelectModel,
  DynamicSliderModel,
  DynamicSwitchModel,
  DynamicTextAreaModel,
  DynamicTimePickerModel
} from '@ng-dynamic-forms/core';
import { DynamicTagModel } from './ds-dynamic-form-ui/models/tag/dynamic-tag.model';
import { DynamicListCheckboxGroupModel } from './ds-dynamic-form-ui/models/list/dynamic-list-checkbox-group.model';
import { DynamicQualdropModel } from './ds-dynamic-form-ui/models/ds-dynamic-qualdrop.model';
import { DynamicScrollableDropdownModel } from './ds-dynamic-form-ui/models/scrollable-dropdown/dynamic-scrollable-dropdown.model';
import { DynamicRelationGroupModel } from './ds-dynamic-form-ui/models/relation-group/dynamic-relation-group.model';
import { DynamicLookupModel } from './ds-dynamic-form-ui/models/lookup/dynamic-lookup.model';
import { DynamicDsDatePickerModel } from './ds-dynamic-form-ui/models/date-picker/date-picker.model';
import { DynamicTypeaheadModel } from './ds-dynamic-form-ui/models/typeahead/dynamic-typeahead.model';
import { DynamicListRadioGroupModel } from './ds-dynamic-form-ui/models/list/dynamic-list-radio-group.model';
import { AuthorityOptions } from '../../../core/integration/models/authority-options.model';
import { FormFieldModel } from './models/form-field.model';
import {
  SubmissionFormsModel
} from '../../../core/config/models/config-submission-forms.model';
import { FormBuilderService } from './form-builder.service';
import { DynamicRowGroupModel } from './ds-dynamic-form-ui/models/ds-dynamic-row-group-model';
import { DsDynamicInputModel } from './ds-dynamic-form-ui/models/ds-dynamic-input.model';
import { FormFieldMetadataValueObject } from './models/form-field-metadata-value.model';
import { DynamicConcatModel } from './ds-dynamic-form-ui/models/ds-dynamic-concat.model';
import { DynamicLookupNameModel } from './ds-dynamic-form-ui/models/lookup/dynamic-lookup-name.model';
import { DynamicRowArrayModel } from './ds-dynamic-form-ui/models/ds-dynamic-row-array-model';
import { FormRowModel } from '../../../core/config/models/config-submission-form.model';

describe('FormBuilderService test suite', () => {

  let testModel: DynamicFormControlModel[];
  let testFormConfiguration: SubmissionFormsModel;
  let service: FormBuilderService;

  const submissionId =  '1234';

  function testValidator() {
    return {testValidator: {valid: true}};
  }

  function testAsyncValidator() {
    return new Promise<boolean>((resolve) => setTimeout(() => resolve(true), 0));
  }

  beforeEach(() => {

    TestBed.configureTestingModule({
      imports: [ReactiveFormsModule],
      providers: [
        {provide: FormBuilderService, useClass: FormBuilderService},
        {provide: DynamicFormValidationService, useValue: {}},
        {provide: NG_VALIDATORS, useValue: testValidator, multi: true},
        {provide: NG_ASYNC_VALIDATORS, useValue: testAsyncValidator, multi: true}
      ]
    });

    const authorityOptions: AuthorityOptions = {
      closed: false,
      metadata: 'list',
      name: 'type_programme',
      scope: 'c1c16450-d56f-41bc-bb81-27f1d1eb5c23'
    };

    testModel = [

      new DynamicSelectModel<string>(
        {
          id: 'testSelect',
          options: [
            {
              label: 'Option 1',
              value: 'option-1'
            },
            {
              label: 'Option 2',
              value: 'option-2'
            }
          ],
          value: 'option-3'
        }
      ),

      new DynamicInputModel(
        {
          id: 'testInput',
          mask: ['(', /[1-9]/, /\d/, /\d/, ')', ' ', /\d/, /\d/, /\d/, '-', /\d/, /\d/, /\d/, /\d/],
        }
      ),

      new DynamicCheckboxGroupModel(
        {
          id: 'testCheckboxGroup',
          group: [
            new DynamicCheckboxModel(
              {
                id: 'testCheckboxGroup1',
                value: true
              }
            ),
            new DynamicCheckboxModel(
              {
                id: 'testCheckboxGroup2',
                value: true
              }
            )
          ]
        }
      ),

      new DynamicRadioGroupModel<string>(
        {
          id: 'testRadioGroup',
          options: [
            {
              label: 'Option 1',
              value: 'option-1'
            },
            {
              label: 'Option 2',
              value: 'option-2'
            }
          ],
          value: 'option-3'
        }
      ),

      new DynamicTextAreaModel({id: 'testTextArea'}),

      new DynamicCheckboxModel({id: 'testCheckbox'}),

      new DynamicFormArrayModel(
        {
          id: 'testFormArray',
          initialCount: 5,
          groupFactory: () => {
            return [
              new DynamicInputModel({id: 'testFormArrayGroupInput'}),
              new DynamicFormArrayModel({
                id: 'testNestedFormArray', groupFactory: () => [
                  new DynamicInputModel({id: 'testNestedFormArrayGroupInput'})
                ]
              })
            ];
          }
        }
      ),

      new DynamicFormGroupModel(
        {
          id: 'testFormGroup',
          group: [
            new DynamicInputModel({id: 'nestedTestInput'}),
            new DynamicTextAreaModel({id: 'nestedTestTextArea'})
          ]
        }
      ),

      new DynamicSliderModel({id: 'testSlider'}),

      new DynamicSwitchModel({id: 'testSwitch'}),

      new DynamicDatePickerModel({id: 'testDatepicker', value: new Date()}),

      new DynamicFileUploadModel({id: 'testFileUpload'}),

      new DynamicEditorModel({id: 'testEditor'}),

      new DynamicTimePickerModel({id: 'testTimePicker'}),

      new DynamicRatingModel({id: 'testRating'}),

      new DynamicColorPickerModel({id: 'testColorPicker'}),

      new DynamicTypeaheadModel({id: 'testTypeahead', repeatable: false, metadataFields: [], submissionId: '1234'}),

      new DynamicScrollableDropdownModel({id: 'testScrollableDropdown', authorityOptions: authorityOptions, repeatable: false, metadataFields: [], submissionId: '1234'}),

      new DynamicTagModel({id: 'testTag', repeatable: false, metadataFields: [], submissionId: '1234'}),

      new DynamicListCheckboxGroupModel({id: 'testCheckboxList', authorityOptions: authorityOptions, repeatable: true}),

      new DynamicListRadioGroupModel({id: 'testRadioList', authorityOptions: authorityOptions, repeatable: false}),

      new DynamicRelationGroupModel({
        submissionId,
        id: 'testRelationGroup',
        formConfiguration: [{
          fields: [{
            hints: 'Enter the name of the author.',
            input: {type: 'onebox'},
            label: 'Authors',
            languageCodes: [],
            mandatory: 'true',
            mandatoryMessage: 'Required field!',
            repeatable: false,
            selectableMetadata: [{
              authority: 'RPAuthority',
              closed: false,
              metadata: 'dc.contributor.author'
            }],
          } as FormFieldModel]
        } as FormRowModel, {
          fields: [{
            hints: 'Enter the affiliation of the author.',
            input: {type: 'onebox'},
            label: 'Affiliation',
            languageCodes: [],
            mandatory: 'false',
            repeatable: false,
            selectableMetadata: [{
              authority: 'OUAuthority',
              closed: false,
              metadata: 'local.contributor.affiliation'
            }]
          } as FormFieldModel]
        } as FormRowModel],
        mandatoryField: '',
        name: 'testRelationGroup',
        relationFields: [],
        scopeUUID: '',
        submissionScope: '',
        repeatable: false,
        metadataFields: []
      }),

      new DynamicDsDatePickerModel({id: 'testDate'}),

      new DynamicLookupModel({id: 'testLookup', repeatable: false, metadataFields: [], submissionId: '1234'}),

      new DynamicLookupNameModel({id: 'testLookupName', repeatable: false, metadataFields: [], submissionId: '1234'}),

      new DynamicQualdropModel({id: 'testCombobox', readOnly: false, required: false}),

      new DynamicRowArrayModel(
        {
          id: 'testFormRowArray',
          initialCount: 5,
          notRepeatable: false,
          groupFactory: () => {
            return [
              new DynamicInputModel({id: 'testFormRowArrayGroupInput'})
            ];
          },
          required: false
        }
      ),
    ];

    testFormConfiguration = {
      name: 'testFormConfiguration',
      rows: [
        {
          fields: [
            {
              input: { type: 'lookup' },
              label: 'Journal',
              mandatory: 'false',
              repeatable: false,
              hints: 'Enter the name of the journal where the item has been\n\t\t\t\t\tpublished, if any.',
              selectableMetadata: [
                {
                  metadata: 'journal',
                  authority: 'JOURNALAuthority',
                  closed: false
                }
              ],
              languageCodes: []
            } as FormFieldModel,
            {
              input: { type: 'onebox' },
              label: 'Issue',
              mandatory: 'false',
              repeatable: false,
              hints: ' Enter issue number.',
              selectableMetadata: [
                {
                  metadata: 'issue'
                }
              ],
              languageCodes: []
            } as FormFieldModel,
            {
              input: { type: 'name' },
              label: 'Name',
              mandatory: 'false',
              repeatable: false,
              hints: 'Enter full name.',
              selectableMetadata: [
                {
                  metadata: 'name'
                }
              ],
              languageCodes: []
            } as FormFieldModel
          ]
        } as FormRowModel,
        {
          fields: [
            {
              hints: 'If the item has any identification numbers or codes associated with↵	it, please enter the types and the actual numbers or codes.',
              input: { type: 'onebox' },
              label: 'Identifiers',
              languageCodes: [],
              mandatory: 'false',
              repeatable: false,
              selectableMetadata: [
                { metadata: 'dc.identifier.issn', label: 'ISSN' },
                { metadata: 'dc.identifier.other', label: 'Other' },
                { metadata: 'dc.identifier.ismn', label: 'ISMN' },
                { metadata: 'dc.identifier.govdoc', label: 'Gov\'t Doc #' },
                { metadata: 'dc.identifier.uri', label: 'URI' },
                { metadata: 'dc.identifier.isbn', label: 'ISBN' },
                { metadata: 'dc.identifier.doi', label: 'DOI' },
                { metadata: 'dc.identifier.pmid', label: 'PubMed ID' },
                { metadata: 'dc.identifier.arxiv', label: 'arXiv' }
              ]
            }, {
              input: { type: 'onebox' },
              label: 'Publisher',
              mandatory: 'false',
              repeatable: false,
              hints: 'Enter the name of the publisher of the previously issued instance of this item.',
              selectableMetadata: [
                {
                  metadata: 'publisher'
                }
              ],
              languageCodes: []
            }
          ]
        } as FormRowModel,
        {
          fields: [
            {
              input: { type: 'onebox' },
              label: 'Conference',
              mandatory: 'false',
              repeatable: false,
              hints: 'Enter the name of the events, if any.',
              selectableMetadata: [
                {
                  metadata: 'conference',
                  authority: 'EVENTAuthority',
                  closed: false
                }
              ],
              languageCodes: []
            }
          ]
        } as FormRowModel
      ],
      self: {
        href: 'testFormConfiguration.url'
      },
      type: 'submissionform',
      _links: {
        self: {
          href: 'testFormConfiguration.url'
        }
      }
    } as any;
  });

  beforeEach(inject([FormBuilderService], (formService: FormBuilderService) => service = formService));

  it('should find a dynamic form control model by id', () => {

    expect(service.findById('testCheckbox', testModel) instanceof DynamicFormControlModel).toBe(true);
    expect(service.findById('testCheckboxGroup', testModel) instanceof DynamicFormControlModel).toBe(true);
    expect(service.findById('testDatepicker', testModel) instanceof DynamicFormControlModel).toBe(true);
    expect(service.findById('testFormArray', testModel) instanceof DynamicFormControlModel).toBe(true);
    expect(service.findById('testInput', testModel) instanceof DynamicFormControlModel).toBe(true);
    expect(service.findById('testRadioGroup', testModel) instanceof DynamicFormControlModel).toBe(true);
    expect(service.findById('testSelect', testModel) instanceof DynamicFormControlModel).toBe(true);
    expect(service.findById('testSlider', testModel) instanceof DynamicFormControlModel).toBe(true);
    expect(service.findById('testSwitch', testModel) instanceof DynamicFormControlModel).toBe(true);
    expect(service.findById('testTextArea', testModel) instanceof DynamicFormControlModel).toBe(true);
    expect(service.findById('testFileUpload', testModel) instanceof DynamicFormControlModel).toBe(true);
    expect(service.findById('testEditor', testModel) instanceof DynamicEditorModel).toBe(true);
    expect(service.findById('testTimePicker', testModel) instanceof DynamicTimePickerModel).toBe(true);
    expect(service.findById('testRating', testModel) instanceof DynamicRatingModel).toBe(true);
    expect(service.findById('testColorPicker', testModel) instanceof DynamicColorPickerModel).toBe(true);
  });

  it('should find a nested dynamic form control model by id', () => {

    expect(service.findById('testCheckboxGroup1', testModel) instanceof DynamicFormControlModel).toBe(true);
    expect(service.findById('testCheckboxGroup2', testModel) instanceof DynamicFormControlModel).toBe(true);
    expect(service.findById('nestedTestInput', testModel) instanceof DynamicFormControlModel).toBe(true);
    expect(service.findById('testFormRowArrayGroupInput', testModel) instanceof DynamicFormControlModel).toBe(true);
    expect(service.findById('testFormRowArrayGroupInput', testModel, 2) instanceof DynamicFormControlModel).toBe(true);
  });

  it('should create an array of form models', () => {
    const formModel = service.modelFromConfiguration(submissionId, testFormConfiguration, 'testScopeUUID');

    expect(formModel[0] instanceof DynamicRowGroupModel).toBe(true);
    expect((formModel[0] as DynamicRowGroupModel).group.length).toBe(3);
    expect((formModel[0] as DynamicRowGroupModel).get(0) instanceof DynamicLookupModel).toBe(true);
    expect((formModel[0] as DynamicRowGroupModel).get(1) instanceof DsDynamicInputModel).toBe(true);
    expect((formModel[0] as DynamicRowGroupModel).get(2) instanceof DynamicConcatModel).toBe(true);

    expect(formModel[1] instanceof DynamicRowGroupModel).toBe(true);
    expect((formModel[1] as DynamicRowGroupModel).group.length).toBe(2);
    expect((formModel[1] as DynamicRowGroupModel).get(0) instanceof DynamicQualdropModel).toBe(true);
    expect(((formModel[1] as DynamicRowGroupModel).get(0) as DynamicQualdropModel).get(0) instanceof DynamicSelectModel).toBe(true);
    expect(((formModel[1] as DynamicRowGroupModel).get(0) as DynamicQualdropModel).get(1) instanceof DsDynamicInputModel).toBe(true);
    expect((formModel[1] as DynamicRowGroupModel).get(1) instanceof DsDynamicInputModel).toBe(true);

    expect(formModel[2] instanceof DynamicRowGroupModel).toBe(true);
    expect((formModel[2] as DynamicRowGroupModel).group.length).toBe(1);
    expect((formModel[2] as DynamicRowGroupModel).get(0) instanceof DynamicTypeaheadModel).toBe(true);
  });

  it('should return form\'s fields value from form model', () => {
    const formModel = service.modelFromConfiguration(submissionId, testFormConfiguration, 'testScopeUUID');
    let value = {} as any;

    expect(service.getValueFromModel(formModel)).toEqual(value);

    ((formModel[0] as DynamicRowGroupModel).get(1) as DsDynamicInputModel).valueUpdates.next('test');

    value = {
      issue: [new FormFieldMetadataValueObject('test')]
    };
    expect(service.getValueFromModel(formModel)).toEqual(value);

    ((formModel[2] as DynamicRowGroupModel).get(0) as DynamicTypeaheadModel).valueUpdates.next('test one');
    value = {
      issue: [new FormFieldMetadataValueObject('test')],
      conference: [new FormFieldMetadataValueObject('test one')]
    };
    expect(service.getValueFromModel(formModel)).toEqual(value);
  });

  it('should clear all form\'s fields value', () => {
    const formModel = service.modelFromConfiguration(submissionId, testFormConfiguration, 'testScopeUUID');
    const value = {} as any;

    ((formModel[0] as DynamicRowGroupModel).get(1) as DsDynamicInputModel).valueUpdates.next('test');
    ((formModel[2] as DynamicRowGroupModel).get(0) as DynamicTypeaheadModel).valueUpdates.next('test one');

    service.clearAllModelsValue(formModel);
    expect(((formModel[0] as DynamicRowGroupModel).get(1) as DynamicTypeaheadModel).value).toEqual(undefined)
    expect(((formModel[2] as DynamicRowGroupModel).get(0) as DynamicTypeaheadModel).value).toEqual(undefined)
  });

  it('should return true when model has a custom group model as parent', () => {
    const formModel = service.modelFromConfiguration(submissionId, testFormConfiguration, 'testScopeUUID');
    let model = service.findById('dc_identifier_QUALDROP_VALUE', formModel);
    let modelParent = service.findById('dc_identifier_QUALDROP_GROUP', formModel);
    model.parent = modelParent;

    expect(service.isModelInCustomGroup(model)).toBe(true);

    model = service.findById('name_CONCAT_FIRST_INPUT', formModel);
    modelParent = service.findById('name_CONCAT_GROUP', formModel);
    model.parent = modelParent;

    expect(service.isModelInCustomGroup(model)).toBe(true);
  });

  it('should return true when model value is an array', () => {
    let model = service.findById('testCheckboxList', testModel) as DynamicFormArrayModel;

    expect(service.hasArrayGroupValue(model)).toBe(true);

    model = service.findById('testRadioList', testModel) as DynamicFormArrayModel;

    expect(service.hasArrayGroupValue(model)).toBe(true);

    model = service.findById('testTag', testModel) as DynamicFormArrayModel;

    expect(service.hasArrayGroupValue(model)).toBe(true);
  });

  it('should return true when model value is a map', () => {
    const formModel = service.modelFromConfiguration(submissionId, testFormConfiguration, 'testScopeUUID');
    const model = service.findById('dc_identifier_QUALDROP_VALUE', formModel);
    const modelParent = service.findById('dc_identifier_QUALDROP_GROUP', formModel);
    model.parent = modelParent;

    expect(service.hasMappedGroupValue(model)).toBe(true);
  });

  it('should return true when model is a Qualdrop Group', () => {
    const formModel = service.modelFromConfiguration(submissionId, testFormConfiguration, 'testScopeUUID');
    let model = service.findById('dc_identifier_QUALDROP_GROUP', formModel);

    expect(service.isQualdropGroup(model)).toBe(true);

    model = service.findById('name_CONCAT_GROUP', formModel);

    expect(service.isQualdropGroup(model)).toBe(false);
  });

  it('should return true when model is a Custom or List Group', () => {
    const formModel = service.modelFromConfiguration(submissionId, testFormConfiguration, 'testScopeUUID');
    let model = service.findById('dc_identifier_QUALDROP_GROUP', formModel);

    expect(service.isCustomOrListGroup(model)).toBe(true);

    model = service.findById('name_CONCAT_GROUP', formModel);

    expect(service.isCustomOrListGroup(model)).toBe(true);

    model = service.findById('testCheckboxList', testModel);

    expect(service.isCustomOrListGroup(model)).toBe(true);

    model = service.findById('testRadioList', testModel);

    expect(service.isCustomOrListGroup(model)).toBe(true);
  });

  it('should return true when model is a Custom Group', () => {
    const formModel = service.modelFromConfiguration(submissionId, testFormConfiguration, 'testScopeUUID');
    let model = service.findById('dc_identifier_QUALDROP_GROUP', formModel);

    expect(service.isCustomGroup(model)).toBe(true);

    model = service.findById('name_CONCAT_GROUP', formModel);

    expect(service.isCustomGroup(model)).toBe(true);

    model = service.findById('testCheckboxList', testModel);

    expect(service.isCustomGroup(model)).toBe(false);

    model = service.findById('testRadioList', testModel);

    expect(service.isCustomGroup(model)).toBe(false);
  });

  it('should return true when model is a List Group', () => {
    let model = service.findById('testCheckboxList', testModel);

    expect(service.isListGroup(model)).toBe(true);

    model = service.findById('testRadioList', testModel);

    expect(service.isListGroup(model)).toBe(true);
  });

  it('should return true when model is a Relation Group', () => {
    const model = service.findById('testRelationGroup', testModel);

    expect(service.isRelationGroup(model)).toBe(true);
  });

  it('should return true when model is a Array Row Group', () => {
    let model = service.findById('testFormRowArray', testModel, null);

    expect(service.isRowArrayGroup(model)).toBe(true);

    model = service.findById('testFormArray', testModel);

    expect(service.isRowArrayGroup(model)).toBe(false);
  });

  it('should return true when model is a Array Group', () => {
    let model = service.findById('testFormRowArray', testModel) as DynamicFormArrayModel;

    expect(service.isArrayGroup(model)).toBe(true);

    model = service.findById('testFormArray', testModel) as DynamicFormArrayModel;

    expect(service.isArrayGroup(model)).toBe(true);
  });

  it('should return properly form control by field id', () => {
    const group = service.createFormGroup(testModel);
    const control = group.controls.testLookup;

    expect(service.getFormControlById('testLookup', group, testModel)).toEqual(control);
  });

  it('should return field id from model', () => {
    const model = service.findById('testRadioList', testModel);

    expect(service.getId(model)).toEqual('testRadioList');
  });

  it('should create a form group', () => {

    const formGroup = service.createFormGroup(testModel);

    expect(formGroup instanceof FormGroup).toBe(true);

    expect(formGroup.get('testCheckbox') instanceof FormControl).toBe(true);
    expect(formGroup.get('testCheckboxGroup') instanceof FormGroup).toBe(true);
    expect(formGroup.get('testDatepicker') instanceof FormControl).toBe(true);
    expect(formGroup.get('testFormArray') instanceof FormArray).toBe(true);
    expect(formGroup.get('testInput') instanceof FormControl).toBe(true);
    expect(formGroup.get('testRadioGroup') instanceof FormControl).toBe(true);
    expect(formGroup.get('testSelect') instanceof FormControl).toBe(true);
    expect(formGroup.get('testTextArea') instanceof FormControl).toBe(true);
    expect(formGroup.get('testFileUpload') instanceof FormControl).toBe(true);
    expect(formGroup.get('testEditor') instanceof FormControl).toBe(true);
    expect(formGroup.get('testTimePicker') instanceof FormControl).toBe(true);
    expect(formGroup.get('testRating') instanceof FormControl).toBe(true);
    expect(formGroup.get('testColorPicker') instanceof FormControl).toBe(true);
  });

  it('should throw when unknown DynamicFormControlModel id is specified in JSON', () => {

    expect(() => service.fromJSON([{id: 'test'}]))
      .toThrow(new Error(`unknown form control model type defined on JSON object with id "test"`));
  });

  it('should resolve array group path', () => {

    service.createFormGroup(testModel);

    const model = service.findById('testFormArray', testModel) as DynamicFormArrayModel;
    const nestedModel = (model.get(0).get(1) as DynamicFormArrayModel).get(0);

    expect(service.getPath(model)).toEqual(['testFormArray']);
    expect(service.getPath(nestedModel)).toEqual(['testFormArray', '0', 'testNestedFormArray', '0']);
  });

  it('should add a form control to an existing form group', () => {

    const formGroup = service.createFormGroup(testModel);
    const nestedFormGroup = formGroup.controls.testFormGroup as FormGroup;
    const nestedFormGroupModel = testModel[7] as DynamicFormGroupModel;
    const newModel1 = new DynamicInputModel({id: 'newInput1'});
    const newModel2 = new DynamicInputModel({id: 'newInput2'});

    service.addFormGroupControl(formGroup, testModel, newModel1);
    service.addFormGroupControl(nestedFormGroup, nestedFormGroupModel, newModel2);

    expect(formGroup.controls[newModel1.id]).toBeTruthy();
    expect(testModel[testModel.length - 1] === newModel1).toBe(true);

    expect((formGroup.controls.testFormGroup as FormGroup).controls[newModel2.id]).toBeTruthy();
    expect(nestedFormGroupModel.get(nestedFormGroupModel.group.length - 1) === newModel2).toBe(true);
  });

  it('should insert a form control to an existing form group', () => {

    const formGroup = service.createFormGroup(testModel);
    const nestedFormGroup = formGroup.controls.testFormGroup as FormGroup;
    const nestedFormGroupModel = testModel[7] as DynamicFormGroupModel;
    const newModel1 = new DynamicInputModel({id: 'newInput1'});
    const newModel2 = new DynamicInputModel({id: 'newInput2'});

    service.insertFormGroupControl(4, formGroup, testModel, newModel1);
    service.insertFormGroupControl(0, nestedFormGroup, nestedFormGroupModel, newModel2);

    expect(formGroup.controls[newModel1.id]).toBeTruthy();
    expect(testModel[4] === newModel1).toBe(true);
    expect(service.getPath(testModel[4])).toEqual(['newInput1']);

    expect((formGroup.controls.testFormGroup as FormGroup).controls[newModel2.id]).toBeTruthy();
    expect(nestedFormGroupModel.get(0) === newModel2).toBe(true);
    expect(service.getPath(nestedFormGroupModel.get(0))).toEqual(['testFormGroup', 'newInput2']);
  });

  it('should move an existing form control to a different group position', () => {

    const formGroup = service.createFormGroup(testModel);
    const nestedFormGroupModel = testModel[7] as DynamicFormGroupModel;
    const model1 = testModel[0];
    const model2 = nestedFormGroupModel.get(0);

    service.moveFormGroupControl(0, 2, testModel);

    expect(formGroup.controls[model1.id]).toBeTruthy();
    expect(testModel[2] === model1).toBe(true);

    service.moveFormGroupControl(0, 1, nestedFormGroupModel);

    expect((formGroup.controls.testFormGroup as FormGroup).controls[model2.id]).toBeTruthy();
    expect(nestedFormGroupModel.get(1) === model2).toBe(true);
  });

  it('should remove a form control from an existing form group', () => {

    const formGroup = service.createFormGroup(testModel);
    const nestedFormGroup = formGroup.controls.testFormGroup as FormGroup;
    const nestedFormGroupModel = testModel[7] as DynamicFormGroupModel;
    const length = testModel.length;
    const size = nestedFormGroupModel.size();
    const index = 1;
    const id1 = testModel[index].id;
    const id2 = nestedFormGroupModel.get(index).id;

    service.removeFormGroupControl(index, formGroup, testModel);

    expect(Object.keys(formGroup.controls).length).toBe(length - 1);
    expect(formGroup.controls[id1]).toBeUndefined();

    expect(testModel.length).toBe(length - 1);
    expect(service.findById(id1, testModel)).toBeNull();

    service.removeFormGroupControl(index, nestedFormGroup, nestedFormGroupModel);

    expect(Object.keys(nestedFormGroup.controls).length).toBe(size - 1);
    expect(nestedFormGroup.controls[id2]).toBeUndefined();

    expect(nestedFormGroupModel.size()).toBe(size - 1);
    expect(service.findById(id2, testModel)).toBeNull();
  });

  it('should create a form array', () => {

    const model = service.findById('testFormArray', testModel) as DynamicFormArrayModel;
    let formArray;

    expect(service.createFormArray).toBeTruthy();

    formArray = service.createFormArray(model);

    expect(formArray instanceof FormArray).toBe(true);
    expect(formArray.length).toBe(model.initialCount);
  });

  it('should add a form array group', () => {

    const model = service.findById('testFormArray', testModel) as DynamicFormArrayModel;
    const formArray = service.createFormArray(model);

    service.addFormArrayGroup(formArray, model);

    expect(formArray.length).toBe(model.initialCount + 1);
  });

  it('should insert a form array group', () => {

    const model = service.findById('testFormArray', testModel) as DynamicFormArrayModel;
    const formArray = service.createFormArray(model);

    service.insertFormArrayGroup(0, formArray, model);

    expect(formArray.length).toBe(model.initialCount + 1);
  });

  it('should move up a form array group', () => {

    const model = service.findById('testFormArray', testModel) as DynamicFormArrayModel;
    const formArray = service.createFormArray(model);
    const index = 3;
    const step = 1;

    (formArray.at(index) as FormGroup).controls.testFormArrayGroupInput.setValue('next test value 1');
    (formArray.at(index + step) as FormGroup).controls.testFormArrayGroupInput.setValue('next test value 2');

    (model.get(index).get(0) as DynamicFormValueControlModel<any>).valueUpdates.next('next test value 1');
    (model.get(index + step).get(0) as DynamicFormValueControlModel<any>).valueUpdates.next('next test value 2');

    service.moveFormArrayGroup(index, step, formArray, model);

    expect(formArray.length).toBe(model.initialCount);

    expect((formArray.at(index) as FormGroup).controls.testFormArrayGroupInput.value).toEqual('next test value 2');
    expect((formArray.at(index + step) as FormGroup).controls.testFormArrayGroupInput.value).toEqual('next test value 1');

    expect((model.get(index).get(0) as DynamicFormValueControlModel<any>).value).toEqual('next test value 2');
    expect((model.get(index + step).get(0) as DynamicFormValueControlModel<any>).value).toEqual('next test value 1');
  });

  it('should move down a form array group', () => {

    const model = service.findById('testFormArray', testModel) as DynamicFormArrayModel;
    const formArray = service.createFormArray(model);
    const index = 3;
    const step = -1;

    (formArray.at(index) as FormGroup).controls.testFormArrayGroupInput.setValue('next test value 1');
    (formArray.at(index + step) as FormGroup).controls.testFormArrayGroupInput.setValue('next test value 2');

    (model.get(index).get(0) as DynamicFormValueControlModel<any>).valueUpdates.next('next test value 1');
    (model.get(index + step).get(0) as DynamicFormValueControlModel<any>).valueUpdates.next('next test value 2');

    service.moveFormArrayGroup(index, step, formArray, model);

    expect(formArray.length).toBe(model.initialCount);

    expect((formArray.at(index) as FormGroup).controls.testFormArrayGroupInput.value).toEqual('next test value 2');
    expect((formArray.at(index + step) as FormGroup).controls.testFormArrayGroupInput.value).toEqual('next test value 1');

    expect((model.get(index).get(0) as DynamicFormValueControlModel<any>).value).toEqual('next test value 2');
    expect((model.get(index + step).get(0) as DynamicFormValueControlModel<any>).value).toEqual('next test value 1');
  });

  it('should throw when form array group is to be moved out of bounds', () => {

    const model = service.findById('testFormArray', testModel) as DynamicFormArrayModel;
    const formArray = service.createFormArray(model);

    expect(() => service.moveFormArrayGroup(2, -5, formArray, model))
      .toThrow(new Error(`form array group cannot be moved due to index or new index being out of bounds`));
  });

  it('should remove a form array group', () => {

    const model = service.findById('testFormArray', testModel) as DynamicFormArrayModel;
    const formArray = service.createFormArray(model);

    service.removeFormArrayGroup(0, formArray, model);

    expect(formArray.length).toBe(model.initialCount - 1);
  });

  it('should clear a form array', () => {

    const model = service.findById('testFormArray', testModel) as DynamicFormArrayModel;
    const formArray = service.createFormArray(model);

    service.clearFormArray(formArray, model);

    expect(formArray.length === 0).toBe(true);
  });
});
