/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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
 *
 *      https://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 org.apache.commons.io;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.file.OpenOption;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.Objects;

import org.apache.commons.io.function.IOConsumer;
import org.apache.commons.io.function.IOFunction;

/**
 * Enumerates access modes for {@link RandomAccessFile} with factory methods.
 *
 * @see RandomAccessFile#RandomAccessFile(File, String)
 * @see RandomAccessFile#RandomAccessFile(String, String)
 * @see Enum
 * @since 2.12.0
 */
public enum RandomAccessFileMode {

    /**
     * Defines mode {@value #R} to open a {@link RandomAccessFile} for reading only.
     *
     * @see RandomAccessFile#RandomAccessFile(File, String)
     * @see RandomAccessFile#RandomAccessFile(String, String)
     */
    READ_ONLY(RandomAccessFileMode.R, 1), // NOPMD bug https://github.com/pmd/pmd/issues/5263

    /**
     * Defines mode {@value #RW} to open a {@link RandomAccessFile} for reading and writing.
     *
     * @see RandomAccessFile#RandomAccessFile(File, String)
     * @see RandomAccessFile#RandomAccessFile(String, String)
     */
    READ_WRITE(RandomAccessFileMode.RW, 2), // NOPMD bug https://github.com/pmd/pmd/issues/5263

    /**
     * Defines mode {@value #RWS} to open a {@link RandomAccessFile} for reading and writing, as with {@value #RW}, and also require that every update to the
     * file's content or metadata be written synchronously to the underlying storage device.
     *
     * @see RandomAccessFile#RandomAccessFile(File, String)
     * @see RandomAccessFile#RandomAccessFile(String, String)
     * @see StandardOpenOption#SYNC
     */
    READ_WRITE_SYNC_ALL(RandomAccessFileMode.RWS, 4), // NOPMD bug https://github.com/pmd/pmd/issues/5263

    /**
     * Defines mode {@value #RWD} to open a {@link RandomAccessFile} for reading and writing, as with {@value #RW}, and also require that every update to the
     * file's content be written synchronously to the underlying storage device.
     *
     * @see RandomAccessFile#RandomAccessFile(File, String)
     * @see RandomAccessFile#RandomAccessFile(String, String)
     * @see StandardOpenOption#DSYNC
     */
    READ_WRITE_SYNC_CONTENT(RandomAccessFileMode.RWD, 3); // NOPMD bug https://github.com/pmd/pmd/issues/5263

    private static final String R = "r";
    private static final String RW = "rw";
    private static final String RWD = "rwd";
    private static final String RWS = "rws";

    /**
     * Gets the enum value that best fits the given {@link OpenOption}s.
     * <p>
     * The input must be a legal and working combination for NIO.
     * </p>
     *
     * @param openOption options like {@link StandardOpenOption}.
     * @return best fit, by default {@link #READ_ONLY}.
     * @see StandardOpenOption
     * @since 2.18.0
     */
    public static RandomAccessFileMode valueOf(final OpenOption... openOption) {
        RandomAccessFileMode bestFit = READ_ONLY;
        for (final OpenOption option : openOption) {
            if (option instanceof StandardOpenOption) {
                switch ((StandardOpenOption) option) {
                case WRITE:
                    if (!bestFit.implies(READ_WRITE)) {
                        bestFit = READ_WRITE;
                    }
                    break;
                case DSYNC:
                    if (!bestFit.implies(READ_WRITE_SYNC_CONTENT)) {
                        bestFit = READ_WRITE_SYNC_CONTENT;
                    }
                    break;
                case SYNC:
                    if (!bestFit.implies(READ_WRITE_SYNC_ALL)) {
                        bestFit = READ_WRITE_SYNC_ALL;
                    }
                    break;
                default:
                    // explicit case skip (spotbugs)
                    continue;
                }
            }
        }
        return bestFit;
    }

    /**
     * Gets the {@link RandomAccessFileMode} value for the given mode, one of {@value #R}, {@value #RW}, {@value #RWD}, or {@value #RWS}.
     *
     * @param mode one of {@value #R}, {@value #RW}, {@value #RWD}, or {@value #RWS}.
     * @return A RandomAccessFileMode.
     * @throws IllegalArgumentException Thrown when mode is not one of {@value #R}, {@value #RW}, {@value #RWD}, or {@value #RWS}.
     * @since 2.18.0
     */
    public static RandomAccessFileMode valueOfMode(final String mode) {
        switch (mode) {
        case R:
            return READ_ONLY;
        case RW:
            return READ_WRITE;
        case RWD:
            return READ_WRITE_SYNC_CONTENT;
        case RWS:
            return READ_WRITE_SYNC_ALL;
        }
        throw new IllegalArgumentException(mode);
    }

    private final int level;

    private final String mode;

    RandomAccessFileMode(final String mode, final int level) {
        this.mode = mode;
        this.level = level;
    }

