package com.intellij.psi.statistics.impl;

import com.intellij.CommonBundle;
import com.intellij.ide.IdeBundle;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.PathManager;
import com.intellij.openapi.ui.Messages;
import com.intellij.psi.statistics.StatisticsInfo;
import com.intellij.psi.statistics.StatisticsManager;
import com.intellij.reference.SoftReference;
import com.intellij.util.NotNullFunction;
import com.intellij.util.ScrambledInputStream;
import com.intellij.util.ScrambledOutputStream;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.TestOnly;

import java.io.*;
import java.util.Arrays;
import java.util.HashSet;

public class StatisticsManagerImpl extends StatisticsManager {
  private static final int UNIT_COUNT = 997;

  @NonNls private static final String STORE_PATH = PathManager.getSystemPath() + File.separator + "stat";

  private final SoftReference[] myUnits = new SoftReference[UNIT_COUNT];
  private final HashSet<StatisticsUnit> myModifiedUnits = new HashSet<StatisticsUnit>();

  public int getUseCount(@NotNull final StatisticsInfo info) {
    if (info == StatisticsInfo.EMPTY) return 0;

    String key1 = info.getContext();
    int unitNumber = getUnitNumber(key1);
    StatisticsUnit unit = getUnit(unitNumber);
    return unit.getData(key1, info.getValue());
  }

  public void incUseCount(@NotNull final StatisticsInfo info) {
    if (info == StatisticsInfo.EMPTY) return;

    final String key1 = info.getContext();
    int unitNumber = getUnitNumber(key1);
    StatisticsUnit unit = getUnit(unitNumber);
    unit.incData(key1, info.getValue());
    myModifiedUnits.add(unit);
    ApplicationManager.getApplication().assertIsDispatchThread();
  }

  public StatisticsInfo[] getAllValues(final String context) {
    final String[] strings = getUnit(getUnitNumber(context)).getKeys2(context);
    return ContainerUtil.map2Array(strings, StatisticsInfo.class, new NotNullFunction<String, StatisticsInfo>() {
      @NotNull
      public StatisticsInfo fun(final String s) {
        return new StatisticsInfo(context, s);
      }
    });
  }

  public void save() {
    if (!ApplicationManager.getApplication().isUnitTestMode()){
      ApplicationManager.getApplication().assertIsDispatchThread();
      for (StatisticsUnit unit : myModifiedUnits) {
        saveUnit(unit.getNumber());
      }
    }
    myModifiedUnits.clear();
  }

  private StatisticsUnit getUnit(int unitNumber) {
    SoftReference ref = myUnits[unitNumber];
    if (ref != null){
      StatisticsUnit unit = (StatisticsUnit)ref.get();
      if (unit != null) return unit;
    }
    StatisticsUnit unit = loadUnit(unitNumber);
    if (unit == null){
      unit = new StatisticsUnit(unitNumber);
    }
    myUnits[unitNumber] = new SoftReference<StatisticsUnit>(unit);
    return unit;
  }

  private static StatisticsUnit loadUnit(int unitNumber) {
    StatisticsUnit unit = new StatisticsUnit(unitNumber);
    if (!ApplicationManager.getApplication().isUnitTestMode()){
      String path = getPathToUnit(unitNumber);
      try{
        InputStream in = new BufferedInputStream(new FileInputStream(path));
        in = new ScrambledInputStream(in);
        try{
          unit.read(in);
        }
        finally{
          in.close();
        }
      }
      catch(IOException e){
      }
      catch(WrongFormatException e){
      }
    }
    return unit;
  }

  private void saveUnit(int unitNumber){
    if (!createStoreFolder()) return;
    StatisticsUnit unit = getUnit(unitNumber);
    String path = getPathToUnit(unitNumber);
    try{
      OutputStream out = new BufferedOutputStream(new FileOutputStream(path));
      out = new ScrambledOutputStream(out);
      try {
        unit.write(out);
      }
      finally{
        out.close();
      }
    }
    catch(IOException e){
      Messages.showMessageDialog(
        IdeBundle.message("error.saving.statistics", e.getLocalizedMessage()),
        CommonBundle.getErrorTitle(),
        Messages.getErrorIcon()
      );
    }
  }

  private static int getUnitNumber(String key1) {
    return Math.abs(key1.hashCode()) % UNIT_COUNT;
  }

  private static boolean createStoreFolder(){
    File homeFile = new File(STORE_PATH);
    if (!homeFile.exists()){
      if (!homeFile.mkdirs()){
        Messages.showMessageDialog(
          IdeBundle.message("error.saving.statistic.failed.to.create.folder", STORE_PATH),
          CommonBundle.getErrorTitle(),
          Messages.getErrorIcon()
        );
        return false;
      }
    }
    return true;
  }

  @SuppressWarnings({"HardCodedStringLiteral"})
  private static String getPathToUnit(int unitNumber) {
    return STORE_PATH + File.separator + "unit." + unitNumber;
  }

  @TestOnly
  public void clearStatistics() {
    Arrays.fill(myUnits, null);
  }
}
@@ -901,16 +901,67 @@
 T = 997;
+%0A  private static final Object LOCK = new Object();
 %0A%0A  @Non
@@ -1407,32 +1407,60 @@
 itNumber(key1);%0A
+    synchronized (LOCK) %7B%0A  
     StatisticsUn
@@ -1486,24 +1486,26 @@
 nitNumber);%0A
+  
     return u
@@ -1540,16 +1540,22 @@
 lue());%0A
+    %7D%0A
   %7D%0A%0A  p
@@ -1743,24 +1743,52 @@
 mber(key1);%0A
+    synchronized (LOCK) %7B%0A  
     Statisti
@@ -1822,24 +1822,26 @@
 umber);%0A    
+ 
+ 
 unit.incData
@@ -1865,24 +1865,26 @@
 lue());%0A    
+  
 myModifiedUn
@@ -1894,24 +1894,30 @@
 .add(unit);%0A
+    %7D%0A
     Applicat
@@ -2056,16 +2056,57 @@
 String%5B%5D
+ strings;%0A    synchronized (LOCK) %7B%0A     
  strings
@@ -2155,24 +2155,30 @@
 2(context);%0A
+    %7D%0A
     return C
@@ -2431,24 +2431,52 @@
 id save() %7B%0A
+    synchronized (LOCK) %7B%0A  
     if (!App
@@ -2529,24 +2529,26 @@
 e())%7B%0A      
+ 
+ 
 ApplicationM
@@ -2593,24 +2593,26 @@
 chThread();%0A
+  
       for (S
@@ -2659,16 +2659,18 @@
         
+  
 saveUnit
@@ -2691,32 +2691,38 @@
 r());%0A      
+ 
+ 
 %7D%0A
+  
     %7D%0A
+  
     myModifi
@@ -2734,24 +2734,30 @@
 ts.clear();%0A
+    %7D%0A
   %7D%0A%0A  priva
@@ -5114,24 +5114,52 @@
 tistics() %7B%0A
+    synchronized (LOCK) %7B%0A  
     Arrays.f
@@ -5178,13 +5178,19 @@
  null);%0A
+    %7D%0A
   %7D%0A%7D

synchronize statistics