/*
 * Copyright 2003-2015 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.smodel.action;

import jetbrains.mps.lang.smodel.generator.smodelAdapter.AttributeOperations;
import jetbrains.mps.lang.smodel.generator.smodelAdapter.IAttributeDescriptor;
import jetbrains.mps.nodeEditor.SNodeEditorUtil;
import jetbrains.mps.smodel.adapter.MetaAdapterByDeclaration;
import jetbrains.mps.util.annotation.ToRemove;
import org.jetbrains.mps.openapi.language.SAbstractConcept;
import org.jetbrains.mps.openapi.language.SConcept;
import org.jetbrains.mps.openapi.language.SContainmentLink;
import org.jetbrains.mps.openapi.language.SInterfaceConcept;
import org.jetbrains.mps.openapi.model.SNode;
import org.jetbrains.mps.openapi.model.SModel;
import jetbrains.mps.smodel.*;
import org.jetbrains.mps.openapi.model.SNodeUtil;

/**
 * Evgeny Gryaznov, 1/4/11
 */
public class SNodeFactoryOperations {
  @Deprecated
  public static SConcept asInstanceConcept(SNode node) {
    if (node == null) { return null; }
    return MetaAdapterByDeclaration.getInstanceConcept(node);
  }

  @Deprecated
  public static SConcept asInstanceConcept(SAbstractConcept concept) {
    return MetaAdapterByDeclaration.asInstanceConcept(concept);
  }

  public static SNode createNewNode(SConcept concept, SNode prototypeNode) {
    if (concept == null) return null;
    return NodeFactoryManager.createNode(concept, prototypeNode, null, null);
  }

  public static SNode createNewNode(SModel model, SConcept concept, SNode prototypeNode) {
    SNode enclosingNode = null;
    if (prototypeNode != null) {
      enclosingNode = prototypeNode.getParent();
    }

    return NodeFactoryManager.createNode(concept, prototypeNode, enclosingNode, model);
  }

  public static SNode createNewRootNode(SModel model, SConcept concept, SNode prototypeNode) {
    SNode newNode = NodeFactoryManager.createNode(concept, prototypeNode, null, model);
    model.addRootNode(newNode);
    return newNode;
  }

  public static SNode addNewChild(SNode node, SContainmentLink role, SConcept childConcept) {
    if (node != null) {
      SNode newChild = NodeFactoryManager.createNode(childConcept, null, node, node.getModel());
      node.addChild(role, newChild);
      return newChild;
    }
    return null;
  }

  public static SNode addNewAttribute(SNode node, IAttributeDescriptor descriptor, SConcept childConcept) {
    if (node != null) {
      SNode newChild = NodeFactoryManager.createNode(childConcept, null, node, node.getModel());
      AttributeOperations.addAttribute(node, descriptor, newChild);
      return newChild;
    }
    return null;
  }

  public static SNode setNewChild(SNode node, SContainmentLink role, SConcept childConcept) {
    if (node != null) {
      Iterable<? extends SNode> ch = node.getChildren(role);
      SNode prototypeNode = ch.iterator().hasNext() ? ch.iterator().next() : null;
      SNode newChild = NodeFactoryManager.createNode(childConcept, prototypeNode, node, node.getModel());
      SNodeEditorUtil.setSingleChild(node, role, newChild);
      return newChild;
    }
    return null;
  }

  public static SNode setNewAttribute(SNode node, IAttributeDescriptor descriptor, SConcept childConcept) {
    if (node != null) {
      SNode prototypeNode = AttributeOperations.getAttribute(node, descriptor);
      SNode newChild = NodeFactoryManager.createNode(childConcept, prototypeNode, node, node.getModel());
      AttributeOperations.setAttribute(node, descriptor, newChild);
      return newChild;
    }
    return null;
  }

  public static SNode replaceWithNewChild(SNode oldChild, SConcept concept) {
    assert oldChild != null : "can't replace node. node is NULL";
    SNode oldChildParent = oldChild.getParent();
    if (oldChildParent == null && !(oldChild.getModel() != null && oldChild.getParent() == null)) {
      return null;
    }
    SModel model = oldChild.getModel();
    SNode newChild = NodeFactoryManager.createNode(concept, oldChild, oldChildParent, model);
    if (newChild == null) return null;
    if (oldChildParent == null) {
      model.addRootNode(newChild);
      model.removeRootNode(oldChild);
    } else {
      SNodeUtil.replaceWithAnother(oldChild, newChild);
    }
    copyAllAttributes(oldChild, newChild);
    return newChild;
  }

  private static void copyAllAttributes(SNode oldChild, SNode newChild) {
    for (SNode attribute : AttributeOperations.getAllAttributes(oldChild)) {
      // todo: should we create check like this?
      // no such property in new child : don't copy the attribute
//          LOG.error("couldn't copy attribute " + attribute.getOwner().getName() + " for property '" + propertyName + "' : so such property in concept " + newChild.getContainingConcept().getName(), newChild);
      // no such link in new child : don't copy the attribute
//          LOG.error("couldn't copy attribute " + attribute.getOwner().getName() + " for link '" + linkRole + "' : so such link in concept " + newChild.getContainingConcept().getName(), newChild);

      SNode newAttribute = CopyUtil.copy(attribute);
      newChild.addChild(attribute.getContainmentLink(), newAttribute);
    }
  }

  public static SNode insertNewNextSiblingChild(SNode node, SConcept concept) {
    if (node == null || node.getParent() == null) return null;
    SNode parent = node.getParent();
    SNode newChild = NodeFactoryManager.createNode(concept, null, parent, node.getModel());
    if (newChild == null) return null;
    SContainmentLink role = node.getContainmentLink();
    parent.insertChildAfter(role, newChild, node);
    return newChild;
  }

  public static SNode insertNewPrevSiblingChild(SNode node, SConcept concept) {
    if (node == null) return null;
    SNode parent = node.getParent();
    if (parent == null) return null;
    SNode newChild = NodeFactoryManager.createNode(concept, null, parent, node.getModel());
    if (newChild == null) return null;
    SContainmentLink role = node.getContainmentLink();
    parent.insertChildBefore(role, newChild, node);
    return newChild;
  }
}
