/*
 * Axelor Business Solutions
 *
 * Copyright (C) 2005-2019 Axelor (<http://axelor.com>).
 *
 * This program is free software: you can redistribute it and/or  modify
 * it under the terms of the GNU Affero General Public License, version 3,
 * as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
(function() {

"use strict";

var ui = angular.module("axelor.ui");

ui.OneToManyCtrl = OneToManyCtrl;
ui.OneToManyCtrl.$inject = ['$scope', '$element', 'DataSource', 'ViewService'];

function OneToManyCtrl($scope, $element, DataSource, ViewService, initCallback) {

  ui.RefFieldCtrl.call(this, $scope, $element, DataSource, ViewService, function(){
    ui.GridViewCtrl.call(this, $scope, $element);
    $scope.editorCanSave = false;
    $scope.selectEnable = false;
    if (initCallback) {
      initCallback();
    }
  });

  var embedded = null,
    detailView = null;

  $scope.createNestedEditor = function() {

    embedded = $('<div ui-embedded-editor class="inline-form"></div>');
    embedded.attr('x-title', $element.attr('x-title'));
    embedded = ViewService.compile(embedded)($scope);
    embedded.hide();

    $element.append(embedded);
    embedded.data('$rel', $element.children('.slickgrid:first').children('.slick-viewport'));

    return embedded;
  };

  var _showNestedEditor = $scope.showNestedEditor;
  $scope.showNestedEditor = function(show, record) {
    _showNestedEditor(show, record);
    if (embedded) {
      embedded.data('$rel').hide();
      var formScope = embedded.scope();
      formScope._viewPromise.then(function () {
        formScope.edit(record);
      });
    }
    return embedded;
  };

  $scope.showDetailView = function() {
    if (detailView == null) {
      detailView = $('<div ui-embedded-editor class="detail-form"></div>').attr('x-title', $element.attr('x-title'));
      detailView = ViewService.compile(detailView)($scope);
      detailView.data('$rel', $());
      detailView.data('$scope').isDetailView = true;
      $element.after(detailView);
    }
    var es = detailView.data('$scope');
    detailView.toggle(es.visible = !es.visible);
  };

  $scope.select = function(value) {

    // if items are same, no need to set values
    if ($scope._dataSource.equals(value, $scope.getValue())) {
      return;
    }

    var items = _.chain([value]).flatten(true).compact().value();
    var records = _.map($scope.getItems(), _.clone);

    _.each(records, function(item) {
      item.selected = false;
    });

    _.each($scope.itemsPending, function (item) {
      var find = _.find(records, function(rec) {
        if (rec.id && rec.id == item.id) {
          return true;
        }
        var a = _.omit(item, 'id', 'version');
        var b = _.omit(rec, 'id', 'version');
        return $scope._dataSource.equals(a, b);
      });
      if (!find) {
        records.push(item);
      }
    });

    _.each(items, function(item){
      item = _.clone(item);
      var find = _.find(records, function(rec){
        var id1 = rec.id || rec.$id;
        var id2 = item.id || item.$id;
        return id1 && id1 === id2;
      });

      if (find) {
        _.extend(find, item);
      } else {
        records.push(item);
      }
    });

    records = $scope.$$ensureIds(records);

    _.each(records, function(rec){
      if (rec.id <= 0) rec.id = null;
    });

    if ($scope.dataView.$resequence) {
      $scope.dataView.$resequence(records);
    }

    var callOnChange = $scope.dataView.$isResequencing !== true;

    $scope.itemsPending = records;
    $scope.setValue(records, callOnChange);
    $scope.$timeout(function() {
      $scope.$broadcast('grid:changed');
    });
  };

  var _setItems = $scope.setItems;
  $scope.setItems = function(items) {
    _setItems(items);
    $scope.itemsPending = [];
    if (embedded !== null) {
      embedded.data('$scope').onClose();
    }
    if (detailView !== null)
    if (items === null || _.isEmpty(items))
      detailView.hide();
    else
      detailView.show();
  };

  $scope.removeItems = function(items, fireOnChange) {
    var all, ids, records;

    if (_.isEmpty(items)) return;

    all = _.isArray(items) ? items : [items];

    ids = _.map(all, function(item) {
      return _.isNumber(item) ? item : item.id;
    });

    records = _.filter($scope.getItems(), function(item) {
      return ids.indexOf(item.id) === -1;
    });

    $scope.setValue(records, fireOnChange);
    $scope.$applyAsync();
  };

  $scope.removeSelected = function(selection) {
    var selected, items;
    if (_.isEmpty(selection)) return;
    selected = _.map(selection, function (i) {
      return $scope.dataView.getItem(i).id;
    });
    items = _.filter($scope.getItems(), function (item) {
      return selected.indexOf(item.id) === -1;
    });
    // remove selected from data view
    _.each(selected, function (id) {
      if (id && id > -1) $scope.dataView.deleteItem(id);
    });
    $scope.dataView.$setSelection([]);
    $scope.setValue(items, true);
    $scope.$applyAsync();
  };

  $scope.canEditTarget = function () {
    return $scope.canEdit() && $scope.attr('canEdit') !== false;
  };

  $scope.canShowEdit = function () {
    var selected = $scope.selection.length ? $scope.selection[0] : null;
    return selected !== null && $scope.canEdit();
  };

  $scope.canShowView = function () {
    if ($scope.canShowEdit()) return false;
    var selected = $scope.selection.length ? $scope.selection[0] : null;
    return selected !== null && $scope.canView();
  };

  $scope.canEdit = function () {
    return $scope.attr('canEdit') !== false && $scope.canView();
  };

  var _canRemove = $scope.canRemove;
  $scope.canRemove = function () {
    var selected = $scope.selection.length ? $scope.selection[0] : null;
    return _canRemove() && selected !== null;
  };

  $scope.canCopy = function () {
    if (!$scope.field || !($scope.field.widgetAttrs||{}).canCopy) return false;
    if (!$scope.canNew()) return false;
    if (!$scope.selection || $scope.selection.length !== 1) return false;
    var record = $scope.dataView.getItem(_.first($scope.selection));
    return !!record;
  };

  $scope.onCopy = function() {
    var ds = $scope._dataSource;
    var index = _.first($scope.selection);
    var item = $scope.dataView.getItem(index);
    var doSelect = function (record) {
      if (!record.id) $scope.$$ensureIds([record]);
      $scope.select([record]);
      $scope.$timeout(function () {
        $scope.dataView.$setSelection([$scope.dataView.getLength() - 1], true);
      }, 100);
    };
    if (item && item.id > 0) {
      ds.copy(item.id).success(doSelect);
    } else if (item) {
      item = angular.copy(item);
      item.id = undefined;
      item.$id = undefined;
      doSelect(item);
    }
  };

  $scope.onEdit = function() {
    var selected = $scope.selection.length ? $scope.selection[0] : null;
    if (selected !== null) {
      var record = $scope.dataView.getItem(selected);
      $scope.showEditor(record);
    }
  };

  $scope.onRemove = function() {
    if (this.isReadonly()) {
      return;
    }
    axelor.dialogs.confirm(_t("Do you really want to delete the selected record(s)?"), function(confirmed){
      if (confirmed && $scope.selection && $scope.selection.length)
        $scope.removeSelected($scope.selection);
    });
  };

  $scope.onSummary = function() {
    var selected = $scope.getSelectedRecord();
    if (selected) {
      $scope.showNestedEditor(true, selected);
    }
  };

  $scope.viewCanCopy = function () {
    return this.hasPermission("create") && !this.isDisabled() && !this.isReadonly() && this.canCopy();
  };

  $scope.viewCanExport = function () {
    if (!$scope.field || !($scope.field.widgetAttrs||{}).canExport) return false;
    return this.hasPermission("export") && this.canExport();
  };

  $scope.getSelectedRecord = function() {
    var selected = _.first($scope.selection || []);
    if (_.isUndefined(selected))
      return null;
    return $scope.dataView.getItem(selected);
  };

  var _onSelectionChanged = $scope.onSelectionChanged;
  $scope.onSelectionChanged = function(e, args) {
    _onSelectionChanged(e, args);

    var field = $scope.field,
      record = $scope.record || {},
      selection = $scope.selection || [];

    record.$many = record.$many || (record.$many = {});
    record.$many[field.name] = selection.length ? $scope.getItems : null;

    if (detailView === null) {
      return;
    }

    $scope.$timeout(function() {
      var dvs = detailView.scope();
      var rec = $scope.getSelectedRecord() || {};
      detailView.show();
      if (!dvs.record || (dvs.record.id !== rec.id)) {
        dvs.edit(rec);
      }
    });
  };

  $scope.onItemDblClick = function(event, args) {
    if($scope.canView()){
      $scope.onEdit();
      $scope.$applyAsync();
    }
  };

  (function (scope) {

    var dummyId = 0;

    function ensureIds(records, clone) {
      var items = [];
      angular.forEach(records, function(record){
        var item = clone ? angular.copy(record, {}) : (record || {});
        if (!item.id) {
          item.id = item.$id || (item.$id = --dummyId);
        }
        items.push(item);
      });
      return items;
    }

    function fetchData() {
      var items = scope.getValue();
      return scope.fetchData(items, function(records){
        scope.setItems(ensureIds(records, true));
      });
    }

    scope.$$ensureIds = ensureIds;
    scope.$$fetchData = fetchData;

  })($scope);

  $scope.reload = function() {
    return $scope.$$fetchData();
  };

  $scope.filter = function() {

  };

  $scope.onSort = function(event, args) {

    //TODO: implement client side sorting (prevent losing O2M changes).
    if ($scope.isDirty() && !$scope.editorCanSave) {
      return;
    }

    var records = $scope.getItems();
    if (records == null || records.length === 0)
      return;

    for (var i = 0; i < records.length; i++) {
      var item = records[i];
      if (!item.id || item.id <= 0) {
        return;
      }
    }

    var sortBy = [];

    angular.forEach(args.sortCols, function(column){
      var field = column.sortCol.descriptor;
      var name = column.sortCol.field;
      if (field.jsonField) {
        if (field.type === 'many-to-one' && field.targetName) {
          name = name + "." + field.targetName;
        }
        name += '::' + ('integer,boolean,decimal'.indexOf(field.type) > -1 ? field.type : 'text');
      }
      var spec = column.sortAsc ? name : '-' + name;
      sortBy.push(spec);
    });

    var ids = _.pluck(records, 'id');
    var criterion = {
      'fieldName': 'id',
      'operator': 'in',
      'value': ids
    };

    var fields = $scope.selectFields();
    var filter = {
      operator: 'and',
      criteria: [criterion]
    };

    $scope.selection = [];
    $scope._dataSource.search({
      filter: filter,
      fields: fields,
      sortBy: sortBy,
      archived: true,
      limit: -1,
      domain: null,
      context: null
    });
  };

  $scope.onShow = function(viewPromise) {

  };

  $scope.show();
}

ui.ManyToManyCtrl = ManyToManyCtrl;
ui.ManyToManyCtrl.$inject = ['$scope', '$element', 'DataSource', 'ViewService'];

function ManyToManyCtrl($scope, $element, DataSource, ViewService) {

  OneToManyCtrl.call(this, $scope, $element, DataSource, ViewService, function(){
    $scope.editorCanSave = true;
    $scope.selectEnable = true;
  });

  var _setValue = $scope.setValue;
  $scope.setValue = function(value, trigger) {
    var compact = _.map(value, function(item) {
      return {
        id: item.id,
        $version: item.version
      };
    });
    _setValue(compact, trigger);
  };
}

ui.formInput('OneToMany', {

  css: 'one2many-item',

  transclude: true,

  showTitle: false,

  collapseIfEmpty: true,

  controller: OneToManyCtrl,

  link: function(scope, element, attrs, model) {

    scope.ngModel = model;
    scope.title = attrs.title;

    scope.formPath = scope.formPath ? scope.formPath + "." + attrs.name : attrs.name;

    var doRenderUnwatch = null;
    var doViewPromised = false;

    var validate = model.$validators.valid || function () { return true; };
    model.$validators.valid = function(modelValue, viewValue) {
      if (scope.isRequired() && _.isEmpty(viewValue)) return false;
      return validate.call(model.$validators, viewValue);
    };

    if (scope.field.requiredIf) {
      scope.$watch("attr('required')", function o2mRequiredWatch(required) {
        if (required !== undefined) {
          model.$validate();
        }
      });
    }
    if (scope.field.validIf) {
      scope.$watch("attr('valid')", function o2mValidWatch(valid) {
        if (valid !== undefined) {
          model.$setValidity('invalid', valid);
        }
      });
    }

    function doRender() {
      if (doRenderUnwatch) {
        return;
      }
      doRenderUnwatch = scope.$watch(function o2mFetchWatch() {
        if (!isVisible() || !doViewPromised) {
          return;
        }
        doRenderUnwatch();
        doRenderUnwatch = null;
          scope.$$fetchData();
        });
    }

    function isVisible() {
      return !element.is(':hidden');
    }

    scope._viewPromise.then(function () {
      doViewPromised = true;
      if (doRenderUnwatch) {
        doRenderUnwatch();
        doRenderUnwatch = null;
        doRender();
      }
      });

    model.$render = doRender;

    var adjustHeight = false;
    var adjustSize = (function() {
      var rowSize = +(scope.field.rowHeight) || 26;
      var	minSize = 56;
      var elem = element;
      if (elem.is('.panel-related')) {
        elem = element.children('.panel-body');
        minSize = 28;
      } else if (scope.$hasPanels) {
        minSize += 28;
      }
      if (elem.is('.picker-input')) {
        elem = null;
      }

      var inc = 0,
        height = +(scope.field.height) || 10;
      var maxSize = (rowSize * height) + minSize;

      var unwatch = scope.$watch('schema', function (schema) {
        if (schema) {
          unwatch();
          unwatch = null;
          if (schema.rowHeight && schema.rowHeight !== rowSize) {
            rowSize = schema.rowHeight;
            maxSize = (rowSize * height) + minSize;
          }
        }
      });

      return function(value) {
        inc = arguments[1] || inc;
        var count = Math.max(_.size(value), scope.dataView.getLength()) + inc, height = minSize;
        if (count > 0) {
          height = (rowSize * count) + (minSize + rowSize);
        }
        if (elem && adjustHeight) {
          elem.css('min-height', count ? Math.min(height, maxSize) : (minSize + 26));
        }
        axelor.$adjustSize();
      };
    })();

    var collapseIfEmpty = this.collapseIfEmpty;
    scope.$watch(attrs.ngModel, function o2mAdjustWatch(value){
      if (!value) {
        // clear data view
        scope.dataView.setItems([]);
      }
      if (collapseIfEmpty) {
        adjustSize(value);
      }
    });

    function deleteItemsById(id) {
      var items = scope.dataView.getItems() || [];
      while (items.length > 0) {
        var item = _.findWhere(items, {id: id});
        var index = _.indexOf(items, item);
        if (index === -1) {
          break;
        }
        items.splice(index, 1);
      }
      return items;
    }

    scope.onGridInit = function(grid, inst) {
      var editIcon = scope.canView() || (!scope.isReadonly() && scope.canEdit());
      var editable = grid.getOptions().editable && !axelor.device.mobile;

      adjustHeight = true;

      if (editable) {
        element.addClass('inline-editable');
        scope.$on('on:new', function(event){
          var items = deleteItemsById(0);
          if (items.length === 0) {
            scope.dataView.setItems([]);
            grid.setSelectedRows([]);
          }
        });
        scope.$watch("isReadonly()", function o2mReadonlyWatch(readonly) {
          grid.setOptions({
            editable: !readonly && scope.canEdit()
          });

          var _editIcon = scope.canView() || (!readonly && scope.canEdit());
          if (_editIcon != editIcon) {
            inst.showColumn('_edit_column', editIcon = _editIcon);
          }
        });

        adjustSize(scope.getValue(), 1);
      } else {
        adjustSize(scope.getValue());
      }

      inst.showColumn('_edit_column', editIcon);

      grid.onAddNewRow.subscribe(function (e, args) {
        var items = scope.getValue() || [];
        var rows = grid.getDataLength();
        adjustSize(items, rows - items.length + 1);
      });

      scope.dataView.onRowCountChanged.subscribe(function (e, args) {
        adjustSize();
      });

      if (!(scope._viewParams || {}).summaryView || scope.field.widget === "MasterDetail") {
        return;
      }
      var col = {
        id: '_summary',
        name: '<span>&nbsp;</span>',
        sortable: false,
        resizable: false,
        width: 16,
        formatter: function(row, cell, value, columnDef, dataContext) {
          return '<i class="fa fa-caret-right" style="display: inline-block; cursor: pointer; padding: 4px 10px;"></i>';
        }
      };

      var cols = grid.getColumns();
      cols.splice(0, 0, col);

      grid.setColumns(cols);
      grid.onClick.subscribe(function(e, args) {
        if ($(e.target).is('.fa-caret-right'))
          scope.$timeout(function(){
            grid.setSelectedRows([args.row]);
            grid.setActiveCell(args.row, args.cell);
            scope.$timeout(function () {
              scope.onSummary();
            });
          });
      });
    };

    scope.onGridBeforeSave = function(records) {
      if (!scope.editorCanSave) {
        deleteItemsById(0);
        scope.select(records);
        return false;
      }
      return true;
    };

    scope.onGridAfterSave = function(records, args) {
      if (scope.editorCanSave) {
        scope.select(records);
      }
    };

    scope.isDisabled = function() {
      return this.isReadonly();
    };

    var field = scope.field;
    if (field.widget === 'MasterDetail') {
      setTimeout(function(){
        scope.showDetailView();
      });
    }

    scope.$watch("attr('title')", function o2mTitleWatch(title){
      scope.title = title;
    });
  },

  template_editable: null,

  template_readonly: null,

  template:
  "<div class='stackbar' ng-class='{noEdit: canView() && !canEdit()}'>" +
  "<div class='navbar'>" +
      "<div class='navbar-inner'>" +
          "<div class='container-fluid'>" +
              "<span class='brand' href='' ui-help-popover ng-bind-html='title'></span>" +
          "<span class='icons-bar dropdown pull-right' ng-show='viewCanCopy() || viewCanExport()'>" +
          "<a href='' class='dropdown-toggle' data-toggle='dropdown'><i class='fa fa-caret-down'></i></a>" +
          "<ul class='dropdown-menu'>" +
            "<li ng-show='viewCanCopy()'><a href='' ng-click='onCopy()' x-translate>Duplicate</a></li>" +
            "<li ng-show='viewCanExport()'><a href='' ng-click='onExport()' x-translate>Export</a></li>" +
          "</ul>" +
        "</span>" +
              "<span class='icons-bar pull-right' ng-show='!isReadonly()'>" +
          "<a href='' ng-click='onSelect()' ng-show='hasPermission(\"read\") && !isDisabled() && canSelect()'>" +
            "<i class='fa fa-search'></i><span x-translate>Select</span>" +
          "</a>" +
          "<a href='' ng-click='onNew()' ng-show='hasPermission(\"create\") && !isDisabled() && canNew()'>" +
            "<i class='fa fa-plus'></i><span x-translate>New</span>" +
          "</a>" +
          "<a href='' ng-click='onEdit()' ng-show='hasPermission(\"read\") && canShowEdit()'>" +
            "<i class='fa fa-pencil'></i><span x-translate>Edit</span>" +
          "</a>" +
          "<a href='' ng-click='onEdit()' ng-show='hasPermission(\"read\") && canShowView()'>" +
            "<i class='fa fa-file-text-o'></i><span x-translate>Show</span>" +
          "</a>" +
          "<a href='' ng-click='onRemove()' ng-show='hasPermission(\"read\") && !isDisabled() && canRemove()'>" +
            "<i class='fa fa-remove'></i><span x-translate>Remove</span>" +
          "</a>" +
                  "<i ng-click='onCopy()' ng-show='hasPermission(\"create\") && !isDisabled() && canCopy()' title='{{\"Duplicate\" | t}}' class='fa fa-files-o'></i>" +
              "</span>" +
          "</div>" +
      "</div>" +
  "</div>" +
  "<div ui-view-grid " +
      "x-view='schema' " +
      "x-data-view='dataView' " +
      "x-handler='this' " +
      "x-no-filter='true' " +
      "x-on-init='onGridInit' " +
      "x-on-before-save='onGridBeforeSave' " +
      "x-on-after-save='onGridAfterSave' " +
      "></div>" +
  "</div>"
});

ui.formInput('ManyToMany', 'OneToMany', {

  css	: 'many2many-item',

  controller: ManyToManyCtrl
});

var panelRelatedTemplate =
"<div class='panel panel-related' ng-class='{noEdit: canView() && !canEdit()}'>" +
  "<div class='panel-header'>" +
    "<div class='panel-title'><span ui-help-popover ng-bind-html='title'></span></div>" +
    "<div class='icons-bar' ng-show='!isReadonly()'>" +
      "<a href='' ng-click='onSelect()' ng-show='hasPermission(\"read\") && !isDisabled() && canSelect()'>" +
        "<i class='fa fa-search'></i><span x-translate>Select</span>" +
      "</a>" +
      "<a href='' ng-click='onNew()' ng-show='hasPermission(\"create\") && !isDisabled() && canNew()'>" +
        "<i class='fa fa-plus'></i><span x-translate>New</span>" +
      "</a>" +
      "<a href='' ng-click='onEdit()' ng-show='hasPermission(\"read\") && canShowEdit()'>" +
        "<i class='fa fa-pencil'></i><span x-translate>Edit</span>" +
      "</a>" +
      "<a href='' ng-click='onEdit()' ng-show='hasPermission(\"read\") && canShowView()'>" +
        "<i class='fa fa-file-text-o'></i><span x-translate>Show</span>" +
      "</a>" +
      "<a href='' ng-click='onRemove()' ng-show='hasPermission(\"read\") && !isDisabled() && canRemove()'>" +
        "<i class='fa fa-remove'></i><span x-translate>Remove</span>" +
      "</a>" +
    "</div>" +
    "<div class='icons-bar dropdown' ng-show='viewCanCopy() || viewCanExport()'>" +
      "<a href='' class='dropdown-toggle' data-toggle='dropdown'><i class='fa fa-caret-down'></i></a>" +
      "<ul class='dropdown-menu'>" +
        "<li ng-show='viewCanCopy()'><a href='' ng-click='onCopy()' x-translate>Duplicate</a></li>" +
        "<li ng-show='viewCanExport()'><a href='' ng-click='onExport()' x-translate>Export</a></li>" +
      "</ul>" +
    "</div>" +
  "</div>" +
  "<div class='panel-body panel-layout'>" +
    "<div ui-view-grid " +
      "x-view='schema' " +
      "x-data-view='dataView' " +
      "x-handler='this' " +
      "x-no-filter='true' " +
      "x-on-init='onGridInit' " +
      "x-on-before-save='onGridBeforeSave' " +
      "x-on-after-save='onGridAfterSave'></div>" +
  "</div>" +
"</div>";

ui.formInput('PanelOneToMany', 'OneToMany', {
  template_editable: null,
  template_readonly: null,
  template: panelRelatedTemplate
});

ui.formInput('PanelManyToMany', 'ManyToMany', {
  template_editable: null,
  template_readonly: null,
  template: panelRelatedTemplate
});

ui.formInput('TagSelect', 'ManyToMany', 'MultiSelect', {

  css	: 'many2many-tags',

  showTitle: true,

  init: function(scope) {
    this._super(scope);

    var nameField = scope.field.targetName || 'id';

    scope.parse = function(value) {
      return value;
    };

    scope.formatItem = function(item) {
      if (item && scope._items && item.id in scope._items) {
        item = scope._items[item.id];
      }
      return item ? item[nameField] : item;
    };

    scope.getItems = function() {
      return _.pluck(this.getSelection(), "value");
    };

    var _select = scope.select;
    scope.select = function (value) {
      var res = _select.apply(scope, arguments);
      scope.itemsPending = [];
      return res;
    };

    scope.handleClick = function(e, item) {
      if (scope.field['tag-edit'] && scope.onTagEdit && !scope.isReadonly()) {
        return scope.onTagEdit(e, item);
      }
      scope.showEditor(item);
    };
  },

  link: function(scope, element, attrs, model) {
    this._super.apply(this, arguments);
    var field = scope.field;
    // special case for json fields
    if (field.jsonField) {
      var nameField = scope.field.targetName;
      var ds = scope._dataSource._new(scope._dataSource._model);
      scope.$watch(attrs.ngModel, function m2mValueWatch(value, old) {
        scope._items = null;
        if (value && value.length && value[0][nameField] === undefined) {
          ds.search({
            fields: ['id', nameField],
            domain: "id in :ids",
            context: { ids: _.pluck(value, 'id') }
          }).success(function (records) {
            scope._items = _.reduce(records, function(memo, item) {
              memo[item.id] = item;
              return memo;
            }, {});
            model.$render();
          });
        }
      }, true);
    }
  },

  link_editable: function(scope, element, attrs, model) {
    this._super.apply(this, arguments);

    var input = this.findInput(element);
    var field = scope.field;

    function create(term, popup) {
      scope.createOnTheFly(term, popup, function (record) {
        scope.select(record);
        setTimeout(function() {
          input.focus();
        });
      });
    }

    scope.loadSelection = function(request, response) {

      if (!scope.canSelect()) {
        return response([]);
      }

      this.fetchSelection(request, function(items, page) {
        var term = request.term;
        var text = '<strong><em>' + term + '</em></strong>';
        var canSelect = scope.canSelect() && (items.length < page.total || (request.term && items.length === 0));
        if (field.create && term && scope.canNew()) {
          items.push({
            label : _t('Create "{0}" and select...', text),
            click : function() { create(term); }
          });
          items.push({
            label : _t('Create "{0}"...', text),
            click : function() { create(term, true); }
          });
        }
        if (canSelect) {
          items.push({
            label : _t("Search more..."),
            click : function() { scope.showSelector(); }
          });
        }
        if ((field.create === undefined || (field.create && !term)) && scope.canNew()) {
          items.push({
            label: _t("Create..."),
            click: function() { scope.showPopupEditor(); }
          });
        }
        response(items);
      });
    };

    scope.matchValues = function(a, b) {
      if (a === b) return true;
      if (!a) return false;
      if (!b) return false;
      return a.id === b.id;
    };

    var _setValue = scope.setValue;
    scope.setValue = function(value, fireOnChange) {
      var items = _.map(value, function(item) {
        if (item.version === undefined) {
          return item;
        }
        var ver = item.version;
        var val = _.omit(item, "version");
        val.$version = ver;
        return val;
      });
      items = _.isEmpty(items) ? null : items;
      return _setValue.call(this, items, fireOnChange);
    };

    var _handleSelect = scope.handleSelect;
    scope.handleSelect = function(e, ui) {
      if (ui.item.click) {
        setTimeout(function(){
          input.val("");
        });
        ui.item.click.call(scope);
        return scope.$applyAsync();
      }
      return _handleSelect.apply(this, arguments);
    };

    var _removeItem = scope.removeItem;
    scope.removeItem = function(e, ui) {
      if (scope.attr('canRemove') === false) return;
      _removeItem.apply(this, arguments);
    };

    if (scope.field && scope.field['tag-edit']) {
      scope.attachTagEditor(scope, element, attrs);
    }
  }
});

ui.InlineOneToManyCtrl = InlineOneToManyCtrl;
ui.InlineOneToManyCtrl.$inject = ['$scope', '$element', 'DataSource', 'ViewService'];

function InlineOneToManyCtrl($scope, $element, DataSource, ViewService) {

  var field = $scope.field || $scope.getViewDef($element);
  var params = {
    model: field.target
  };

  if (field.editor) {
    params.views = [{
      type: 'grid',
      items: field.editor.items
    }];
  }

  $scope._viewParams = params;

  OneToManyCtrl.call(this, $scope, $element, DataSource, ViewService, function(){
    $scope.editorCanSave = false;
    $scope.selectEnable = false;
  });
}

// used in panel form
ui.formInput('InlineOneToMany', 'OneToMany', {

  showTitle: true,

  controller: InlineOneToManyCtrl,

  link: function(scope, element, attrs, model) {
    this._super.apply(this, arguments);

    scope.onGridInit = function() {};
    scope.items = [];

    var showOnNew = (scope.field.editor||{}).showOnNew !== false;
    var unwatch = null;

    function canAdd() {
      return scope.hasPermission("write") && !scope.isDisabled() && scope.canNew();
    }

    model.$render = function () {
      if (unwatch) {
        unwatch();
        unwatch = null;
      }
      scope.items = model.$viewValue;
      if ((scope.items && scope.items.length > 0) || scope.$$readonly) {
        return;
      }
      scope.items = showOnNew && canAdd() ? [{}] : [];
      unwatch = scope.$watch('items[0]', function o2mFirstItemWatch(item, old) {
        if (!item) return;
        if (item.$changed) {
          unwatch();
          model.$setViewValue(scope.items);
        }
        item.$changed = true;
      }, true);
    };

    function isEmpty(record) {
      if (!record || _.isEmpty(record)) return true;
      var values = _.filter(record, function (value, name) {
        return !(/[\$_]/.test(name) || value === null || value === undefined);
      });
      return values.length === 0;
    }

    function itemsChanged() {
      var items = scope.items;
      if (items && items.length > 0) {
        var changed = false;
        var values = _.filter(items, function (item) {
          if (item.$changed) {
            changed = true;
          }
          return !isEmpty(item);
        });
        if (changed && values.length) {
          model.$setViewValue(values);
        }
      }
    }

    scope.$watch('items', itemsChanged, true);
    scope.$itemsChanged = itemsChanged;

    scope.$watch('$$readonly', function o2mReadonlyWatch(readonly, old) {
      if (readonly === undefined) return;
      var items = model.$viewValue;
      if (_.isEmpty(items)) {
        scope.items = (showOnNew && canAdd() && !readonly) ? [{}] : items;
      }
    });

    scope.$copy = function (record) {
      return angular.copy(record);
    };

    scope.addItem = function () {
      var items = scope.items || (scope.items = []);
      var item = _.last(items);
      if (items && items.length && isEmpty(item)) {
        return;
      }
      if (canAdd()) {
        items.push({});
      }
    };

    scope.removeItem = function (index) {
      var items = scope.items;
      items.splice(index, 1);
      var values = _.filter(items, function (item) {
        return !isEmpty(item);
      });
      if (items.length === 0 && showOnNew) {
        scope.addItem();
      }
      model.$setViewValue(values);
    };

    scope.canRemove = function () {
      return scope.attr('canRemove') !== false;
    };

    scope.setValidity = function (key, value, record) {
      if (arguments.length === 3) {
        record.$valid = value;
        value = _.all(scope.items, function (x) { return x.$valid; });
      }
      model.$setValidity(key, value);
    };

    scope.setExclusive = function (name, record) {
      _.each(scope.items, function (item) {
        if (record !== item) {
          item[name] = false;
        }
      });
    };
  },

  template_readonly:function (scope) {
    var field = scope.field;
    var tmpl = (field.viewer || {}).template;
    if (!tmpl && field.editor && (field.editor.viewer || !field.targetName)) {
      tmpl = '<div class="o2m-editor-form" ui-panel-editor></div>';
    }
    if (!tmpl && field.targetName) {
      tmpl = '{{record.' + field.targetName + '}}';
    }
    tmpl = tmpl || '{{record.id}}';
    return "<div class='o2m-list'>" +
    "<div class='o2m-list-row' ng-class-even=\"'even'\" ng-repeat='record in items' ng-init='$$original = $copy(record)'>" + tmpl + "</div>" +
    "</div>";
  },

  template_editable: function (scope) {
    return "<div class='o2m-list'>" +
      "<div class='o2m-list-row' ng-class-even=\"'even'\" ng-repeat='record in items' ng-init='$$original = $copy(record)'>" +
        "<div class='o2m-editor-form' ui-panel-editor></div>" +
        "<span class='o2m-list-remove' ng-show='hasPermission(\"remove\") && !isDisabled() && canRemove()'>" +
          "<a tabindex='-1' href='' ng-click='removeItem($index)' title='{{\"Remove\" | t}}'><i class='fa fa-times'></i></a>" +
        "</span>" +
      "</div>" +
      "<div class='o2m-list-row o2m-list-add' ng-show='hasPermission(\"write\") && !isDisabled() && canNew()'>" +
        "<a tabindex='-1' href='' ng-click='addItem()'  title='{{\"Add\" | t}}'><i class='fa fa-plus'></i></a>" +
      "</div>" +
    "</div>";
  },
  template: null
});

//used in panel form
ui.formInput('InlineManyToMany', 'InlineOneToMany', {

});

// used in editable grid
ui.formInput('OneToManyInline', 'OneToMany', {

  css	: 'one2many-inline',

  collapseIfEmpty : false,

  link: function(scope, element, attrs, model) {

    this._super.apply(this, arguments);

    scope.onSort = function() {

    };

    var field = scope.field;
    var input = element.children('input');
    var grid = element.children('[ui-slick-grid]');

    var container = null;
    var wrapper = $('<div class="slick-editor-dropdown"></div>')
      .css("position", "absolute")
      .hide();

    var render = model.$render,
      renderPending = false;
    model.$render = function() {
      if (wrapper.is(":visible")) {
        renderPending = false;
        render();
        grid.trigger('adjust:size');
      } else {
        renderPending = true;
      }
    };

    scope.waitForActions(function(){
      container = element.parents('.ui-dialog-content,.view-container').first();
      grid.height(175).appendTo(wrapper);
      wrapper.height(175).appendTo(container);
    });

    function adjust() {
      if (!wrapper.is(":visible"))
        return;
      if (axelor.device.small) {
        dropdownVisible = false;
        return wrapper.hide();
      }
      wrapper.position({
        my: "left top",
        at: "left bottom",
        of: element,
        within: container
      })
      .zIndex(element.zIndex() + 1)
      .width(element.width());
    }

    var dropdownVisible = false;
    scope.onDropdown = function () {
      dropdownVisible = !dropdownVisible;
      if (!dropdownVisible) {
        wrapper.hide();
        return;
      }
      if (renderPending) {
        renderPending = false;
        render();
        setTimeout(function () {
          axelor.$adjustSize();
        });
      }
      wrapper.show();
      adjust();
    };

    scope.canDropdown = function () {
      return !axelor.device.small;
    };

    scope.canShowAdd = function () {
      return dropdownVisible && scope.canEdit();
    };

    scope.canShowRemove = function () {
      return dropdownVisible && scope.canRemove() && !_.isEmpty(scope.selection);
    };

    element.on("hide:slick-editor", function(e){
      dropdownVisible = false;
      wrapper.hide();
    });

    scope.$onAdjust(adjust, 300);

    input.on('keydown', function (e) {
      if (e.keyCode === 40 && e.ctrlKey && !dropdownVisible) {
        scope.onDropdown();
      }
    });

    function hidePopup(e) {
      if (element.is(':hidden')) {
        return;
      }
      var all = element.add(wrapper);
      var elem = $(e.target);
      if (all.is(elem) || all.has(elem).length > 0) return;
      if (elem.zIndex() > element.parents('.slickgrid:first').zIndex()) return;
      if (elem.parents(".ui-dialog:first").zIndex() > element.parents('.slickgrid:first').zIndex()) return;

      element.trigger('close:slick-editor');
    }

    $(document).on('mousedown.mini-grid', hidePopup);

    scope.$watch(attrs.ngModel, function o2mModelWatch(value) {
      var text = "";
      if (value && value.length)
        text = "(" + value.length + ")";
      input.val(text);
    });

    scope.$watch('schema.loaded', function o2mSchemaWatch(viewLoaded) {
      var schema = scope.schema;
      if (schema && scope.attr('canEdit') === false) {
        schema.editIcon = false;
      }
    });

    scope.$on("$destroy", function(e){
      wrapper.remove();
      $(document).off('mousedown.mini-grid', hidePopup);
    });

    scope.canEdit = function () {
      return scope.hasPermission('create') && !scope.isReadonly() && scope.attr('canEdit') !== false;
    };

    scope.canRemove = function() {
      return scope.hasPermission('create') && !scope.isReadonly() && scope.attr('canEdit') !== false;
    };
  },

  template_editable: null,

  template_readonly: null,

  template:
  '<span class="picker-input picker-icons-2" style="position: absolute;">'+
    '<input type="text" readonly>'+
    '<span class="picker-icons">'+
      '<i class="fa fa-plus" ng-click="onSelect()" ng-show="canShowAdd()" title="{{\'Select\' | t}}"></i>'+
      '<i class="fa fa-minus" ng-click="onRemove()" ng-show="canShowRemove()" title="{{\'Select\' | t}}"></i>'+
      '<i class="fa fa-caret-down" ng-show="canDropdown()" ng-click="onDropdown()" title="{{\'Show\' | t}}"></i>'+
    '</span>'+
    '<div ui-view-grid ' +
      'x-view="schema" '+
      'x-data-view="dataView" '+
      'x-handler="this" '+
      'x-no-filter="true" '+
      'x-on-init="onGridInit" '+
      'x-on-before-save="onGridBeforeSave" '+
      'x-on-after-save="onGridAfterSave" '+
      '></div>'+
  '</span>'
});

ui.formInput('ManyToManyInline', 'OneToManyInline', {

  css	: 'many2many-inline',

  controller: ManyToManyCtrl,

  link: function(scope, element, attrs, model) {
    this._super.apply(this, arguments);
  }
});

})();
