/*
 * Copyright (c) 2015-present, Facebook, Inc.
 * All rights reserved.
 *
 * This source code is licensed under the BSD-style license found in the
 * LICENSE file in the root directory of this source tree. An additional grant
 * of patent rights can be found in the PATENTS file in the same directory.
 */

package com.facebook.cache.disk;

import javax.annotation.Nullable;

import java.io.File;
import java.io.IOException;

import com.facebook.cache.common.CacheErrorLogger;
import com.facebook.common.file.FileTree;
import com.facebook.common.file.FileUtils;
import com.facebook.common.internal.Preconditions;
import com.facebook.common.internal.VisibleForTesting;
import com.facebook.common.logging.FLog;

/**
 * A supplier of a DiskStorage concrete implementation.
 */
public class DefaultDiskStorageSupplier implements DiskStorageSupplier {
  private static final Class<?> TAG = DefaultDiskStorageSupplier.class;

  private final int mVersion;
  private final File mRootDirectory;
  private final CacheErrorLogger mCacheErrorLogger;

  @VisibleForTesting
  volatile State mCurrentState;

  /**
   * Represents the current 'cached' state.
   */
  @VisibleForTesting static class State {
    public final @Nullable DiskStorage storage;
    public final @Nullable File rootDirectory;

    @VisibleForTesting State(@Nullable File rootDirectory, @Nullable DiskStorage storage) {
      this.storage = storage;
      this.rootDirectory = rootDirectory;
    }
  }

  public DefaultDiskStorageSupplier(
      int version,
      File baseDirectoryPath,
      String baseDirectoryName,
      CacheErrorLogger cacheErrorLogger) {
    mVersion = version;
    mCacheErrorLogger = cacheErrorLogger;
    mRootDirectory = new File(baseDirectoryPath, baseDirectoryName);
    mCurrentState = new State(null, null);
  }

  /**
   * Gets a concrete disk-storage instance. If nothing has changed since the last call, then
   * the last state is returned
   * @return an instance of the appropriate DiskStorage class
   * @throws IOException
   */
  @Override
  public synchronized DiskStorage get() throws IOException {
    if (shouldCreateNewStorage()) {
      // discard anything we created
      deleteOldStorageIfNecessary();
      createStorage();
    }
    return Preconditions.checkNotNull(mCurrentState.storage);
  }

  private boolean shouldCreateNewStorage() {
    State currentState = mCurrentState;
    return (currentState.storage == null ||
        currentState.rootDirectory == null ||
        !currentState.rootDirectory.exists());
  }

  @VisibleForTesting
  void deleteOldStorageIfNecessary() {
    if (mCurrentState.storage != null && mCurrentState.rootDirectory != null) {
      // LATER: Actually delegate this call to the storage. We shouldn't be
      // making an end-run around it
      FileTree.deleteRecursively(mCurrentState.rootDirectory);
    }
  }

  private void createStorage() throws IOException {
    createRootDirectoryIfNecessary(mRootDirectory);
    DiskStorage storage = new DefaultDiskStorage(mRootDirectory, mVersion, mCacheErrorLogger);
    mCurrentState = new State(mRootDirectory, storage);
  }

  @VisibleForTesting
  void createRootDirectoryIfNecessary(File rootDirectory) throws IOException {
    try {
      FileUtils.mkdirs(rootDirectory);
    } catch (FileUtils.CreateDirectoryException cde) {
      mCacheErrorLogger.logError(
          CacheErrorLogger.CacheErrorCategory.WRITE_CREATE_DIR,
          TAG,
          "createRootDirectoryIfNecessary",
          cde);
      throw cde;
    }
    FLog.d(TAG, "Created cache directory %s", rootDirectory.getAbsolutePath());
  }
}