/*
 * 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 gnu.trove.THashMap;
import gnu.trove.THashSet;
import jetbrains.mps.lang.pattern.util.MatchingUtil;
import org.jetbrains.mps.openapi.model.SNode;

import java.util.Collection;
import java.util.Map;
import java.util.Set;

public class StructuralNodeMap<T> implements Map<SNode, T> {
  private Map<SNode, SNode> myRepresentatorsMap = new THashMap<SNode, SNode>();
  private Map<SNode, T> myMap = new THashMap<SNode, T>();
  private Set<SNode> myAbsentNodes = new THashSet<SNode>();

  public SNode getKeyRepresentator(SNode node) {
    return myRepresentatorsMap.get(node);
  }

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

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

  @Override
  public boolean containsValue(Object value) {
    return myMap.containsValue(value);
  }

  @Override
  public void putAll(Map<? extends SNode, ? extends T> t) {
    throw new UnsupportedOperationException();
  }

  @Override
  public Set<SNode> keySet() {
    return myMap.keySet();
  }

  @Override
  public void clear() {
    myRepresentatorsMap.clear();
    myMap.clear();
    myAbsentNodes.clear();
  }

  @Override
  public Collection<T> values() {
    return myMap.values();
  }

  @Override
  public Set<Entry<SNode, T>> entrySet() {
    return myMap.entrySet();
  }

  @Override
  public T get(Object key) {
    if (!(key instanceof SNode)) return null;
    SNode keyNode = (SNode) key;
    SNode representator = getKeyRepresentator(keyNode);
    if (representator == null) {
      if (myAbsentNodes.contains(keyNode)) return null;
      for (SNode node : myMap.keySet()) {
        if (MatchingUtil.matchNodes(node, keyNode)) {
          myRepresentatorsMap.put(keyNode, node);
          return myMap.get(node);
        }
      }
    }
    if (representator == null) {
      myAbsentNodes.add(keyNode);
      return null;
    }
    return myMap.get(representator);
  }

  @Override
  public T put(SNode keyNode, T value) {
    SNode representator = getKeyRepresentator(keyNode);
    if (representator == null) {
      for (SNode node : myMap.keySet()) {
        if (MatchingUtil.matchNodes(node, keyNode)) {
          myRepresentatorsMap.put(keyNode, node);
          return myMap.put(node, value);
        }
      }
    }
    if (representator == null) {
      myRepresentatorsMap.put(keyNode, keyNode);
      myAbsentNodes.remove(keyNode);
      representator = keyNode;
    }
    return myMap.put(representator, value);
  }

  @Override
  public T remove(Object key) {
    if (!(key instanceof SNode)) return null;
    SNode keyNode = (SNode) key;
    myAbsentNodes.add(keyNode);
    SNode representator = getKeyRepresentator(keyNode);
    if (representator == null) {
      for (SNode node : myMap.keySet()) {
        if (MatchingUtil.matchNodes(node, keyNode)) {
          myRepresentatorsMap.put(keyNode, node);
          return myMap.remove(node);
        }
      }
    }
    if (representator == null) return null;
    return myMap.remove(representator);
  }

  @Override
  public boolean containsKey(Object key) {
    if (!(key instanceof SNode)) return false;
    SNode keyNode = (SNode) key;
    SNode representator = getKeyRepresentator(keyNode);
    if (representator == null) {
      if (myAbsentNodes.contains(keyNode)) return false;
      for (SNode node : myMap.keySet()) {
        if (MatchingUtil.matchNodes(node, keyNode)) {
          myRepresentatorsMap.put(keyNode, node);
          return myMap.containsKey(node);
        }
      }
    }
    if (representator == null) {
      myAbsentNodes.add(keyNode);
      return false;
    }
    return myMap.containsKey(representator);
  }
}
