/*
 * Copyright 2003-2011 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.typesystem.inference.util;

import jetbrains.mps.lang.pattern.util.IMatchModifier;
import jetbrains.mps.lang.pattern.util.MatchingUtil;
import org.jetbrains.mps.openapi.model.SNode;

import java.util.*;

public class StructuralNodeSet<T> implements Set<SNode> {
  private Set<SNodeWrapper> myWrappers = new LinkedHashSet<SNodeWrapper>();

  public StructuralNodeSet() {
  }

  public StructuralNodeSet(StructuralNodeSet<T> set) {
    myWrappers.addAll(set.myWrappers);
  }

  public StructuralNodeSet(Collection<? extends SNode> collection) {
    addAll(collection);
  }

  public boolean containsStructurally(SNode ourNode) {
    return myWrappers.contains(new SNodeWrapper(ourNode));
  }

  public boolean removeStructurally(SNode ourNode) {
    return myWrappers.remove(new SNodeWrapper(ourNode));
  }

  public boolean addStructurally(SNode ourNode) {
    return myWrappers.add(new SNodeWrapper(ourNode));
  }

  public boolean addCollectionStructurally(Collection<? extends SNode> ourNodes) {
    boolean result = false;
    if (ourNodes == null) return false;
    for (SNode ourNode : ourNodes) {
      boolean someResult = addStructurally(ourNode);
      result = result || someResult;
    }
    return result;
  }

  public boolean addAllStructurally(StructuralNodeSet ourNodes) {
    return myWrappers.addAll(ourNodes.myWrappers);
  }

  public void putAllStructurally(StructuralNodeSet<T> ourNodes) {
    for (SNodeWrapper w : ourNodes.myWrappers) {
      if (!myWrappers.contains(w)) {
        myWrappers.add(w);
      }
    }
  }

  @Override
  public int size() {
    return myWrappers.size();
  }

  @Override
  public boolean isEmpty() {
    return myWrappers.isEmpty();
  }

  @Override
  public void clear() {
    myWrappers.clear();
  }

  @Override
  public boolean contains(Object o) {
    if (!(o instanceof SNode)) return false;
    return containsStructurally((SNode) o);
  }

  @Override
  public Iterator<SNode> iterator() {
    return getNodes().iterator();
  }

  private List<SNode> getNodes() {
    List<SNode> nodes = new ArrayList<SNode>();
    for (SNodeWrapper w : myWrappers) {
      nodes.add(w.myNode);
    }
    return nodes;
  }

  @Override
  public boolean add(SNode o) {
    return addStructurally(o);
  }

  @Override
  public boolean addAll(Collection<? extends SNode> c) {
    return addCollectionStructurally(c);
  }


  @Override
  public boolean remove(Object o) {
    if (!(o instanceof SNode)) return false;
    return removeStructurally((SNode) o);
  }


  @Override
  public Object[] toArray() {
    return getNodes().toArray();
  }

  @Override
  public <T> T[] toArray(T[] a) {
    return getNodes().toArray(a);
  }

  @Override
  public boolean containsAll(Collection<?> c) {
    throw new UnsupportedOperationException();
  }

  @Override
  public boolean retainAll(Collection<?> c) {
    throw new UnsupportedOperationException();
  }

  @Override
  public boolean removeAll(Collection<?> c) {
    throw new UnsupportedOperationException();
  }

  public void addAndRewrite(SNode node) {
    if (!containsStructurally(node)) {
      myWrappers.add(new SNodeWrapper(node));
    } else {
      SNodeWrapper wrapper = new SNodeWrapper(node);
      myWrappers.remove(wrapper);
      myWrappers.add(wrapper);
    }
  }

  private static class SNodeWrapper {
    private SNode myNode;
    private int myHashCode;


    private SNodeWrapper(SNode node) {
      myNode = node;
      myHashCode = MatchingUtil.hash(myNode);
    }

    @Override
    public int hashCode() {
      return myHashCode;
    }

    @Override
    public boolean equals(Object obj) {
      if (!(obj instanceof SNodeWrapper)) return false;
      SNodeWrapper wrapper = (SNodeWrapper) obj;
      return MatchingUtil.matchNodes(wrapper.myNode, myNode, IMatchModifier.DEFAULT, false);
    }

  }
}
