Show:
import Ember from 'ember';

import FdEditformRow from '../objects/fd-editform-row';
import FdEditformControl from '../objects/fd-editform-control';
import FdEditformGroup from '../objects/fd-editform-group';
import FdEditformTabgroup from '../objects/fd-editform-tabgroup';
import FdEditformTab from '../objects/fd-editform-tab';

import FdViewAttributesProperty from '../objects/fd-view-attributes-property';
import FdViewAttributesMaster from '../objects/fd-view-attributes-master';
import FdViewAttributesDetail from '../objects/fd-view-attributes-detail';
import FdAttributesTree from '../objects/fd-attributes-tree';
import {
  getDataForBuildTree,
  getTreeNodeByNotUsedAttributes,
  getAssociationTreeNode,
  getAggregationTreeNode,
  getTreeNodeByNotUsedAggregation,
  getClassTreeNode
 } from '../utils/fd-attributes-for-tree';
import { createPropertyName, restorationNodeTree, afterCloseNodeTree, findFreeNodeTreeNameIndex } from '../utils/fd-metods-for-tree';
import { copyViewDefinition } from '../utils/fd-copy-view-definition';
import FdWorkPanelToggler from '../mixins/fd-work-panel-toggler';
import { controlsToDefinition, locateControlByPath } from '../utils/fd-view-path-functions';
import FdDataTypes from '../utils/fd-datatypes';

