/*
 * Copyright 2003-2013 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.core.psi.impl.events;

import jetbrains.mps.smodel.ModelAccess;
import jetbrains.mps.util.Computable;
import org.jetbrains.mps.openapi.model.SNode;
import org.jetbrains.mps.openapi.model.SNodeId;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;

/**
 * User: fyodor
 * Date: 2/18/13
 */
public final class NodePath {

  private List<SNodeId> myPath;

  public static final NodePath EMPTY = new NodePath(Collections.<SNodeId>emptyList());

  public static NodePath forSNode (final SNode sNode) {
    List<SNodeId> path = ModelAccess.instance().runReadAction(new Computable<List<SNodeId>>() {
      @Override
      public List<SNodeId> compute() {
        LinkedList<SNodeId> path = new LinkedList<SNodeId>();

        for (SNode sn = sNode; sn != null; sn = sn.getParent()) {
          SNodeId nodeId = sn.getNodeId();
          if (nodeId == null) return null;

          path.addFirst(nodeId);
        }
        return path;
      }
    });

    if (path == null) {
      throw new IllegalArgumentException("null SNodeId");
    }

    return new NodePath (path);
  }

  public NodePath (Collection<SNodeId> ids) {
    myPath = new ArrayList<SNodeId>(ids);
  }

  boolean isEmpty () {
    return myPath.isEmpty();
  }

  public SNodeId root() {
    if (myPath.isEmpty()) throw new IllegalStateException("empty NodePath");
    return myPath.get(0);
  }

  public Iterable<SNodeId> path () {
    return myPath;
  }

  public SNodeId leaf() {
    if (myPath.isEmpty()) throw new IllegalStateException("empty NodePath");
    return myPath.get(myPath.size()-1);
  }

  public NodePath findLowestCommonParent (NodePath that) {
    if (isEmpty()) return this;
    if (that.isEmpty()) return that;

    ListIterator<SNodeId> thisIt = this.myPath.listIterator();
    ListIterator<SNodeId> thatIt = that.myPath.listIterator();

    LinkedList<SNodeId> result = null;
    while (thisIt.hasNext() && thatIt.hasNext()) {
      SNodeId thisId = thisIt.next();
      SNodeId thatId = thatIt.next();
      if (!thisId.equals(thatId)) {
        break;
      }
      if (result == null) {
        result = new LinkedList<SNodeId>();
      }
      result.addLast(thisId);
    }

    return result != null ? new NodePath(result) : null;
  }

  @Override
  public boolean equals(Object that) {
    if (that == null) return false;
    if (that.getClass() != NodePath.class) return false;
    return this.myPath == ((NodePath)that).myPath ||
          (this.myPath != null && this.myPath.equals(((NodePath)that).myPath));
  }

  @Override
  public int hashCode() {
    return myPath != null ? myPath.hashCode() * 37 : 137;
  }
}
