/*
 * 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 com.intellij.util.io;

import com.intellij.openapi.progress.EmptyProgressIndicator;
import com.intellij.openapi.project.CacheUpdateRunner;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.vfs.CharsetToolkit;
import com.intellij.openapi.vfs.LocalFileSystem;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.testFramework.PlatformTestCase;
import com.intellij.testFramework.fixtures.LightPlatformCodeInsightFixtureTestCase;
import com.intellij.util.TimeoutUtil;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.indexing.FileBasedIndex;
import com.intellij.util.indexing.FileBasedIndexImpl;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.ide.PooledThreadExecutor;

import java.io.DataInput;
import java.io.DataOutput;
import java.io.File;
import java.io.IOException;
import java.util.*;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;

/**
 * @author Dmitry Avdeev
 */
public class PersistenceStressTest extends LightPlatformCodeInsightFixtureTestCase {
  private final ExecutorService myThreadPool = PooledThreadExecutor.INSTANCE;
  private final List<PersistentHashMap<String, Record>> myMaps = new ArrayList<>();
  private final List<String> myKeys = new ArrayList<>();
  private PersistentStringEnumerator myEnumerator;

  public static class Record {
    final int magnitude;
    public final Date date;

    Record(int magnitude, Date date) {
      this.magnitude = magnitude;
      this.date = date;
    }
  }

  @Override
  protected void setUp() throws Exception {
    super.setUp();
    Random random = new Random();
    for (int i = 0; i < 100; i++) {
      byte[] bytes = new byte[1000];
      random.nextBytes(bytes);
      String key = new String(bytes, CharsetToolkit.UTF8_CHARSET);
      myKeys.add(key);
    }
    File tempDirectory = FileUtil.createTempDirectory("map", "");

    for (int i = 0; i < 10; i++) {
      PersistentHashMap<String, Record> map = createMap(FileUtil.createTempFile(tempDirectory, "persistent", "map" + i));
      myMaps.add(map);
    }
    PagedFileStorage.StorageLockContext storageLockContext = new PagedFileStorage.StorageLockContext(false);
    myEnumerator = new PersistentStringEnumerator(FileUtil.createTempFile(tempDirectory, "persistent", "enum"), storageLockContext);
  }

  @Override
  public void tearDown() throws Exception {
    for (PersistentHashMap<String, Record> map : myMaps) {
      map.close();
    }
    myEnumerator.close();
    myEnumerator = null;
    myMaps.clear();
    super.tearDown();
  }

  public void testReadWrite() throws Exception {
    List<Future<Boolean>> futures = Collections.synchronizedList(new ArrayList<>());
    for (PersistentHashMap<String, Record> map : myMaps) {
      Future<Boolean> submit = myThreadPool.submit(() -> doTask(map));
      futures.add(submit);
    }
    Future<?> waitFuture = myThreadPool.submit(() -> {
      while (ContainerUtil.exists(futures, future -> !future.isDone())) {
        TimeoutUtil.sleep(100);
        myMaps.forEach(PersistentHashMap::dropMemoryCaches);
      }
    });

    List<VirtualFile> files = new ArrayList<>();
    for (int i = 0; i < 100; i++) {
      File file = FileUtil.createTempFile("", ".txt");
      VirtualFile virtualFile = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(file);
      assertNotNull(virtualFile);
      PlatformTestCase.setFileText(virtualFile, "foo bar");
      files.add(virtualFile);
    }

    FileBasedIndexImpl index = (FileBasedIndexImpl)FileBasedIndex.getInstance();
    while (ContainerUtil.exists(futures, future -> !future.isDone())) {
      Thread.sleep(100);
      CacheUpdateRunner.processFiles(new EmptyProgressIndicator(), files, getProject(),
                                     content -> index.indexFileContent(getProject(), content));
    }
    for (Future<Boolean> future : futures) {
      assertTrue(future.get());
    }
    waitFuture.get();
  }

  private boolean doTask(PersistentHashMap<String, Record> map) {
    Random random = new Random();
    try {
      for (int i = 0; i < 100000; i++) {
        if (random.nextInt(1000) == 1) {
          map.force();
          continue;
        }
        String key = myKeys.get(random.nextInt(myKeys.size()));
        myEnumerator.enumerate(key);
        if (random.nextBoolean()) {
  //        for (int j = 0; j < 10; j++) {
            map.put(key, new Record(random.nextInt(), new Date()));
  //        }
        }
        else {
  //        for (int j = 0; j < 10; j++) {
            map.get(key);
  //        }
        }
      }
//      System.out.println("Done!");
      return true;
    }
    catch (Throwable e) {
      e.printStackTrace();
      return false;
    }
  }

  @NotNull
  private static PersistentHashMap<String, Record> createMap(File file) throws IOException {
    return new PersistentHashMap<>(file, new EnumeratorStringDescriptor(), new DataExternalizer<Record>() {
      @Override
      public void save(@NotNull DataOutput out, Record value) throws IOException {
        out.writeInt(value.magnitude);
        out.writeLong(value.date.getTime());
      }

      @Override
      public Record read(@NotNull DataInput in) throws IOException {
        return new Record(in.readInt(), new Date(in.readLong()));
      }
    });
  }
}

@@ -2995,24 +2995,36 @@
 Exception %7B%0A
+    try %7B%0A  
     for (Per
@@ -3072,24 +3072,26 @@
 ps) %7B%0A      
+ 
+ 
 map.close();
@@ -3091,26 +3091,30 @@
 lose();%0A    
-%7D%0A
+  %7D%0A  
     myEnumer
@@ -3127,24 +3127,26 @@
 lose();%0A    
+ 
+ 
 myEnumerator
@@ -3154,24 +3154,26 @@
 = null;%0A    
+  
 myMaps.clear
@@ -3172,24 +3172,46 @@
 ps.clear();%0A
+    %7D%0A    finally %7B%0A  
     super.te
@@ -3216,24 +3216,30 @@
 tearDown();%0A
+    %7D%0A
   %7D%0A%0A  publi

call super tear down in finally