/*
 * 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.
 */
package org.jetbrains.jps.backwardRefs;

import com.intellij.openapi.util.Factory;
import com.intellij.util.SmartList;
import com.intellij.util.containers.ContainerUtil;
import gnu.trove.THashMap;
import gnu.trove.TObjectIntHashMap;
import org.jetbrains.jps.backwardRefs.index.CompiledFileData;
import org.jetbrains.jps.javac.ast.api.JavacDef;
import org.jetbrains.jps.javac.ast.api.JavacRef;

import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class BackwardReferenceIndexUtil {
  static void registerFile(String filePath,
                           TObjectIntHashMap<? extends JavacRef> refs,
                           List<JavacDef> defs,
                           final BackwardReferenceIndexWriter writer) {
    final int fileId = writer.enumeratePath(filePath);
    int funExprId = 0;

    final Map<LightRef, Void> definitions = new HashMap<>(defs.size());
    final Map<LightRef, Collection<LightRef>> backwardHierarchyMap = new HashMap<>();

    final AnonymousClassEnumerator anonymousClassEnumerator = new AnonymousClassEnumerator();

    for (JavacDef def : defs) {
      if (def instanceof JavacDef.JavacClassDef) {
        JavacRef.JavacClass sym = (JavacRef.JavacClass)def.getDefinedElement();

        final LightRef.LightClassHierarchyElementDef aClass;
        if (sym.isAnonymous()) {
          final JavacRef[] classes = ((JavacDef.JavacClassDef)def).getSuperClasses();
          aClass = anonymousClassEnumerator.addAnonymous(sym.getName(), writer.asClassUsage(classes[0]));
        } else {
          aClass = writer.asClassUsage(sym);
        }
        definitions.put(aClass, null);

        final JavacRef[] superClasses = ((JavacDef.JavacClassDef)def).getSuperClasses();
        for (JavacRef superClass : superClasses) {
          LightRef.JavaLightClassRef superClassRef = writer.asClassUsage(superClass);

          backwardHierarchyMap.computeIfAbsent(superClassRef, k -> new SmartList<>()).add(aClass);
        }
      }
      else if (def instanceof JavacDef.JavacFunExprDef) {
        final LightRef.JavaLightClassRef functionalType = writer.asClassUsage(def.getDefinedElement());
        int id = funExprId++;
        LightRef.JavaLightFunExprDef result = new LightRef.JavaLightFunExprDef(id);
        definitions.put(result, null);

        ContainerUtil.getOrCreate(backwardHierarchyMap, functionalType,
                                  (Factory<Collection<LightRef>>)() -> new SmartList<>()).add(result);
      }
    }

    Map<LightRef, Integer> convertedRefs = new THashMap<>();
    refs.forEachEntry((ref, count) -> {
      final LightRef lightRef = writer.enumerateNames(ref, name -> {
        final LightRef.LightClassHierarchyElementDef ref1 = anonymousClassEnumerator.getLightRefIfAnonymous(name);
        return ref1 == null ? null : ref1.getName();
      });
      if (lightRef != null) {
        convertedRefs.put(lightRef, count);
      }
      return true;
    });
    writer.writeData(fileId, new CompiledFileData(backwardHierarchyMap, convertedRefs, definitions));
  }

  private static class AnonymousClassEnumerator {
    private THashMap<String, LightRef.LightClassHierarchyElementDef> myAnonymousName2Id = null;
    private int myFreeId = 0;

    private LightRef.JavaLightAnonymousClassRef addAnonymous(String internalName,
                                                             LightRef.JavaLightClassRef base) {
      if (myAnonymousName2Id == null) {
        myAnonymousName2Id = new THashMap<>();
      }
      myAnonymousName2Id.put(internalName, base);
      return new LightRef.JavaLightAnonymousClassRef(myFreeId++);
    }

    private LightRef.LightClassHierarchyElementDef getLightRefIfAnonymous(String className) {
      return myAnonymousName2Id == null ? null : myAnonymousName2Id.get(className);
    }
  }
}

@@ -3824,38 +3824,8 @@
 ull;
-%0A    private int myFreeId = 0;
 %0A%0A  
@@ -4091,24 +4091,82 @@
 ();%0A      %7D%0A
+      final int anonymousIdx = myAnonymousName2Id.size();%0A
       myAnon
@@ -4260,18 +4260,20 @@
 Ref(
-myFreeId++
+anonymousIdx
 );%0A 

remove redundant field