export default Ember.Controller.extend(FdWorkPanelToggler, {
  queryParams: ['classId'],

  /**
    @private
    @property _dataTypes
    @type Ember.Object
  */
  _dataTypes: FdDataTypes.create(),

  /**
    @private
    @property _dataForBuildTree
    @type Object
  */
  _dataForBuildTree: Ember.computed('model.dataobject.id', function() {
    return getDataForBuildTree(this.get('store'), this.get('model.dataobject.id'));
  }),

  /**
    @private
    @property _showModalDialog
    @type Boolean
    @default false
  */
  _showModalDialog: false,

  /**
    @private
    @property _showLookupDialog
    @type Boolean
    @default false
  */
  _showLookupDialog: false,

  /**
    @private
    @property _lookupCaption
    @type String
  */
  _lookupCaption: undefined,

  /**
    @private
    @property _lookupView
    @type FdDevViewModel
  */
  _lookupView: undefined,

  /**
    @private
    @property _lookupTypes
    @type Array
  */
  _lookupTypes: undefined,

  /**
    Indicates that the user has started moving control, and the next selected control will be the target of the move.

    @private
    @property _moveItem
    @type Boolean
    @default false
  */
  _moveItem: false,

  /**
    The current dragged item.

    @private
    @property _draggedItem
    @type FdEditformRow|FdEditformControl
  */
  _draggedItem: undefined,

  /**
    Flag: indicates whether show tree.

    @private
    @property _showNotUsedAttributesTree
    @type Boolean
    @default false
  */
  _showNotUsedAttributesTree: false,

  /**
    Selected nodes in jsTree.

    @property selectedNodesNotUsedAttributesTree
    @type Array
    @default []
   */
  selectedNodesNotUsedAttributesTree: Ember.A(),

  /**
    Included plugins for jsTree.

    @property pluginsTree
    @type String
    @default 'wholerow, types, search'
   */
  pluginsTree: 'wholerow, types, search',

  /**
    Type settings for jsTree.

    @property typesOptionsTree
    @type Object
  */
  typesOptionsTree: Ember.computed(() => ({
    'property': {
      icon: 'assets/images/attribute.bmp'
    },
    'master': {
      icon: 'assets/images/master.bmp'
    },
    'detail': {
      icon: 'assets/images/datail.png'
    },
    'class': {
      icon: 'assets/images/class.bmp'
    }
  })),

  /**
    Data for search tree node.

    @property searchTermTree
    @type String
    @default ''
   */
  searchTermTree: '',

  /**
    Search settings for jsTree.

    @property searchOptionsTree
    @type Object
  */
  searchOptionsTree: Ember.computed(() => ({
    show_only_matches: true
  })),

  /**
    Data for jsTree.

    @property dataNotUsedAttributesTree
    @type Array
  */
  dataNotUsedAttributesTree: Ember.A(),

  /**
    Update data in tree.

    @method dataNotUsedAttributesTreeObserver
  */
  dataNotUsedAttributesTreeObserver: Ember.observer('_showNotUsedAttributesTree', function() {
    if (!this.get('_showNotUsedAttributesTree')) {
      return;
    }

    let dataobjectId = this.get('model.dataobject.id');
    let mockView = Ember.Object.create({
      definition: controlsToDefinition(this.get('controlsTree')),
      class: this.get('model.dataobject')
    });

    let dataForBuildTree = getDataForBuildTree(this.get('store'), dataobjectId);
    let attributesForTree = getTreeNodeByNotUsedAttributes(this.get('store'), dataForBuildTree.classes, mockView, 'type');
    let associationForTree = getAssociationTreeNode(Ember.A(), dataForBuildTree.associations, 'node_', dataobjectId, 'name');
    let aggregationForTree = getTreeNodeByNotUsedAggregation(dataForBuildTree.aggregations, mockView, 'name');

    let attributesTree = Ember.A();
    attributesTree.pushObjects([
      FdAttributesTree.create({
        text: this.get('i18n').t('forms.fd-editform-constructor.form-config-panel.tree.not-used-attributes.property').toString(),
        type: 'class',
        id: 'attributes',
        children: attributesForTree,
        copyChildren: attributesForTree,
        state: { opened: true }
      }),
      FdAttributesTree.create({
        text: this.get('i18n').t('forms.fd-editform-constructor.form-config-panel.tree.not-used-attributes.master').toString(),
        type: 'class',
        id: 'masters',
        children: associationForTree,
        copyChildren: associationForTree,
        state: { opened: true }
      }),
      FdAttributesTree.create({
        text: this.get('i18n').t('forms.fd-editform-constructor.form-config-panel.tree.not-used-attributes.detail').toString(),
        type: 'class',
        id: 'details',
        children: aggregationForTree,
        copyChildren: aggregationForTree,
        state: { opened: true }
      })
    ]);

    this.set('dataNotUsedAttributesTree', attributesTree);
  }),

  _applyDisabled: Ember.computed('selectedNodesNotUsedAttributesTree', function() {
    let selectedNodes = this.get('selectedNodesNotUsedAttributesTree');
    if (selectedNodes.length === 0 || selectedNodes[0].type === 'class') {
      return 'disabled';
    } else {
      return '';
    }
  }),

  /**
    @private
    @property _selectedIsRow
    @readOnly
    @type Boolean
  */
  _selectedIsRow: Ember.computed('selectedItem', function() {
    return this.get('selectedItem') instanceof FdEditformRow;
  }).readOnly(),

  /**
    @private
    @property _selectedIsControl
    @readOnly
    @type Boolean
  */
  _selectedIsControl: Ember.computed('selectedItem', function() {
    return this.get('selectedItem') instanceof FdEditformControl;
  }).readOnly(),

  /**
    @private
    @property _selectedIsGroup
    @readOnly
    @type Boolean
  */
  _selectedIsGroup: Ember.computed('selectedItem', function() {
    return this.get('selectedItem') instanceof FdEditformGroup;
  }).readOnly(),

  /**
    @private
    @property _selectedIsTab
    @readOnly
    @type Boolean
  */
  _selectedIsTab: Ember.computed('selectedItem', function() {
    return this.get('selectedItem') instanceof FdEditformTab;
  }).readOnly(),

  /**
    An array in the container of the selected item.

    @private
    @property _selectedItemStorage
    @readOnly
    @type Ember.NativeArray
  */
  _selectedItemStorage: Ember.computed('selectedItem', function() {
    let selectedItem = this.get('selectedItem');
    if (selectedItem) {
      return this._getItemStorage(this._findItemContainer(selectedItem));
    }
  }).readOnly(),

  /**
    @private
    @property _selectedIsFirst
    @readOnly
    @type Boolean
  */
  _selectedIsFirst: Ember.computed('_selectedItemStorage.[]', function() {
    let result = false;
    let selectedItem = this.get('selectedItem');
    let selectedItemStorage = this.get('_selectedItemStorage');
    if (selectedItem && selectedItemStorage) {
      result = selectedItemStorage.get('firstObject') === selectedItem;
    }

    return result;
  }).readOnly(),

  /**
    @private
    @property _selectedIsLast
    @readOnly
    @type Boolean
  */
  _selectedIsLast: Ember.computed('_selectedItemStorage.[]', function() {
    let result = false;
    let selectedItem = this.get('selectedItem');
    let selectedItemStorage = this.get('_selectedItemStorage');
    if (selectedItem && selectedItemStorage) {
      result = selectedItemStorage.get('lastObject') === selectedItem;
    }

    return result;
  }).readOnly(),

  /**
    The selected item.

    @property selectedItem
    @type {FdEditformRow|FdEditformControl|FdEditformGroup|FdEditformTabgroup|FdEditformTab}
  */
  selectedItem: undefined,

  /**
    @property implementations
    @type Ember.NativeArray
  */
  implementations: Ember.computed.filter('model.classes', clazz => clazz.get('stereotype') === '«implementation»' || clazz.get('stereotype') === null),

  /**
    @property attributes
    @type Ember.NativeArray
  */
  attributes: Ember.computed('_dataForBuildTree', function() {
    return getClassTreeNode(Ember.A(), this.get('_dataForBuildTree.classes'), this.get('model.dataobject.id'), 'type');
  }),

  /**
    @property masters
    @type Ember.NativeArray
  */
  masters: Ember.computed('_dataForBuildTree', function() {
    return getAssociationTreeNode(Ember.A(), this.get('_dataForBuildTree.associations'), 'node_', this.get('model.dataobject.id'), 'name');
  }),

  /**
    @property details
    @type Ember.NativeArray
  */
  details: Ember.computed('_dataForBuildTree', function() {
    return getAggregationTreeNode(Ember.A(), this.get('_dataForBuildTree.aggregations'), this.get('model.dataobject.id'), 'name');
  }),

  /**
    @property mastersType
    @type Ember.NativeArray
  */
  mastersType: Ember.computed('implementations', function() {
    return this._buildTree(this.get('implementations'), 'master', true);
  }),

  /**
    @property detailsType
    @type Ember.NativeArray
  */
  detailsType: Ember.computed('implementations', 'model.aggregations', 'model.dataobject.id', function() {
    let implementations = this.get('implementations');
    let aggregations = this.get('model.aggregations');
    let dataObjectId = this.get('model.dataobject.id');
    let details = implementations.filter(clazz => clazz.get('id') !== dataObjectId && aggregations.findBy('endClass.id', clazz.get('id')) === undefined);

    return this._buildTree(details, 'detail', true);
  }),

  /**
    @property simpleTypes
    @type Ember.NativeArray
  */
  simpleTypes: Ember.computed('_dataTypes', function() {
    return this._buildTree(this.get('_dataTypes').fDTypes(), 'property');
  }),

  /**
    @property typemap
    @type Ember.NativeArray
  */
  typemap: Ember.computed('model.stage.typeMapCSStr', '_dataTypes', function() {
    let dataTypes = this.get('_dataTypes');
    let typemap = this.get('model.stage.typeMapCSStr').filter(t => dataTypes.fDTypeToFlexberry(t.name) === null);
    return this._buildTree(typemap, '«typemap»');
  }),

  /**
    @property enums
    @type Ember.NativeArray
  */
  enums: Ember.computed('model.classes', function() {
    return this._buildTree(this.get('model.classes').filterBy('stereotype', '«enumeration»'), '«enumeration»');
  }),

  /**
    @property types
    @type Ember.NativeArray
  */
  types: Ember.computed('model.classes', function() {
    return this._buildTree(this.get('model.classes').filterBy('stereotype', '«type»'), '«type»');
  }),

  /**
    An object with all (including inherited) the attributes, associations and aggregations for the this form data object.

    @property dataObjectProperties
    @type Object
  */
  dataObjectProperties: Ember.computed('model.dataobject.attributes.@each.type', 'model.inheritances', 'model.associations', 'model.aggregations', function() {
    let dataObject = this.get('model.dataobject');
    let inheritances = this.get('model.inheritances');
    let associations = this.get('model.associations');
    let aggregations = this.get('model.aggregations');

    return this._getClassProperties(dataObject, inheritances, associations, aggregations);
  }),

  /**
    The controls tree created from a view definition.

    @property controlsTree
    @readOnly
    @type Ember.NativeArray
  */
  controlsTree: Ember.computed('model.editform.formViews.firstObject.view.definition', function() {
    let controlsTree = Ember.A();

    let definition = this.get('model.editform.formViews.firstObject.view.definition');
    let length = definition.get('length');
    for (let i = 0; i < length; i++) {
      let propertyDefinition = definition.objectAt(i);
      let path = propertyDefinition.get('path');
      let caption = propertyDefinition.get('caption') || propertyDefinition.get('name');
      let width = this._getWidth(path);
      let control = FdEditformControl.create({ caption, width, propertyDefinition });
      locateControlByPath(controlsTree, control, path);
    }

    return controlsTree;
  }).readOnly(),

  actions: {
    /**
      Adds a new control to the form, if there is a selected item, the control will be added to it.

      @method actions.addControl
    */
    addControl() {
      let dataobject = this.get('model.dataobject');
      let attributes = dataobject.get('attributes');
      let atrIndex = findFreeNodeTreeNameIndex('newAttribute', 1, attributes, 'name');

      this.get('store').createRecord('fd-dev-attribute', {
        class: dataobject,
        name: 'newAttribute' + atrIndex,
        type: 'string',
        notNull: false,
        defaultValue: ''
      });

      this.notifyPropertyChange('_dataForBuildTree');

      let propertyDefinition = FdViewAttributesProperty.create({
        name: 'newAttribute' + atrIndex,
        visible: true,
      });

      let control = FdEditformControl.create({
        caption: `${this.get('i18n').t('forms.fd-editform-constructor.new-control-caption').toString()} #${this.incrementProperty('_newControlIndex')}`,
        type: 'string',
        propertyDefinition: propertyDefinition,
      });

      this._insertItem(control, this.get('selectedItem') || this.get('controlsTree'));
      this.send('selectItem', control);
      Ember.run.scheduleOnce('afterRender', this, this._scrollToSelected);
    },

    /**
      Adds a new empty control to the form, if there is a selected item, the empty control will be added to it.

      @method actions.addEmptyControl
    */
    addEmptyControl() {
      let control = FdEditformControl.create({
        caption: `${this.get('i18n').t('forms.fd-editform-constructor.new-control-caption').toString()} #${this.incrementProperty('_newControlIndex')}`,
        type: 'string',
        propertyDefinition: FdViewAttributesProperty.create({
          name: '',
          visible: true,
        }),
      });

      this._insertItem(control, this.get('selectedItem') || this.get('controlsTree'));
      this.send('selectItem', control);
      Ember.run.scheduleOnce('afterRender', this, this._scrollToSelected);
    },

    /**
      Adds a new group to the form, if there is a selected item, the group will be added to it.

      @method actions.addGroup
    */
    addGroup() {
      this._insertItem(FdEditformGroup.create({
        caption: `${this.get('i18n').t('forms.fd-editform-constructor.new-group-caption').toString()} #${this.incrementProperty('_newGroupIndex')}`,
        rows: Ember.A(),
      }), this.get('selectedItem') || this.get('controlsTree'));
    },

    /**
      Adds a new tab to the form, if there is a selected item, the tab will be added to it.

      @method actions.addTab
    */
    addTab() {
      this._insertItem(FdEditformTab.create({
        caption: `${this.get('i18n').t('forms.fd-editform-constructor.new-tab-caption').toString()} #${this.incrementProperty('_newTabIndex')}`,
        rows: Ember.A(),
      }), this.get('selectedItem') || this.get('controlsTree'));
    },

    /**
      Removes the selected item.

      @method actions.removeSelectedItem
      @param {Boolean} approve The user is sure.
    */
    removeSelectedItem(approve) {
      if (approve) {
        this._removeItem(this.get('selectedItem'));

        // Refresh definition for filter not used attributes in 'dataNotUsedAttributesTreeObserver'.
        // let view = this.get('model.editform.formViews.firstObject.view');
        // view.set('definition', controlsToDefinition(this.get('controlsTree')));

        this.set('selectedItem', undefined);
      } else {
        this.set('_showModalDialog', true);
      }
    },

    /**
      Sorts the selected item in its container.

      @method actions.sortSelectedItem
      @param {Number} step Step of moving the item.
    */
    sortSelectedItem(step) {
      let selectedItem = this.get('selectedItem');
      let selectedItemStorage = this.get('_selectedItemStorage');
      let index = selectedItemStorage.indexOf(selectedItem) + step;

      selectedItemStorage.removeObject(selectedItem);
      selectedItemStorage.insertAt(index, selectedItem);
    },

    /**
      Close edit form constructor and go to application structure constructor.

      @method actions.close
    */
    close() {
      history.back();
    },

    /**
      Set the selected item.

      @method actions.selectItem
      @param {FdEditformRow|FdEditformControl|FdEditformGroup|FdEditformTabgroup|FdEditformTab} item
    */
    selectItem(item) {
      let selectedItem = this.get('selectedItem');
      if (this.get('_moveItem') && !Ember.isNone(item)) {
        if (this._findItemContainer(item, selectedItem) === null) {
          let selectedItemContainer = this._findItemContainer(selectedItem);
          try {
            this._removeItem(selectedItem);
            this._insertItem(selectedItem, item);
            this.notifyPropertyChange('_selectedItemStorage');
            this.set('_moveItem', false);
          } catch (error) {
            this._insertItem(selectedItem, selectedItemContainer);
            this.set('error', error);
          }
        }
      } else if (!this.get('_moveItem')) {
        let configPanelSidebar = Ember.$('.ui.sidebar.config-panel');
        let sidebarOpened = configPanelSidebar.hasClass('visible');

        if ((item || sidebarOpened) && selectedItem !== item) {
          this.send('toggleConfigPanel', 'control-properties', item);
        }

        this.set('selectedItem', item);

        let newSelectedItem = selectedItem === item ? undefined : item;
        if (!Ember.isNone(newSelectedItem) && newSelectedItem.get('propertyDefinition.name') === '') {
          this.set('_showNotUsedAttributesTree', true);
        } else {
          this.set('_showNotUsedAttributesTree', false);
        }
      }
    },

    /**
      Set the current dragged item.

      @method actions.setDragItem
      @param {FdEditformRow|FdEditformControl} item New dragged item.
    */
    setDragItem(item) {
      this.set('_draggedItem', item);
    },

    /**
      Get the current dragged item.

      @method actions.getDragItem
      @return {FdEditformRow|FdEditformControl} The current dragged item or `undefined`.
    */
    getDragItem() {
      return this.get('_draggedItem');
    },

    /**
      Returns an object with properties to render the component.

      @method actions.getComponentProperties
      @param {FdViewAttributesProperty|FdViewAttributesMaster|FdViewAttributesDetail} propertyDefinition Definition a property in a view.
      @return {Object} An object with properties for the component.
    */
    getComponentProperties(propertyDefinition) {
      return this._getComponentProperties(propertyDefinition, this.get('dataObjectProperties'));
    },

    /**
      Shows the lookup form in modal dialog.

      @method actions.showLookup
      @param {String} caption Caption for the lookup form.
      @param {FdDevViewModel} view The view on which the table will be render in the lookup form.
      @param {Array} types An array of types for the view.
    */
    showLookup(caption, view, types) {
      this.set('_lookupCaption', caption);
      this.set('_lookupView', view);
      this.set('_lookupTypes', types);
      this.set('_showLookupDialog', true);
    },

    /**
      Move the current dragged item above or below relative to the passed item.

      @method actions.moveDragItem
      @param {FdEditformRow|FdEditformControl} item The item above or below which will be moved the current dragged item.
      @param {String} direction The direction of the item move, allowed values: 'up' or 'down'.
    */
    moveDragItem(item, direction) {
      let draggedItem = this.get('_draggedItem');
      if (this._findItemContainer(item, draggedItem) === null) {
        let draggedItemStorage = this._getItemStorage(this._findItemContainer(draggedItem));
        draggedItemStorage.removeObject(draggedItem);

        let itemStorage;
        let index = draggedItemStorage.indexOf(item);
        if (index === -1) {
          itemStorage = this._getItemStorage(this._findItemContainer(item));
          index = itemStorage.indexOf(item);
        } else {
          itemStorage = draggedItemStorage;
        }

        if (direction === 'down') {
          index = Math.min(itemStorage.get('length'), index + 1);
        }

        itemStorage.insertAt(index, draggedItem);
      }
    },

    /**
      Saves the form's metadata.

      @method actions.save
      @param {Boolean} close If `true`, the `close` action will be run.
    */
    save(close) {
      this.set('state', 'loading');
      try {
        this._saveMetadata(this.get('model'), this.get('controlsTree')).then(() => {
          this.set('model.originalDefinition', copyViewDefinition(this.get('model.editform.formViews.firstObject.view.definition')));
          this.set('state', '');
          if (close) {
            this.send('close');
          }
        });
      } catch (error) {
        this.set('state', '');
        this.set('error', error);
      }
    },

    /**
      Set attribute in control.

      @method actions.setAttributeInControl
    */
    setAttributeInControl() {
      let selectedNodes = this.get('selectedNodesNotUsedAttributesTree')[0];
      let selectedItem = this.get('selectedItem');
      let treeData = this.get('dataNotUsedAttributesTree');

      // Create propertyName
      let propertyName = createPropertyName(selectedNodes, treeData[1], false);

      selectedItem.set('type', selectedNodes.original.typeNode);
      let propertyDefinition;
      if (selectedNodes.type === 'detail') {
        propertyDefinition = FdViewAttributesDetail.create({
          name: propertyName,
          visible: true
        });

        selectedItem.set('propertyDefinition', propertyDefinition);
      } else if (selectedNodes.type === 'master') {
        propertyDefinition = FdViewAttributesMaster.create({
          name: propertyName,
          visible: true
        });

        selectedItem.set('propertyDefinition', propertyDefinition);
      } else {
        selectedItem.set('propertyDefinition.name', propertyName);
        propertyDefinition = selectedItem.get('propertyDefinition');
      }

      let view = this.get('model.editform.formViews.firstObject.view');
      let viewDefinition = view.get('definition');
      viewDefinition.pushObject(propertyDefinition);

      this.set('_showNotUsedAttributesTree', false);
    },

    /**
      Don't set attribute in control.

      @method actions.deleteEmptyControl
    */
    deleteEmptyControl() {
      this._removeItem(this.get('selectedItem'));
      this.set('selectedItem', undefined);
      this.set('_showNotUsedAttributesTree', false);
    },

    /**
      Handles creating jsTree.

      @method actions.handleTreeDidBecomeReady
    */
    handleTreeDidBecomeReady() {
      let treeObject = this.get('treeObjectNotUsedAttributesTree');
      treeObject.on('open_node.jstree', this._openNodeTree.bind(this));
      treeObject.on('after_close.jstree', afterCloseNodeTree.bind(this));
    },
  },

  /**
    Overridden action for jsTree 'openNode'.

    @method _openNodeTree
  */
  _openNodeTree(e, data) {
    let treeData = this.get('dataNotUsedAttributesTree');
    restorationNodeTree(treeData, data.node.original, Ember.A(['master', 'class']), false, (function(node) {
      let view = this.get('model.editform.formViews.firstObject.view');
      let dataForBuildTree = getDataForBuildTree(this.get('store'), node.get('idNode'));
      let childrenAttributes = getTreeNodeByNotUsedAttributes(this.get('store'), dataForBuildTree.classes, view, 'type');
      let childrenNode = getAssociationTreeNode(childrenAttributes, dataForBuildTree.associations, node.get('id'), null, 'name');

      return childrenNode;
    }).bind(this));

    this.get('actionReceiverNotUsedAttributesTree').send('redraw');
  },

  /**
    Inserts an `item` into `container`, if `container` is `FdEditformControl`, `item` is inserted into the parent row after `container`.

    @method _insertItem
    @param {FdEditformRow|FdEditformControl|FdEditformGroup|FdEditformTabgroup|FdEditformTab} item
      The item that need to insert.
    @param {Ember.NativeArray|FdEditformRow|FdEditformControl|FdEditformGroup|FdEditformTabgroup|FdEditformTab} container
      The container in that need to insert the item.
  */
  _insertItem(item, container) {
    let _item;
    let index;
    let target;
    if (container instanceof FdEditformRow) {
      target = container.get('controls');
      _item = this._getControl(item);
    } else if (container instanceof FdEditformGroup || container instanceof FdEditformTab) {
      target = container.get('rows');
      _item = this._getRow(item);
    } else if (container instanceof FdEditformTabgroup) {
      if (item instanceof FdEditformTab) {
        target = container.get('tabs');
        _item = item;
      } else {
        target = container.get('activeTab.rows');
        _item = this._getRow(item);
      }
    } else if (container instanceof FdEditformControl) {
      target = this._findItemContainer(container).get('controls');
      index = target.indexOf(container) + 1;
      _item = this._getControl(item);
    } else {
      target = container;
      _item = this._getRow(item);
    }

    if (typeof index !== 'number') {
      index = target.get('length');
    }

    target.insertAt(index, _item);
  },

  /**
    Removes the specified item from the form.

    @method _removeItem
    @param {FdEditformRow|FdEditformControl|FdEditformGroup|FdEditformTabgroup|FdEditformTab} item The item that need to remove.
  */
  _removeItem(item) {
    let container = this._findItemContainer(item);
    if (container instanceof FdEditformRow) {
      if (container.get('controls.length') === 1) {
        this._removeItem(container);
      } else {
        container.get('controls').removeObject(item);
      }
    } else if (container instanceof FdEditformTabgroup) {
      if (container.get('tabs.length') === 1) {
        this._removeItem(container);
      } else {
        container.get('tabs').removeObject(item);
        container.set('activeTab', container.get('tabs.firstObject'));
      }
    } else if (container instanceof FdEditformGroup || container instanceof FdEditformTab) {
      container.get('rows').removeObject(item);
    } else {
      container.removeObject(item);
    }
  },

  /**
    Looks for a container that contains the item.

    @method _findItemContainer
    @param {FdEditformRow|FdEditformControl|FdEditformGroup|FdEditformTabgroup|FdEditformTab} item
      The sought item.
    @param {Ember.NativeArray|FdEditformRow|FdEditformGroup|FdEditformTabgroup|FdEditformTab} [container]
      The container from which to start the search, if not specified, uses `controlsTree`.
    @return {Ember.NativeArray|FdEditformRow|FdEditformGroup|FdEditformTabgroup|FdEditformTab}
      The container that was found or `null`.
  */
  _findItemContainer(item, container = this.get('controlsTree')) {
    let foundContainer;
    if (container instanceof FdEditformControl) {
      foundContainer = null;
    } else if (container instanceof FdEditformRow && container.get('controls').indexOf(item) === -1) {
      foundContainer = this._findItemContainer(item, container.get('controls'));
    } else if (container instanceof FdEditformTabgroup && container.get('tabs').indexOf(item) === -1) {
      foundContainer = this._findItemContainer(item, container.get('tabs'));
    } else if ((container instanceof FdEditformGroup || container instanceof FdEditformTab) && container.get('rows').indexOf(item) === -1) {
      foundContainer = this._findItemContainer(item, container.get('rows'));
    } else if (Ember.isArray(container) && container.indexOf(item) === -1) {
      let index = 0;
      let length = container.get('length');
      while (index < length && !foundContainer) {
        foundContainer = this._findItemContainer(item, container.objectAt(index++));
      }
    } else {
      foundContainer = container;
    }

    return foundContainer;
  },

  /**
    Returns the item storage in the container.

    @private
    @method _getItemStorage
    @param {FdEditformRow|FdEditformGroup|FdEditformTabgroup|FdEditformTab} container Item container.
    @return {Ember.NativeArray} Item storage.
  */
  _getItemStorage(container) {
    if (container instanceof FdEditformRow) {
      return container.get('controls');
    } else if (container instanceof FdEditformTabgroup) {
      return container.get('tabs');
    } else if (container instanceof FdEditformGroup || container instanceof FdEditformTab) {
      return container.get('rows');
    } else if (Ember.isArray(container)) {
      return container;
    } else {
      throw new Error(this.get('i18n').t('forms.fd-editform-constructor.unsupported-container-error'));
    }
  },

  /**
    Returns a row that can be added to the form.

    @private
    @method _getRow
    @param {FdEditformRow|FdEditformControl|FdEditformGroup|FdEditformTabgroup|FdEditformTab} item The item to be converted.
    @return {FdEditformRow} A row that can be added to the form.
  */
  _getRow(item) {
    let row;
    if (item instanceof FdEditformRow) {
      row = item;
    } else if (item instanceof FdEditformTab) {
      row = FdEditformRow.create({ controls: Ember.A([
        FdEditformTabgroup.create({ tabs: Ember.A([item]) }),
      ]) });
    } else if (this._isControl(item)) {
      row = FdEditformRow.create({ controls: Ember.A([item]) });
    } else {
      throw new Error(this.get('i18n').t('forms.fd-editform-constructor.item-cast-error'));
    }

    return row;
  },

  /**
    Returns the control that can be added to a row.

    @method _getControl
    @param {FdEditformRow|FdEditformControl|FdEditformGroup|FdEditformTabgroup|FdEditformTab} item The item to be converted.
    @return {FdEditformControl|FdEditformGroup|FdEditformTabgroup} The control that can be added to a row.
  */
  _getControl(item) {
    let control;
    if (this._isControl(item)) {
      control = item;
    } else if (item instanceof FdEditformTab) {
      control = FdEditformTabgroup.create({ tabs: Ember.A([item]) });
    } else if (item instanceof FdEditformRow && item.get('controls.length') === 1) {
      control = item.get('controls.firstObject');
    } else {
      throw new Error(this.get('i18n').t('forms.fd-editform-constructor.item-cast-error'));
    }

    return control;
  },

  /**
    Checks whether the control is suitable for placement in a row.

    @private
    @method _isControl
    @param {Any} control The control to check.
    @return {Boolean} If the control can be placed in a row then `true`, else `false`.
  */
  _isControl(control) {
    return control instanceof FdEditformControl || control instanceof FdEditformGroup || control instanceof FdEditformTabgroup;
  },

  /**
    Save editform metadata: dataobject attributes, view, editform class.

    @method _saveMetadata
    @param {Object} model Complex model for processing and save.
    @param {Ember.NativeArray} controlsTree The controls tree.
    @return {Ember.RSVP.Promise}
  */
  _saveMetadata(model, controlsTree) {
    let view = model.editform.get('formViews.firstObject.view');
    let viewDefinition = controlsToDefinition(controlsTree);

    // Check viewDefinition on errors.
    let duplicateValues = Ember.A();
    let detailViewNull = Ember.A();
    viewDefinition.forEach((atr) => {
      let countDefinition = viewDefinition.filterBy('name', atr.name);
      if (countDefinition.length !== 1) {
        duplicateValues.addObject(atr.name);
      }

      if (atr instanceof FdViewAttributesDetail && atr.detailViewName === '') {
        detailViewNull.addObject(atr.name);
      }
    });

    if (duplicateValues.length !== 0 || detailViewNull.length !== 0) {
      let dublicateValuesText = this.get('i18n').t('forms.fd-editform-constructor.duplicate-value-error');
      let unknownDetailViewText = this.get('i18n').t('forms.fd-editform-constructor.unknown-detail-view-error');
      let duplicateError = duplicateValues.length > 0 ? `${dublicateValuesText}: ` + duplicateValues.uniq() + '. ' : '';
      let detailViewError = detailViewNull.length > 0 ? `${unknownDetailViewText}: ` + detailViewNull.uniq() + '. ' : '';
      throw new Error(duplicateError + detailViewError);
    }

    view.set('definition', viewDefinition);

    // Save attributes.
    let dataobject = this.get('model.dataobject');
    if (Ember.isNone(dataobject.get('caption'))) {
      dataobject.set('caption', dataobject.get('name'));
    }

    let attributes = dataobject.get('attributes');
    let changedAttributes = attributes.filterBy('hasDirtyAttributes');

    let association = this.get('store').peekAll('fd-dev-association');
    let changedAssociations = association.filterBy('hasDirtyAttributes');

    let aggregation = this.get('store').peekAll('fd-dev-aggregation');
    let changedAggregation = aggregation.filterBy('hasDirtyAttributes');

    // Сохранить класс формы редактирования
    let editform = this.get('model.editform');
    editform.set('propertyLookupStr', Ember.A(editform.get('propertyLookupStr').toArray()));

    return Ember.RSVP.all([
      view.save(),
      dataobject.save(),
      editform.save(),
      Ember.RSVP.all(changedAttributes.map(a => a.save())),
      Ember.RSVP.all(changedAssociations.map(a => a.save())),
      Ember.RSVP.all(changedAggregation.map(a => a.save())),
    ]);
  },

  /**
    Scrolls the form to the selected control with jQuery.

    @private
    @method _scrollToSelected
  */
  _scrollToSelected() {
    let form = Ember.$('.full.height');
    let scrollTop = Ember.$('.selected:first').offset().top + form.scrollTop() - (form.offset().top + 10);

    form.animate({ scrollTop });
  },

  /**
    Returns an object with all (including inherited) the attributes, associations and aggregations for the class.

    @private
    @method _getClassProperties
    @param {FdDevClassModel} clazz The class for which to get the properties.
    @param {Ember.NativeArray} inheritances All inheritances.
    @param {Ember.NativeArray} associations All associations.
    @param {Ember.NativeArray} aggregations All aggregations.
    @return {Object} An object with properties for the class.
  */
  _getClassProperties(clazz, inheritances, associations, aggregations) {
    let properties = {
      attributes: Ember.A(),
      associations: Ember.A(),
      aggregations: Ember.A(),
    };
    let clazzId = clazz.get('id');

    properties.attributes.pushObjects(clazz.get('attributes').toArray());
    properties.associations.pushObjects(associations.filterBy('endClass.id', clazzId));
    properties.associations.pushObjects(aggregations.filterBy('endClass.id', clazzId));
    properties.aggregations.pushObjects(aggregations.filterBy('startClass.id', clazzId));

    let parents = inheritances.filterBy('child.id', clazzId);
    while (parents.length > 0) {
      let parent = parents.pop().get('parent');
      let parentId = parent.get('id');

      properties.attributes.pushObjects(parent.get('attributes').toArray());
      properties.associations.pushObjects(associations.filterBy('endClass.id', parentId));
      properties.associations.pushObjects(aggregations.filterBy('endClass.id', parentId));
      properties.aggregations.pushObjects(aggregations.filterBy('startClass.id', parentId));

      if (parents.length === 0) {
        parents = inheritances.filterBy('child.id', parentId);
      }
    }

    return properties;
  },

  /**
    Returns an object with properties to render the component.

    @private
    @method _getComponentProperties
    @param {FdViewAttributesProperty|FdViewAttributesMaster|FdViewAttributesDetail} propertyDefinition Definition a property in a view.
    @param {Object} dataObjectProperties
    @return {Object} An object with properties for the component.
  */
  _getComponentProperties(propertyDefinition, dataObjectProperties) {
    let type;
    let view;
    let types;
    let items;

    let path = propertyDefinition.get('name').split('.');
    let propertyName = path.pop();
    if (propertyDefinition instanceof FdViewAttributesDetail) {
      let { aggregations } = dataObjectProperties;
      let relation = aggregations.findBy('endRole', propertyName) || aggregations.findBy('endClass.name', propertyName);
      type = 'detail';
      view = Ember.A(this.get('model.views').filterBy('class.id', relation.get('endClass.id'))).findBy('name', propertyDefinition.get('detailViewName'));
      types = this._getTypesForView(view);
    } else if (propertyDefinition instanceof FdViewAttributesMaster) {
      type = 'master';
      let propertyLookup = this.get('model.editform.propertyLookupStr').findBy('property', propertyDefinition.get('name'));
      if (propertyLookup) {
        let form = this.get('model.classes').findBy('name', propertyLookup.container);
        if (form) {
          view = form.get('formViews.firstObject.view');
          types = this._getTypesForView(view);
        }
      }
    } else if (propertyDefinition instanceof FdViewAttributesProperty) {
      let properties = dataObjectProperties;
      let inheritances = this.get('model.inheritances');
      let associations = this.get('model.associations');
      let aggregations = this.get('model.aggregations');
      while (path.length > 0) {
        let role = path.shift();
        let relation = properties.associations.findBy('startRole', role) || properties.associations.findBy('startClass.name', role);
        if (relation) {
          properties = this._getClassProperties(relation.get('startClass'), inheritances, associations, aggregations);
        } else {
          console.error('Not found association with name:' + role);
        }
      }

      let attribute = properties.attributes.findBy('name', propertyName);
      if (attribute) {
        let typeInMap = this.get('model.stage.typeMapCSStr').findBy('name', attribute.get('type'));
        if (typeInMap) {
          type = typeInMap.value || typeInMap.name;
        } else {
          let clazz = this.get('model.classes').findBy('name', attribute.get('type'));
          if (clazz && clazz.get('stereotype') === '«enumeration»') {
            type = 'enumeration';
            items = clazz.get('attributes').mapBy('name');
          } else {
            type = 'default';
          }
        }
      } else {
        type = 'default';
      }
    } else {
      throw new Error('Invalid property definition.');
    }

    return { type, view, types, items };
  },

  /**
    Returns an array of types used in the view.

    @private
    @method _getTypesForView
    @param {FdDevViewModel} view A view for which types are needed.
    @return {Array} An array of types used in the view.
  */
  _getTypesForView(view) {
    let types = [];

    let clazz = view.get('class');
    let definition = view.get('definition');
    let inheritances = this.get('model.inheritances');
    let associations = this.get('model.associations');
    let aggregations = this.get('model.aggregations');
    let dataObjectProperties = this._getClassProperties(clazz, inheritances, associations, aggregations);

    let length = definition.get('length');
    for (let i = 0; i < length; i++) {
      types.push(this._getComponentProperties(definition.objectAt(i), dataObjectProperties));
    }

    return types;
  },

  /**
    Create tree.

    @method _buildTree
    @param {Array} data Data for tree.
    @param {String} type Type for tree.
    @param {Boolean} nodeId Flag need add in object node id.
    @return {Object} Object data for tree.
  */
  _buildTree(data, type, nodeId) {
    let treeData = Ember.A();
    data.forEach((item, index) => {
      let text;
      if (type === '«typemap»') {
        text = item.name;
      } else if (type === 'property') {
        text = item;
      } else {
        text = item.get('name');
      }

      let newNode = FdAttributesTree.create({
        text: text,
        type: type,
        id: type + index
      });

      if (!Ember.isNone(nodeId)) {
        newNode.set('idNode', item.get('id'));
      }

      treeData.pushObject(newNode);
    });

    return treeData;
  },

  /**
      Looks for width in the path

      @method _getWidth
      @param {String} path Property path from view.
  */
  _getWidth: function(path) {
    let partsPath = path.split('\\');
    let lastPartsPath = partsPath[partsPath.length - 1];
    let startWidth = lastPartsPath.lastIndexOf('(');
    let endWidth = lastPartsPath.lastIndexOf(')');
    let width = '';
    if (endWidth !== -1 && startWidth !== -1 && endWidth === lastPartsPath.length - 1 && lastPartsPath.charAt(0) === '#') {
      width = lastPartsPath.slice(startWidth + 1, endWidth);
    }

    return width;
  },

  /**
    Destroys helper.
  */
  willDestroy() {
    this._super(...arguments);
    let treeObject = this.get('treeObjectNotUsedAttributesTree');
    if (!Ember.isNone(treeObject)) {
      treeObject.off('open_node.jstree', this._openNodeTree.bind(this));
      treeObject.off('after_close.jstree', afterCloseNodeTree.bind(this));
    }
  }
});