/*
 * Copyright 2000-2015 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.execution;

import com.intellij.openapi.Disposable;
import com.intellij.openapi.application.PathManager;
import com.intellij.openapi.components.ServiceManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.util.ThrowableComputable;
import com.intellij.openapi.util.io.FileUtilRt;
import com.intellij.openapi.vfs.newvfs.persistent.FlushingDaemon;
import com.intellij.util.io.*;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.io.DataInput;
import java.io.DataOutput;
import java.io.File;
import java.io.IOException;
import java.util.Date;
import java.util.concurrent.ScheduledFuture;

/**
 * @author Dmitry Avdeev
 */
public class TestStateStorage implements Disposable {

  private static final File TEST_HISTORY_PATH = new File(PathManager.getSystemPath(), "testHistory");
  public static File getTestHistoryRoot(Project project) {
    return new File(TEST_HISTORY_PATH, project.getLocationHash());
  }

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

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

  private static final Logger LOG = Logger.getInstance(TestStateStorage.class);
  @Nullable
  private PersistentHashMap<String, Record> myMap;
  private volatile ScheduledFuture<?> myMapFlusher;

  public static TestStateStorage getInstance(@NotNull Project project) {
    return ServiceManager.getService(project, TestStateStorage.class);
  }

  public TestStateStorage(Project project) {

    File file = new File(getTestHistoryRoot(project).getPath() + "/testStateMap");
    FileUtilRt.createParentDirs(file);
    try {
      myMap = create(file);
    } catch (IOException e) {
      LOG.error(e);
    }
    myMapFlusher = FlushingDaemon.everyFiveSeconds(new Runnable() {
      @Override
      public void run() {
        flushMap();
      }
    });

    Disposer.register(project, this);
  }

  private synchronized void flushMap() {
    if (myMapFlusher == null) return; // disposed
    if (myMap != null && myMap.isDirty()) myMap.force();
  }

  @NotNull
  private static PersistentHashMap<String, Record> create(final File file) throws IOException {
    return IOUtil.openCleanOrResetBroken(new ThrowableComputable<PersistentHashMap<String, Record>, IOException>() {
      @Override
      public PersistentHashMap<String, Record> compute() throws IOException {
        return new PersistentHashMap<String, Record>(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()));
          }
        });
      }
    }, file);
  }

  @Nullable
  public synchronized Record getState(String testUrl) {
    try {
      return myMap == null ? null : myMap.get(testUrl);
    }
    catch (IOException e) {
      LOG.error(e);
      return null;
    }
  }

  public synchronized void writeState(@NotNull String testUrl, Record record) {
    if (myMap == null) return;
    try {
      myMap.put(testUrl, record);
    }
    catch (IOException e) {
      LOG.error(e);
    }
  }

  @Override
  public synchronized void dispose() {
    myMapFlusher.cancel(false);
    myMapFlusher = null;
    if (myMap == null) return;
    try {
      myMap.close();
    }
    catch (IOException e) {
      LOG.error(e);
    }
  }
}

@@ -1106,17 +1106,171 @@
 util.io.
-*
+DataExternalizer;%0Aimport com.intellij.util.io.EnumeratorStringDescriptor;%0Aimport com.intellij.util.io.IOUtil;%0Aimport com.intellij.util.io.PersistentHashMap
 ;%0Aimport
@@ -1710,16 +1710,46 @@
 tory%22);%0A
+  private final File myFile;%0A%0A
   public
@@ -2473,22 +2473,19 @@
  %7B%0A%0A    
-File f
+myF
 ile = ne
@@ -2581,17 +2581,19 @@
 entDirs(
-f
+myF
 ile);%0A  
@@ -2618,19 +2618,22 @@
 p = 
-create(file
+initializeMap(
 );%0A 
@@ -2873,24 +2873,185 @@
 this);%0A  %7D%0A%0A
+  protected PersistentHashMap%3CString, Record%3E initializeMap() throws IOException %7B%0A    return IOUtil.openCleanOrResetBroken(getComputable(myFile), myFile);%0A  %7D%0A%0A
   private sy
@@ -3219,16 +3219,36 @@
  static 
+ThrowableComputable%3C
 Persiste
@@ -3276,95 +3276,67 @@
 ord%3E
- create(final File file) throws IOException %7B%0A    return IOUtil.openCleanOrResetBroken(
+, IOException%3E getComputable(final File file) %7B%0A    return 
 new 
@@ -4043,15 +4043,8 @@
    %7D
-, file)
 ;%0A  
@@ -4216,33 +4216,55 @@
  e) %7B%0A      
-LOG.error
+thingsWentWrongLetsReinitialize
 (e);%0A      r
@@ -4477,33 +4477,55 @@
  e) %7B%0A      
-LOG.error
+thingsWentWrongLetsReinitialize
 (e);%0A    %7D%0A 
@@ -4761,11 +4761,264 @@
    %7D%0A  %7D
+%0A%0A  private void thingsWentWrongLetsReinitialize(IOException e) %7B%0A    try %7B%0A      myMap = initializeMap();%0A      LOG.error(%22Repaired after crash%22, e);%0A    %7D%0A    catch (IOException e1) %7B%0A      LOG.error(%22Cannot repair%22, e1);%0A      myMap = null;%0A    %7D%0A  %7D
 %0A%7D%0A

repair after crash