/*
 * Copyright 2000-2009 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.
 */

/*
 * @author max
 */
package com.intellij.ui;

import com.intellij.openapi.project.Project;
import com.intellij.openapi.project.impl.ProjectLifecycleListener;
import com.intellij.openapi.util.LowMemoryWatcher;
import com.intellij.psi.util.PsiModificationTracker;
import com.intellij.util.Function;
import com.intellij.util.messages.MessageBus;
import com.intellij.util.messages.MessageBusConnection;
import org.jetbrains.annotations.NotNull;

import javax.swing.*;
import java.util.LinkedHashMap;
import java.util.Map;

public class IconDeferrerImpl extends IconDeferrer {
  private final Object LOCK = new Object();
  private final Map<Object, Icon> myIconsCache = new LinkedHashMap<Object, Icon>() {
    @Override
    protected boolean removeEldestEntry(Map.Entry<Object, Icon> eldest) {
      return size() > 100;
    }
  };
  private long myLastClearTimestamp = 0;
  @SuppressWarnings("UnusedDeclaration")
  private final LowMemoryWatcher myLowMemoryWatcher = LowMemoryWatcher.register(new Runnable() {
    @Override
    public void run() {
      clear();
    }
  });

  public IconDeferrerImpl(MessageBus bus) {
    final MessageBusConnection connection = bus.connect();
    connection.subscribe(PsiModificationTracker.TOPIC, new PsiModificationTracker.Listener() {
      @Override
      public void modificationCountChanged() {
        clear();
      }
    });
    connection.subscribe(ProjectLifecycleListener.TOPIC, new ProjectLifecycleListener.Adapter() {
      @Override
      public void afterProjectClosed(@NotNull Project project) {
        clear();
      }
    });
  }

  private void clear() {
    synchronized (LOCK) {
      myIconsCache.clear();
      myLastClearTimestamp++;
    }
  }

  @Override
  public <T> Icon defer(final Icon base, final T param, @NotNull final Function<T, Icon> f) {
    return deferImpl(base, param, f, false);
  }

  @Override
  public <T> Icon deferAutoUpdatable(Icon base, T param, @NotNull Function<T, Icon> f) {
    return deferImpl(base, param, f, true);
  }

  private <T> Icon deferImpl(Icon base, T param, @NotNull Function<T, Icon> f, boolean autoupdatable) {
    if (myEvaluationIsInProgress.get().booleanValue()) {
      return f.fun(param);
    }

    synchronized (LOCK) {
      Icon result = myIconsCache.get(param);
      if (result == null) {
        final long started = myLastClearTimestamp;
        result = new DeferredIconImpl<T>(base, param, f, new DeferredIconImpl.IconListener<T>() {
          @Override
          public void evalDone(T key, @NotNull Icon r) {
            synchronized (LOCK) {
              // check if our results is not outdated yet
              if (started == myLastClearTimestamp) {
                myIconsCache.put(key, r);
              }
            }
          }
        }, autoupdatable);
        myIconsCache.put(param, result);
      }

      return result;
    }
  }

  private static final ThreadLocal<Boolean> myEvaluationIsInProgress = new ThreadLocal<Boolean>() {
    @Override
    protected Boolean initialValue() {
      return Boolean.FALSE;
    }
  };

  static void evaluateDeferred(@NotNull Runnable runnable) {
    try {
      myEvaluationIsInProgress.set(Boolean.TRUE);
      runnable.run();
    }
    finally {
      myEvaluationIsInProgress.set(Boolean.FALSE);
    }
  }
}

@@ -1709,20 +1709,58 @@
 eBus bus
+, final PsiModificationTracker tracker
 ) %7B%0A
-
     fina
@@ -1901,24 +1901,77 @@
 istener() %7B%0A
+      private long myLastOutOfCodeModificationCount;%0A
       @Overr
@@ -2017,24 +2017,212 @@
 Changed() %7B%0A
+        if(tracker.getOutOfCodeBlockModificationCount() != myLastOutOfCodeModificationCount) %7B%0A          myLastOutOfCodeModificationCount = tracker.getOutOfCodeBlockModificationCount();%0A  
         clea
@@ -2218,32 +2218,42 @@
        clear();%0A
+        %7D%0A
       %7D%0A    %7D);%0A

handle only OutOfCodeModificationCount