/*
 * Copyright 2000-2016 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;

  protected IconDeferrerImpl() {}

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

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

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

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

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

    synchronized (LOCK) {
      Icon result = myIconsCache.get(param);
      if (result == null) {
        final long started = myLastClearTimestamp;
        result = new DeferredIconImpl<>(base, param, evaluator, (DeferredIconImpl<T> source, T key, Icon r) -> {
          synchronized (LOCK) {
            // check if our results is not outdated yet
            if (started == myLastClearTimestamp) {
              myIconsCache.put(key, autoUpdatable ? source : r);
            }
          }
        }, autoUpdatable);
        myIconsCache.put(param, result);
      }

      return result;
    }
  }

  protected void cacheIcon(Object key,Icon value) {
    synchronized (LOCK) {
      myIconsCache.put(key, value);
    }
  }

  private static final ThreadLocal<Boolean> myEvaluationIsInProgress = ThreadLocal.withInitial(() -> Boolean.FALSE);

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

  @Override
  public boolean equalIcons(Icon icon1, Icon icon2) {
    return DeferredIconImpl.equalIcons(icon1, icon2);
  }
}

@@ -802,24 +802,191 @@
 oryWatcher;%0A
+import com.intellij.openapi.vfs.VirtualFileManager;%0Aimport com.intellij.openapi.vfs.newvfs.BulkFileListener;%0Aimport com.intellij.openapi.vfs.newvfs.events.VFileEvent;%0A
 import com.i
@@ -1022,24 +1022,24 @@
 ionTracker;%0A
-
 import com.i
@@ -1260,16 +1260,39 @@
 ashMap;%0A
+import java.util.List;%0A
 import j
@@ -1786,24 +1786,24 @@
 .connect();%0A
-
     connecti
@@ -1855,24 +1855,253 @@
 is::clear);%0A
+    // update %22locked%22 icon%0A    connection.subscribe(VirtualFileManager.VFS_CHANGES, new BulkFileListener() %7B%0A      @Override%0A      public void after(@NotNull List%3C? extends VFileEvent%3E events) %7B%0A        clear();%0A      %7D%0A    %7D);%0A
     connecti

update "locked" icon modifier immediately when file readonliness changed