/*
 * 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.vcs.log.util;

import com.intellij.openapi.application.PathManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.util.PathUtilRt;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.io.*;
import com.intellij.vcs.log.VcsLogProvider;
import org.jetbrains.annotations.NotNull;

import java.io.File;
import java.io.IOException;
import java.util.Comparator;
import java.util.List;
import java.util.Map;

public class PersistentUtil {
  @NotNull public static final File LOG_CACHE = new File(PathManager.getSystemPath(), "vcs-log");
  @NotNull private static final String CORRUPTION_MARKER = "corruption.marker";

  @NotNull
  public static String calcLogId(@NotNull Project project, @NotNull Map<VirtualFile, VcsLogProvider> logProviders) {
    int hashcode = calcLogProvidersHash(logProviders);
    return project.getLocationHash() + "." + Integer.toHexString(hashcode);
  }

  private static int calcLogProvidersHash(@NotNull final Map<VirtualFile, VcsLogProvider> logProviders) {
    List<VirtualFile> sortedRoots = ContainerUtil.sorted(logProviders.keySet(), Comparator.comparing(VirtualFile::getPath));
    return StringUtil.join(sortedRoots, root -> root.getPath() + "." + logProviders.get(root).getSupportedVcs().getName(), ".").hashCode();
  }

  @NotNull
  public static File getStorageFile(@NotNull String storageKind, @NotNull String logId, int version) {
    File subdir = new File(LOG_CACHE, storageKind);
    String safeLogId = PathUtilRt.suggestFileName(logId, true, true);
    final File mapFile = new File(subdir, safeLogId + "." + version);
    if (!mapFile.exists()) {
      IOUtil.deleteAllFilesStartingWith(new File(subdir, safeLogId));
    }
    return mapFile;
  }

  public static void cleanupOldStorageFile(@NotNull String storageKind, @NotNull String logId) {
    File subdir = new File(LOG_CACHE, storageKind);
    String safeLogId = PathUtilRt.suggestFileName(logId, true, true);
    IOUtil.deleteAllFilesStartingWith(new File(subdir, safeLogId));

    File[] files = subdir.listFiles();
    if (files != null && files.length == 0) {
      subdir.delete();
    }
  }

  @NotNull
  public static <T> PersistentEnumeratorBase<T> createPersistentEnumerator(@NotNull KeyDescriptor<T> keyDescriptor,
                                                                           @NotNull String storageKind,
                                                                           @NotNull String logId,
                                                                           int version) throws IOException {
    File storageFile = getStorageFile(storageKind, logId, version);

    return IOUtil.openCleanOrResetBroken(() ->
                                           new PersistentBTreeEnumerator<>(storageFile, keyDescriptor, Page.PAGE_SIZE, null, version),
                                         storageFile);
  }

  public static boolean deleteWithRenamingAllFilesStartingWith(@NotNull File baseFile) {
    File parentFile = baseFile.getParentFile();
    if (parentFile == null) return false;

    File[] files = parentFile.listFiles(pathname -> pathname.getName().startsWith(baseFile.getName()));
    if (files == null) return true;

    boolean deleted = true;
    for (File f : files) {
      deleted &= FileUtil.deleteWithRenaming(f);
    }
    return deleted;
  }

  // this method cleans up all storage files for a project in a specified subdir
  // it assumes that these storage files all start with "safeLogId."
  // as method getStorageFile creates them
  // so these two methods should be changed in sync
  public static boolean cleanupStorageFiles(@NotNull String subdirName, @NotNull String id) {
    File subdir = new File(LOG_CACHE, subdirName);
    String safeLogId = PathUtilRt.suggestFileName(id, true, true);
    return deleteWithRenamingAllFilesStartingWith(new File(subdir, safeLogId + "."));
  }

  // do not forget to change cleanupStorageFiles method when editing this one
  @NotNull
  public static File getStorageFile(@NotNull String subdirName,
                                    @NotNull String kind,
                                    @NotNull String id,
                                    int version,
                                    boolean cleanupOldVersions) {
    File subdir = new File(LOG_CACHE, subdirName);
    String safeLogId = PathUtilRt.suggestFileName(id, true, true);
    File file = new File(subdir, safeLogId + "." + kind + "." + version);
    if (cleanupOldVersions && !file.exists()) {
      IOUtil.deleteAllFilesStartingWith(new File(subdir, safeLogId + "." + kind));
    }
    return file;
  }

  @NotNull
  public static File getCorruptionMarkerFile() {
    return new File(LOG_CACHE, CORRUPTION_MARKER);
  }
}