    /**
     * Performs an operation on the {@link RandomAccessFile} specified at the given {@link Path}.
     * <p>
     * This method allocates and releases the {@link RandomAccessFile} given to the consumer.
     * </p>
     *
     * @param file the file specifying the {@link RandomAccessFile} to open.
     * @param consumer the function to apply.
     * @throws FileNotFoundException See {@link IORandomAccessFile#IORandomAccessFile(File, String)}.
     * @throws IOException Thrown by the given function.
     * @since 2.18.0
     */
    public void accept(final Path file, final IOConsumer<RandomAccessFile> consumer) throws IOException {
        try (RandomAccessFile raf = create(file)) {
            consumer.accept(raf);
        }
    }

    /**
     * Applies the given function for a {@link RandomAccessFile} specified at the given {@link Path}.
     * <p>
     * This method allocates and releases the {@link RandomAccessFile} given to the function.
     * </p>
     *
     * @param <T> the return type of the function.
     * @param file the file specifying the {@link RandomAccessFile} to open.
     * @param function the function to apply.
     * @return the function's result value.
     * @throws FileNotFoundException See {@link IORandomAccessFile#IORandomAccessFile(File, String)}.
     * @throws IOException Thrown by the given function.
     * @since 2.18.0
     */
    public <T> T apply(final Path file, final IOFunction<RandomAccessFile, T> function) throws IOException {
        try (RandomAccessFile raf = create(file)) {
            return function.apply(raf);
        }
    }

    /**
     * Constructs a random access file to read from, and optionally to write to, the file specified by the {@link File} argument.
     * <p>
     * Prefer {@link #create(Path)} over this.
     * </p>
     *
     * @param file the file object
     * @return a random access file
     * @throws FileNotFoundException See {@link IORandomAccessFile#IORandomAccessFile(File, String)}.
     */
    public RandomAccessFile create(final File file) throws FileNotFoundException {
        return new IORandomAccessFile(file, mode);
    }

    /**
     * Constructs a random access file to read from, and optionally to write to, the file specified by the {@link File} argument.
     *
     * @param file the file object
     * @return a random access file
     * @throws FileNotFoundException See {@link IORandomAccessFile#IORandomAccessFile(File, String)}.
     */
    public RandomAccessFile create(final Path file) throws FileNotFoundException {
        return create(Objects.requireNonNull(file.toFile(), "file"));
    }

    /**
     * Constructs a random access file to read from, and optionally to write to, the file specified by the {@link File} argument.
     * <p>
     * Prefer {@link #create(Path)} over this.
     * </p>
     *
     * @param name the file object
     * @return a random access file
     * @throws FileNotFoundException See {@link IORandomAccessFile#IORandomAccessFile(File, String)}.
     */
    public RandomAccessFile create(final String name) throws FileNotFoundException {
        return new IORandomAccessFile(name, mode);
    }

    /**
     * A level for relative comparison of access mode rights, the larger, the more access.
     * <p>
     * The relative order from lowest to highest access rights is:
     * </p>
     * <ol>
     * <li>{@link #READ_ONLY}</li>
     * <li>{@link #READ_WRITE}</li>
     * <li>{@link #READ_WRITE_SYNC_CONTENT}</li>
     * <li>{@link #READ_WRITE_SYNC_ALL}</li>
     * </ol>
     * <p>
     * This is unrelated to {@link #ordinal()}.
     * </p>
     *
     * @return A level for relative comparison.
     */
    private int getLevel() {
        return level;
    }

    /**
     * Gets the access mode, one of {@value #R}, {@value #RW}, {@value #RWD}, or {@value #RWS}.
     *
     * @return one of {@value #R}, {@value #RW}, {@value #RWD}, or {@value #RWS}.
     * @since 2.18.0
     */
    public String getMode() {
        return mode;
    }

    /**
     * Tests whether this mode implies the given {@code other} mode.
     * <p>
     * For example:
     * </p>
     * <ol>
     * <li>{@link RandomAccessFileMode#READ_WRITE_SYNC_ALL} implies {{@link RandomAccessFileMode#READ_WRITE_SYNC_CONTENT}}.</li>
     * <li>{@link RandomAccessFileMode#READ_WRITE_SYNC_CONTENT} implies {{@link RandomAccessFileMode#READ_WRITE}}.</li>
     * <li>{@link RandomAccessFileMode#READ_WRITE} implies {{@link RandomAccessFileMode#READ_ONLY}}.</li>
     * </ol>
     *
     * @param other the non-null mode to test against.
     * @return whether this mode implies the given {@code other} mode.
     * @since 2.18.0
     */
    public boolean implies(final RandomAccessFileMode other) {
        // Note: The method name "implies" is inspired by java.security.Permission.implies(Permission)
        return getLevel() >= other.getLevel();
    }

    /**
     * Constructs a random access file to read from, and optionally to write to, the file specified by the {@link File} argument.
     *
     * @param name the file object
     * @return a random access file
     * @throws FileNotFoundException See {@link IORandomAccessFile#IORandomAccessFile(File, String)}.
     * @since 2.18.0
     */
    public IORandomAccessFile io(final String name) throws FileNotFoundException {
        return new IORandomAccessFile(name, mode);
    }

}
