/*
 * 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.ufs;

import alluxio.underfs.UfsStatus;

import com.google.common.annotations.VisibleForTesting;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import javax.annotation.concurrent.ThreadSafe;

/**
 * This class provides a snapshot of everything in the journal and some utility methods.
 */
@ThreadSafe
@VisibleForTesting
public final class UfsJournalSnapshot {
  /** The committed checkpoints. */
  private final List<UfsJournalFile> mCheckpoints;
  /** The journal edit logs including the incomplete log. */
  private final List<UfsJournalFile> mLogs;
  /** The temporary checkpoint files. */
  private final List<UfsJournalFile> mTemporaryCheckpoints;

  /**
   * Creates an instance of the journal snapshot.
   *
   * @param checkpoints the checkpoints
   * @param logs the logs including the incomplete log
   * @param temporaryCheckpoints the temporary checkpoint files
   */
  private UfsJournalSnapshot(List<UfsJournalFile> checkpoints, List<UfsJournalFile> logs,
      List<UfsJournalFile> temporaryCheckpoints) {
    mCheckpoints = checkpoints;
    mLogs = logs;
    mTemporaryCheckpoints = temporaryCheckpoints;
  }

  /**
   * @return the checkpoints sorted by end sequence number increasingly
   */
  public List<UfsJournalFile> getCheckpoints() {
    return mCheckpoints;
  }

  /**
   * @return the latest checkpoint, null if no checkpoint exists
   */
  public UfsJournalFile getLatestCheckpoint() {
    if (!mCheckpoints.isEmpty()) {
      return mCheckpoints.get(mCheckpoints.size() - 1);
    }
    return null;
  }

  /**
   * @return the logs sorted by the end sequence number increasingly
   */
  public List<UfsJournalFile> getLogs() {
    return mLogs;
  }

  /**
   * @return the temporary checkpoints
   */
  public List<UfsJournalFile> getTemporaryCheckpoints() {
    return mTemporaryCheckpoints;
  }

  /**
   * Creates a snapshot of the journal.
   *
   * @param journal the journal
   * @return the journal snapshot
   */
  public static UfsJournalSnapshot getSnapshot(UfsJournal journal) throws IOException {
    // Checkpoints.
    List<UfsJournalFile> checkpoints = new ArrayList<>();
    UfsStatus[] statuses = journal.getUfs().listStatus(journal.getCheckpointDir().toString());
    if (statuses != null) {
      for (UfsStatus status : statuses) {
        UfsJournalFile file = UfsJournalFile.decodeCheckpointFile(journal, status.getName());
        if (file != null) {
          checkpoints.add(file);
        }
      }
      Collections.sort(checkpoints);
    }

    List<UfsJournalFile> logs = new ArrayList<>();
    statuses = journal.getUfs().listStatus(journal.getLogDir().toString());
    if (statuses != null) {
      for (UfsStatus status : statuses) {
        UfsJournalFile file = UfsJournalFile.decodeLogFile(journal, status.getName());
        if (file != null) {
          logs.add(file);
        }
      }
      Collections.sort(logs);
    }

    List<UfsJournalFile> tmpCheckpoints = new ArrayList<>();
    statuses = journal.getUfs().listStatus(journal.getTmpDir().toString());
    if (statuses != null) {
      for (UfsStatus status : statuses) {
        tmpCheckpoints.add(UfsJournalFile.decodeTemporaryCheckpointFile(journal, status.getName()));
      }
    }

    return new UfsJournalSnapshot(checkpoints, logs, tmpCheckpoints);
  }

  /**
   * Gets the current log (the incomplete log) that is being written to.
   *
   * @param journal the journal
   * @return the current log
   */
  @VisibleForTesting
  public static UfsJournalFile getCurrentLog(UfsJournal journal) throws IOException {
    List<UfsJournalFile> logs = new ArrayList<>();
    UfsStatus[] statuses = journal.getUfs().listStatus(journal.getLogDir().toString());
    if (statuses != null) {
      for (UfsStatus status : statuses) {
        UfsJournalFile file = UfsJournalFile.decodeLogFile(journal, status.getName());
        if (file != null) {
          logs.add(file);
        }
      }
      if (!logs.isEmpty()) {
        UfsJournalFile file = Collections.max(logs);
        if (file.isIncompleteLog()) {
          return file;
        }
      }
    }
    return null;
  }

  /**
   * Gets the first journal log sequence number that is not yet checkpointed.
   *
   * @return the first journal log sequence number that is not yet checkpointed
   */
  static long getNextLogSequenceNumberToCheckpoint(UfsJournal journal) throws IOException {
    List<UfsJournalFile> checkpoints = new ArrayList<>();
    UfsStatus[] statuses = journal.getUfs().listStatus(journal.getCheckpointDir().toString());
    if (statuses != null) {
      for (UfsStatus status : statuses) {
        UfsJournalFile file = UfsJournalFile.decodeCheckpointFile(journal, status.getName());
        if (file != null) {
          checkpoints.add(file);
        }
      }
      Collections.sort(checkpoints);
    }
    if (checkpoints.isEmpty()) {
      return 0;
    }
    return checkpoints.get(checkpoints.size() - 1).getEnd();
  }
}
