package org.aikodi.chameleon.core.reference;

import java.lang.ref.SoftReference;
import java.util.concurrent.atomic.AtomicReference;

import org.aikodi.chameleon.core.Config;
import org.aikodi.chameleon.core.declaration.Declaration;
import org.aikodi.chameleon.core.element.ElementImpl;
import org.aikodi.chameleon.core.lookup.DeclarationCollector;
import org.aikodi.chameleon.core.lookup.DeclarationSelector;
import org.aikodi.chameleon.core.lookup.LookupContext;
import org.aikodi.chameleon.core.lookup.LookupException;
import org.aikodi.chameleon.core.validation.Valid;
import org.aikodi.chameleon.core.validation.Verification;
import org.aikodi.chameleon.exception.ChameleonProgrammerException;

/**
 * An class with default implementations for cross-references.
 * 
 * @author Marko van Dooren
 *
 * @param <D> The type of the declaration that is referenced by the cross-reference.
 */
public abstract class CrossReferenceImpl<D extends Declaration> extends ElementImpl implements CrossReference<D> {

  /**
   * The type of an expression is cached to increase performance. Call {@link #flushCache()} to
   * flush the cache of the model has changed.
   * 
   * The reference is stored in a soft reference to allow garbage collection of cached types.
   * 
   * The soft reference is stored in an atomic reference to deal with concurrent lookups of the
   * type of this expression without needing a lock.
   */
  private final AtomicReference<SoftReference<D>> _cache = new AtomicReference<>();

  protected D getCache() {
    SoftReference<D> cache = _cache.get();
    D result = (cache == null ? null: cache.get());
    return result;
  }

  @Override
  public void flushLocalCache() {
    super.flushLocalCache();
    boolean success = false;
    do {
      success = _cache.compareAndSet(_cache.get(), null);
    } while (!success);

  }

  protected void setCache(D value) {
    if (Config.cacheElementReferences() == true) {
      // We only try to set once. Concurrent lookups of this name
      // should result in the same element.
      _cache.compareAndSet(null, new SoftReference<D>(value));
    }
  }


  /**
   * Return the declaration selector that is responsible for selecting the
   * declaration referenced by this cross-reference.
   * 
   * @return
   */
  public abstract DeclarationSelector<D> selector();

  @Override
  public D getElement() throws LookupException {
    return getElement(selector());
  }

  @Override
  public Declaration getDeclarator() throws LookupException {
    //      return getElement(new DeclaratorSelector(selector()));
    //FIXME Now that Declaration has a declarator() method, do we still
    //need the separate getElement(DeclaratorSelector) method? 
    return getElement().declarator();
  }

  protected <X extends Declaration> X getElement(DeclarationSelector<X> selector) throws LookupException {
    X result = null;

    // OPTIMISATION
    boolean cache = selector.equals(selector());
    if (cache) {
      result = (X) getCache();
    }
    if (result != null) {
      return result;
    }
    synchronized (this) {
      DeclarationCollector<X> collector = new DeclarationCollector<X>(selector);
      lookupContext().lookUp(collector);
      result = collector.result();
      if (cache) {
        setCache((D) result);
      }
      return result;
    }
  }

  protected LookupContext lookupContext() throws LookupException {
    return lexicalContext();
  }

  @Override
  public Verification verifySelf() {
    try {
      return getElement() != null ? Valid.create() : new UnresolvableCrossReference(this);
    } catch (LookupException e) {
      return new UnresolvableCrossReference(this, e.getMessage());
    } catch (ChameleonProgrammerException e) {
      return new UnresolvableCrossReference(this, e.getMessage());
    }
  }

}

@@ -2398,38 +2398,329 @@
     
-return getElement(selector());
+D result = (D) getCache();%0A    if (result != null) %7B%0A      return result;%0A    %7D%0A    synchronized (this) %7B%0A      DeclarationCollector%3CD%3E collector = new DeclarationCollector%3CD%3E(selector());%0A      lookupContext().lookUp(collector);%0A      result = collector.result();%0A        setCache((D) result);%0A      return result;%0A    %7D
 %0A  %7D
@@ -3037,24 +3037,26 @@
 tor();%0A  %7D%0A%0A
+//
   protected 
@@ -3146,24 +3146,26 @@
 Exception %7B%0A
+//
     X result
@@ -3173,17 +3173,21 @@
 = null;%0A
-%0A
+//%0A//
     // O
@@ -3198,16 +3198,18 @@
 ISATION%0A
+//
     bool
@@ -3245,24 +3245,26 @@
 elector());%0A
+//
     if (cach
@@ -3264,24 +3264,26 @@
 f (cache) %7B%0A
+//
       result
@@ -3301,22 +3301,26 @@
 ache();%0A
+//
     %7D%0A
+//
     if (
@@ -3329,32 +3329,34 @@
 sult != null) %7B%0A
+//
       return res
@@ -3352,38 +3352,42 @@
  return result;%0A
+//
     %7D%0A
+//
     synchronized
@@ -3388,32 +3388,34 @@
 onized (this) %7B%0A
+//
       Declaratio
@@ -3475,24 +3475,26 @@
 (selector);%0A
+//
       lookup
@@ -3514,32 +3514,34 @@
 kUp(collector);%0A
+//
       result = c
@@ -3555,24 +3555,26 @@
 r.result();%0A
+//
       if (ca
@@ -3576,24 +3576,26 @@
 f (cache) %7B%0A
+//
         setC
@@ -3616,16 +3616,18 @@
 t);%0A
+//
       %7D%0A
     
@@ -3618,24 +3618,26 @@
 ;%0A//      %7D%0A
+//
       return
@@ -3641,30 +3641,34 @@
 urn result;%0A
+//
     %7D%0A
+//
   %7D%0A%0A  prote

remove getElement(DeclarationSelector)