/*
 * 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.idea.java.psi.impl;

import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiManager;
import com.intellij.psi.PsiReference;
import com.intellij.util.IncorrectOperationException;
import jetbrains.mps.idea.core.psi.impl.MPSPsiNode;
import jetbrains.mps.idea.core.psi.impl.MPSPsiRef;
import jetbrains.mps.idea.java.refactoring.MoveRenameBatch;
import jetbrains.mps.smodel.ModelImports;
import jetbrains.mps.smodel.SNodePointer;
import jetbrains.mps.smodel.StaticReference;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.mps.openapi.model.SModel;
import org.jetbrains.mps.openapi.model.SModelReference;
import org.jetbrains.mps.openapi.model.SNode;
import org.jetbrains.mps.openapi.model.SNodeId;
import org.jetbrains.mps.openapi.model.SNodeReference;
import org.jetbrains.mps.openapi.model.SReference;

import jetbrains.mps.idea.java.psiStubs.JavaForeignIdBuilder;

/**
 * Reference to java entity (class, method, field)
 * danilla 4/29/13
 */

public abstract class MPSPsiJavaRef extends MPSPsiRef {
  public MPSPsiJavaRef(String role, SModelReference model, SNodeId nodeId, PsiManager manager) {
    super(role, model, nodeId, manager);
  }

  public MPSPsiJavaRef(String role, String referenceText, PsiManager manager) {
    super(role, referenceText, manager);
  }

  @Override
  public abstract PsiReference getReference();

  // get source node of this reference
  protected SNodeReference getSource() {
    PsiElement parent = getParent();
    // this includes parent != null
    assert parent instanceof MPSPsiNode;
    return ((MPSPsiNode) parent).getSNodeReference();
  }

  protected SReference getSReference() {
    SNodeReference source = getSource();
    SNode sourceNode = source.resolve(getProjectRepository());
    return sourceNode.getReference(getRole());
  }

  protected void scheduleRefUpdate(Runnable updater) {
    PsiElement parent = getParent();
    assert parent instanceof MPSPsiNode;
    SNodeReference source = ((MPSPsiNode) parent).getSNodeReference();
//    SNodeReference target = new SNodePointer(getModelReference(), getNodeId());
    getProject().getComponent(MoveRenameBatch.class).scheduleNormalRefUpdate(source, getRole(), updater);
  }

  // supposed to be extended in subclasses of MPSPsiJavaRef
  abstract class MPSPsiJavaReference extends MPSPsiReference {

    @Override
    public PsiElement bindToElement(@NotNull final PsiElement element) throws IncorrectOperationException {
      scheduleRefUpdate(new Runnable() {
        @Override
        public void run() {
          SNodeReference source = getSource();
          SNode sourceNode = source.resolve(getProjectRepository());
          SNodePointer oldNode = new SNodePointer(getModelReference(), getNodeId());
          SNodePointer newNode = JavaForeignIdBuilder.computeNodePtr(element).toSNodeReference();
          SReference newRef = StaticReference.create(getRole(), sourceNode, newNode.getModelReference(), newNode.getNodeId());
          sourceNode.setReference(getRole(), newRef);

          // add model import if needed
          if (!oldNode.getModelReference().equals(newNode.getModelReference())) {
            SModel model = sourceNode.getModel();
            SModelReference newTargetModel = newNode.getModelReference();

            ModelImports modelImports = new ModelImports(model);
            modelImports.addModelImport(newTargetModel);

            if (oldNode.getModelReference().resolve(getProjectRepository()) == null) {
              modelImports.removeModelImport(oldNode.getModelReference());
            }
          }
        }
      });
      return MPSPsiJavaRef.this;
    }
  }
}
