/*
 * Copyright 2003-2016 JetBrains s.r.o.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package jetbrains.mps.lang.editor.cellProviders;

import jetbrains.mps.editor.runtime.cells.AbstractCellAction;
import jetbrains.mps.editor.runtime.descriptor.AbstractEditorBuilder;
import jetbrains.mps.editor.runtime.descriptor.EditorBuilderEnvironment;
import jetbrains.mps.editor.runtime.impl.cellActions.CellAction_DeleteSimple;
import jetbrains.mps.lang.smodel.generator.smodelAdapter.AttributeOperations;
import jetbrains.mps.nodeEditor.SNodeEditorUtil;
import jetbrains.mps.nodeEditor.cellActions.CellAction_DeleteNode.DeleteDirection;
import jetbrains.mps.nodeEditor.cells.EditorCell_Constant;
import jetbrains.mps.nodeEditor.cells.EditorCell_Error;
import jetbrains.mps.nodeEditor.cells.EditorCell_Label;
import jetbrains.mps.openapi.editor.EditorContext;
import jetbrains.mps.openapi.editor.cells.CellActionType;
import jetbrains.mps.openapi.editor.cells.EditorCell;
import jetbrains.mps.openapi.editor.cells.EditorCell_Collection;
import jetbrains.mps.smodel.action.NodeFactoryManager;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.mps.openapi.language.SContainmentLink;
import org.jetbrains.mps.openapi.model.SNode;

import java.util.Iterator;


/**
 * @author simon
 */
public abstract class SingleRoleCellProvider extends AbstractEditorBuilder implements EditorBuilderEnvironment {

  protected final SContainmentLink myContainmentLink;

  /**
   * @deprecated since MPS 3.5 use {@link #getNode()} method, this field will be removed in the next release
   */
  @Deprecated
  protected final SNode myOwnerNode;

  /**
   * @deprecated since MPS 3.5 use {@link #getEditorContext()} method, this field will be removed in the next release
   */
  @Deprecated
  protected final EditorContext myEditorContext;

  /**
   * @deprecated since MPS 3.5 use {@link #SingleRoleCellProvider(SContainmentLink, EditorContext)} constructor
   */
  @Deprecated
  public SingleRoleCellProvider(final SNode ownerNode, final SContainmentLink containmentLink, EditorContext editorContext) {
    super(editorContext);
    myOwnerNode = ownerNode;
    myContainmentLink = containmentLink;
    myEditorContext = editorContext;
  }

  public SingleRoleCellProvider(final SContainmentLink containmentLink, EditorContext editorContext) {
    super(editorContext);
    myOwnerNode = null;
    myContainmentLink = containmentLink;
    myEditorContext = editorContext;
  }

  /**
   * This is a compatibility implementation of corresponding method from super-class.
   * Since MPS 3.5 this method will be generated in sub-classes.
   * After MPS 3.5 this method should be removed, so sub-classes will implement it directly.
   *
   * @return
   */
  @NotNull
  @Override
  public SNode getNode() {
    return myOwnerNode;
  }

  protected EditorCell createChildCell(SNode child) {
    return createChildCell_internal(child);
  }

  @NotNull
  private EditorCell createChildCell_internal(SNode child) {
    return getEditorContext().getEditorComponent().getUpdater().getCurrentUpdateSession().updateChildNodeCell(child);
  }

  public EditorCell createCell() {
    if (areAttributesEmpty()) {
      return createSingleCell();
    } else {
      EditorCell_Collection resultCell = jetbrains.mps.nodeEditor.cells.EditorCell_Collection.createIndent2(getEditorContext(), getNode());
      addInnerCells(resultCell);
      return resultCell;
    }
  }

  private void addInnerCells(EditorCell_Collection parentCell) {
    SNode realChild = getRealChild();
    for (SNode node : getNodesToPresent()) {
      EditorCell cell;
      if (node == realChild) {
        cell = createChildCell(node);
      } else {
        cell = createChildCell_internal(node);
        cell.setAction(CellActionType.BACKSPACE, new CellAction_DeleteSimple(node, DeleteDirection.BACKWARD));
        cell.setAction(CellActionType.DELETE, new CellAction_DeleteSimple(node, DeleteDirection.FORWARD));
      }
      parentCell.addEditorCell(cell);
    }
    if (realChild == null) {
      parentCell.addEditorCell(createEmptyCellCollection());
    }
  }

  @Nullable
  private SNode getRealChild() {
    Iterator<? extends SNode> childIterator = getNode().getChildren(myContainmentLink).iterator();
    return childIterator.hasNext() ? childIterator.next() : null;
  }

  private EditorCell createSingleCell() {
    SNode child = getRealChild();
    if (child != null) {
      return createChildCell(child);
    } else {
      return createEmptyCellCollection();
    }
  }

  private boolean areAttributesEmpty() {
    return !AttributeOperations.getChildAttributes(getNode(), myContainmentLink).iterator().hasNext();
  }

  private EditorCell createEmptyCellCollection() {
    EditorCell_Collection collection = jetbrains.mps.nodeEditor.cells.EditorCell_Collection.createIndent2(getEditorContext(), getNode());
    collection.setSelectable(false);
    collection.addEditorCell(createEmptyCell());

    collection.setAction(CellActionType.INSERT, new CellAction_InsertInSingleRole());
    collection.setAction(CellActionType.INSERT_BEFORE, new CellAction_InsertInSingleRole());
    return collection;
  }

  protected SNode createNodeToInsert() {
    return NodeFactoryManager.createNode(myContainmentLink.getTargetConcept(), null, getNode(), getNode().getModel());
  }

  protected EditorCell createEmptyCell() {
    EditorCell_Label result = myContainmentLink.isOptional() ? new EditorCell_Constant(getEditorContext(), getNode(), "") :
                              new EditorCell_Error(getEditorContext(), getNode(), getNoTargetText());
    result.setDefaultText(getNoTargetText());
    result.setEditable(true);
    return result;
  }

  protected String getNoTargetText() {
    return "<no " + myContainmentLink.getName() + ">";
  }

  protected Iterable<SNode> getNodesToPresent() {
    return AttributeOperations.getChildNodesAndAttributes(getNode(), myContainmentLink);
  }

  private class CellAction_InsertInSingleRole extends AbstractCellAction {
    @Override
    public void execute(EditorContext context) {
      final SNode nodeToInsert = createNode();
      SNodeEditorUtil.setSingleChild(getNode(), myContainmentLink, nodeToInsert);
    }

    private SNode createNode() {
      return SingleRoleCellProvider.this.createNodeToInsert();
    }
  }
}

