/*
 * 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.menus.substitute;

import jetbrains.mps.kernel.model.SModelUtil;
import jetbrains.mps.openapi.editor.menus.substitute.SubstituteMenuContext;
import jetbrains.mps.openapi.editor.menus.substitute.SubstituteMenuItem;
import jetbrains.mps.smodel.adapter.MetaAdapterByDeclaration;
import jetbrains.mps.smodel.constraints.ModelConstraints;
import jetbrains.mps.smodel.constraints.ReferenceDescriptor;
import jetbrains.mps.smodel.presentation.ReferenceConceptUtil;
import jetbrains.mps.util.IterableUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
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.SReferenceLink;
import org.jetbrains.mps.openapi.model.SNode;
import org.jetbrains.mps.openapi.model.SNodeReference;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

/**
 * @author simon
 */
public class SimpleConceptSubstituteMenuPart implements SubstituteMenuPart {
  @NotNull
  private SConcept myConcept;

  public SimpleConceptSubstituteMenuPart(@NotNull SAbstractConcept concept) {
    if (concept instanceof SConcept) {
      myConcept = ((SConcept) concept);
    } else {
      myConcept = MetaAdapterByDeclaration.asInstanceConcept(concept);
    }
  }

  @NotNull
  @Override
  public List<SubstituteMenuItem> createItems(SubstituteMenuContext context) {
    List<SubstituteMenuItem> smartItems = createSmartItemsItems(context);
    if (smartItems != null) {
      return smartItems;
    }
    return Collections.singletonList(
        new DefaultSubstituteMenuItem(myConcept, context.getParentNode(), context.getCurrentTargetNode(), context.getEditorContext()));

  }

  @Nullable
  private List<SubstituteMenuItem> createSmartItemsItems(SubstituteMenuContext context) {
    SReferenceLink smartReference = ReferenceConceptUtil.getCharacteristicReference(myConcept);
    SNode smartReferenceNode = getSmartReferenceNode(context);
    final SAbstractConcept smartReferenceNodeTargetConcept = getSpecialSmartReferenceTargetConcept(smartReferenceNode);

    if (smartReference != null) {
      SAbstractConcept targetConcept = smartReference.getTargetConcept();
      if (smartReferenceNodeTargetConcept != null && checkSmartReferenceNodeSpecializesSmartReference(smartReferenceNode, smartReference)) {
        targetConcept = smartReferenceNodeTargetConcept;
      }
      return createSmartSubstituteMenuItems(context, smartReference, targetConcept, createSmartReferenceDescriptor(context));
    } else if (smartReferenceNode != null && smartReferenceNodeTargetConcept != null) {
      final SReferenceLink smartReferenceNodeReference = getReferenceLink(smartReferenceNode);
      if (smartReferenceNodeReference != null) {
        return createSmartSubstituteMenuItems(context, smartReferenceNodeReference,
            smartReferenceNodeTargetConcept, createSmartReferenceDescriptorByNode(context, getMyConceptSourceNode(context)));
      }
    }
    return null;
  }

  private SNode getSmartReferenceNode(SubstituteMenuContext context) {
    final SNode conceptSourceNode = getMyConceptSourceNode(context);
    if (conceptSourceNode == null) {
      return null;
    }
    return ReferenceConceptUtil.getCharacteristicReference(
        conceptSourceNode);
  }

  @Nullable
  private SNode getMyConceptSourceNode(SubstituteMenuContext context) {
    final SNodeReference conceptSourceNodeRef = myConcept.getSourceNode();
    if (conceptSourceNodeRef == null) {
      return null;
    }

    final SNode conceptSourceNode = conceptSourceNodeRef.resolve(context.getEditorContext().getRepository());
    if (conceptSourceNode == null) {
      return null;
    }
    return conceptSourceNode;
  }

  //todo remove this method when we will get rid of specialized concepts
  @Nullable
  private SAbstractConcept getSpecialSmartReferenceTargetConcept(SNode specialSmartReference) {
    final SNode specializedTargetConceptNode = SModelUtil.getLinkDeclarationTarget(specialSmartReference);
    if (specializedTargetConceptNode == null) {
      return null;
    }
    return MetaAdapterByDeclaration.getConcept(specializedTargetConceptNode);
  }

  //todo remove this method when we will get rid of specialized concepts
  private boolean checkSmartReferenceNodeSpecializesSmartReference(SNode specialSmartReference, @NotNull SReferenceLink smartReference) {
    if (specialSmartReference == null) {
      return false;
    }
    final SNode genuineLinkDeclaration = SModelUtil.getGenuineLinkDeclaration(specialSmartReference);
    if (genuineLinkDeclaration == null || genuineLinkDeclaration == specialSmartReference) {
      return false;
    }
    return smartReference.equals(MetaAdapterByDeclaration.getReferenceLink(genuineLinkDeclaration));
  }

  //todo remove this method when we will get rid of specialized concepts
  private SReferenceLink getReferenceLink(SNode referenceNode) {
    final SNode genuineLinkDeclaration = SModelUtil.getGenuineLinkDeclaration(referenceNode);
    if (genuineLinkDeclaration == null) {
      return null;
    }
    return MetaAdapterByDeclaration.getReferenceLink(genuineLinkDeclaration);
  }

  @NotNull
  private List<SubstituteMenuItem> createSmartSubstituteMenuItems(SubstituteMenuContext context, SReferenceLink smartReference,
      SAbstractConcept targetConcept, ReferenceDescriptor refDescriptor) {
    SNode currentChild = context.getCurrentTargetNode();
    SNode parentNode = context.getParentNode();

    List<SubstituteMenuItem> result = new ArrayList<>();
    Iterable<SNode> referentNodes = refDescriptor.getScope().getAvailableElements(null);
    for (SNode referentNode : referentNodes) {
      if (referentNode == null ||
          !checkForSubconcept(targetConcept, referentNode)) {
        continue;
      }
      result.add(new SmartReferenceSubstituteMenuItem(referentNode, parentNode,
          currentChild, myConcept, smartReference, refDescriptor, context.getEditorContext()));
    }
    return result;
  }

  @NotNull
  private ReferenceDescriptor createSmartReferenceDescriptor(SubstituteMenuContext context) {
    SNode currentChild = context.getCurrentTargetNode();
    final SNode parentNode = context.getParentNode();

    int index = getIndex(currentChild, parentNode);
    return ModelConstraints.getSmartReferenceDescriptor(parentNode,
        context.getLink(), index, myConcept, context.getEditorContext().getRepository());
  }

  @NotNull
  private ReferenceDescriptor createSmartReferenceDescriptorByNode(SubstituteMenuContext context, SNode conceptNode) {
    SNode currentChild = context.getCurrentTargetNode();
    final SNode parentNode = context.getParentNode();

    int index = getIndex(currentChild, parentNode);

    return ModelConstraints.getSmartReferenceDescriptor(parentNode,
        context.getLink() != null ? context.getLink().getName() : null, index, conceptNode);
  }

  private int getIndex(@Nullable SNode currentChild, @NotNull SNode parentNode) {
    int index = 0;
    if (currentChild != null) {
      final SContainmentLink containmentLink = currentChild.getContainmentLink();
      index = IterableUtil.indexOf(parentNode.getChildren(containmentLink), currentChild);
    }
    return index;
  }

  private boolean checkForSubconcept(SAbstractConcept concept, SNode referentNode) {
    return referentNode.getConcept().isSubConceptOf(concept);
  }

}
