/*
 * The Alluxio Open Foundation licenses this work under the Apache License, version 2.0
 * (the "License"). You may not use this work except in compliance with the License, which is
 * available at www.apache.org/licenses/LICENSE-2.0
 *
 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
 * either express or implied, as more fully set forth in the License.
 *
 * See the NOTICE file distributed with this work for information regarding copyright ownership.
 */

package alluxio.master.journal.tool;

import alluxio.master.file.meta.InodeView;
import alluxio.master.journal.checkpoint.CheckpointInputStream;
import alluxio.master.journal.checkpoint.CompoundCheckpointFormat;
import alluxio.master.metastore.rocks.RocksInodeStore;
import alluxio.resource.CloseableIterator;
import alluxio.util.io.FileUtils;
import alluxio.util.io.PathUtils;

import com.google.common.base.Strings;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.BufferedOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Optional;

/**
 * An abstract class for journal dumpers.
 */
public abstract class AbstractJournalDumper {
  private static final Logger LOG = LoggerFactory.getLogger(AbstractJournalDumper.class);

  protected final String mMaster;
  protected final long mStart;
  protected final long mEnd;
  protected final String mInputDir;
  protected final String mOutputDir;
  protected final String mCheckpointsDir;
  protected final String mJournalEntryFile;

  /**
   * @param master journal master
   * @param start journal start sequence
   * @param end journal end sequence
   * @param outputDir output dir for journal dump
   * @param inputDir input dir for journal files
   */
  public AbstractJournalDumper(String master, long start, long end, String outputDir,
      String inputDir) throws IOException {
    mMaster = master;
    mStart = start;
    mEnd = end;
    mInputDir = inputDir;
    mOutputDir = outputDir;
    mCheckpointsDir = PathUtils.concatPath(outputDir, "checkpoints");
    mJournalEntryFile = PathUtils.concatPath(outputDir, "edits.txt");

    // Ensure output directory.
    Files.createDirectories(Paths.get(mOutputDir));
  }

  /**
   * Dumps journal.
   */
  abstract void dumpJournal() throws Throwable;

  /**
   * Used to read checkpoint streams.
   *
   * @param checkpoint the checkpoint stream
   * @param path persistence path
   * @throws IOException if checkpoint read fails
   */
  protected void readCheckpoint(CheckpointInputStream checkpoint, Path path) throws IOException {
    LOG.debug("Reading checkpoint of type {} to {}", checkpoint.getType().name(), path);
    switch (checkpoint.getType()) {
      case COMPOUND:
        readCompoundCheckpoint(checkpoint, path);
        break;
      case ROCKS_SINGLE:
        readRocksCheckpoint(checkpoint, path);
        break;
      default:
        readRegularCheckpoint(checkpoint, path);
        break;
    }
  }

  private void readCompoundCheckpoint(CheckpointInputStream checkpoint, Path path)
      throws IOException {
    Files.createDirectories(path);
    CompoundCheckpointFormat.CompoundCheckpointReader reader =
        new CompoundCheckpointFormat.CompoundCheckpointReader(checkpoint);
    Optional<CompoundCheckpointFormat.CompoundCheckpointReader.Entry> entryOpt;
    while ((entryOpt = reader.nextCheckpoint()).isPresent()) {
      CompoundCheckpointFormat.CompoundCheckpointReader.Entry entry = entryOpt.get();
      Path checkpointPath = path.resolve(entry.getName().toString());
      LOG.debug("Reading checkpoint for {} to {}", entry.getName(), checkpointPath);
      readCheckpoint(entry.getStream(), checkpointPath);
    }
  }

  private void readRocksCheckpoint(CheckpointInputStream checkpoint, Path path) throws IOException {
    // An empty dir for storing the db.
    Path dbPath = Paths.get(path.toFile().getPath() + "-rocks-db");
    // Create RocksInodeStore over checkpoint stream for extracting the inodes.
    try (PrintStream out =
             new PrintStream(new BufferedOutputStream(new FileOutputStream(path.toFile())));
         RocksInodeStore inodeStore = new RocksInodeStore(dbPath.toAbsolutePath().toString())) {
      // Create and restore RocksInodeStore from the checkpoint.
      inodeStore.restoreFromCheckpoint(checkpoint);
      // Dump entries.
      final String ENTRY_SEPARATOR = Strings.repeat("-", 80);
      try (CloseableIterator<InodeView> iterator = inodeStore.getCloseableIterator()) {
        while (iterator.hasNext()) {
          InodeView inode = iterator.next();
          out.println(ENTRY_SEPARATOR);
          out.println(inode.toProto());
        }
      }
    } finally {
      // Remove the temp db directory.
      FileUtils.deletePathRecursively(dbPath.toFile().getPath());
    }
  }

  private void readRegularCheckpoint(CheckpointInputStream checkpoint, Path path)
      throws IOException {
    try (PrintStream out =
        new PrintStream(new BufferedOutputStream(new FileOutputStream(path.toFile())))) {
      checkpoint.getType().getCheckpointFormat().parseToHumanReadable(checkpoint, out);
    }
  }
}
