package org.codehaus.plexus.util;

/*
 * Copyright The Codehaus Foundation.
 *
 * 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.
 */

import java.io.BufferedOutputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.io.Writer;
import java.lang.reflect.Method;
import java.nio.file.Files;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.TestInfo;

import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;

/**
 * Base class for testcases doing tests with files.
 *
 * @author Jeremias Maerki
 * @version $Id: $Id
 * @since 3.4.0
 */
public abstract class FileBasedTestCase {
    private static File testDir;

    private TestInfo testInfo;

    /**
     * <p>getTestDirectory.</p>
     *
     * @return a {@link java.io.File} object.
     */
    public static File getTestDirectory() {
        if (testDir == null) {
            testDir = (new File("target/test/io/")).getAbsoluteFile();
        }
        return testDir;
    }

    /**
     * <p>createFile.</p>
     *
     * @param file a {@link java.io.File} object.
     * @param size a long.
     * @return an array of {@link byte} objects.
     * @throws java.io.IOException if any.
     */
    protected byte[] createFile(final File file, final long size) throws IOException {
        if (!file.getParentFile().exists()) {
            throw new IOException("Cannot create file " + file + " as the parent directory does not exist");
        }

        byte[] data = generateTestData(size);

        final BufferedOutputStream output = new BufferedOutputStream(Files.newOutputStream(file.toPath()));

        try {
            output.write(data);

            return data;
        } finally {
            output.close();
        }
    }

    /**
     * <p>createSymlink.</p>
     *
     * @param link a {@link java.io.File} object.
     * @param target a {@link java.io.File} object.
     * @return a boolean.
     */
    protected boolean createSymlink(final File link, final File target) {
        try {
            String[] args = {"ln", "-s", target.getAbsolutePath(), link.getAbsolutePath()};
            Process process = Runtime.getRuntime().exec(args);
            process.waitFor();
            if (0 != process.exitValue()) {
                return false;
            }
        } catch (Exception e) {
            // assume platform does not support "ln" command, tests should be skipped
            return false;
        }
        return true;
    }

    /**
     * <p>generateTestData.</p>
     *
     * @param size a long.
     * @return an array of {@link byte} objects.
     */
    protected byte[] generateTestData(final long size) {
        try {
            ByteArrayOutputStream baout = new ByteArrayOutputStream();
            generateTestData(baout, size);
            return baout.toByteArray();
        } catch (IOException ioe) {
            throw new RuntimeException("This should never happen: " + ioe.getMessage());
        }
    }

    /**
     * <p>generateTestData.</p>
     *
     * @param out a {@link java.io.OutputStream} object.
     * @param size a long.
     * @throws java.io.IOException if any.
     */
    protected void generateTestData(final OutputStream out, final long size) throws IOException {
        for (int i = 0; i < size; i++) {
            // output.write((byte)'X');

            // nice varied byte pattern compatible with Readers and Writers
            out.write((byte) ((i % 127) + 1));
        }
    }

    /**
     * <p>newFile.</p>
     *
     * @param filename a {@link java.lang.String} object.
     * @return a {@link java.io.File} object.
     * @throws java.io.IOException if any.
     */
    protected File newFile(String filename) throws IOException {
        final File destination = new File(getTestDirectory(), filename);
        /*
         * assertTrue( filename + "Test output data file shouldn't previously exist", !destination.exists() );
         */
        if (destination.exists()) {
            FileUtils.forceDelete(destination);
        }
        return destination;
    }

    /**
     * <p>checkFile.</p>
     *
     * @param file a {@link java.io.File} object.
     * @param referenceFile a {@link java.io.File} object.
     * @throws java.lang.Exception if any.
     */
    protected void checkFile(final File file, final File referenceFile) throws Exception {
        assertTrue(file.exists(), "Check existence of output file");
        assertEqualContent(referenceFile, file);
    }

    /**
     * <p>checkWrite.</p>
     *
     * @param output a {@link java.io.OutputStream} object.
     * @throws java.lang.Exception if any.
     */
    protected void checkWrite(final OutputStream output) throws Exception {
        try {
            new PrintStream(output).write(0);
        } catch (final Throwable t) {
            fail("The copy() method closed the stream " + "when it shouldn't have. " + t.getMessage());
        }
    }

    /**
     * <p>checkWrite.</p>
     *
     * @param output a {@link java.io.Writer} object.
     * @throws java.lang.Exception if any.
     */
    protected void checkWrite(final Writer output) throws Exception {
        try {
            new PrintWriter(output).write('a');
        } catch (final Throwable t) {
            fail("The copy() method closed the stream " + "when it shouldn't have. " + t.getMessage());
        }
    }

    /**
     * <p>deleteFile.</p>
     *
     * @param file a {@link java.io.File} object.
     * @throws java.lang.Exception if any.
     */
    protected void deleteFile(final File file) throws Exception {
        if (file.exists()) {
            assertTrue(file.delete(), "Couldn't delete file: " + file);
        }
    }

    // ----------------------------------------------------------------------
    // Assertions
    // ----------------------------------------------------------------------

    /** Assert that the content of two files is the same. */
    private void assertEqualContent(final File f0, final File f1) throws IOException {
        /*
         * This doesn't work because the filesize isn't updated until the file is closed. assertTrue( "The files " + f0
         * + " and " + f1 + " have differing file sizes (" + f0.length() + " vs " + f1.length() + ")", ( f0.length() ==
         * f1.length() ) );
         */
        byte[] buf0 = Files.readAllBytes(f0.toPath());
        byte[] buf1 = Files.readAllBytes(f0.toPath());
        assertArrayEquals(buf0, buf1, "The files " + f0 + " and " + f1 + " have different content");
    }

    /**
     * Assert that the content of a file is equal to that in a byte[].
     *
     * @param b0 an array of {@link byte} objects.
     * @param file a {@link java.io.File} object.
     * @throws java.io.IOException if any.
     */
    protected void assertEqualContent(final byte[] b0, final File file) throws IOException {
        byte[] b1 = Files.readAllBytes(file.toPath());
        assertArrayEquals(b0, b1, "Content differs");
    }

    /**
     * <p>assertIsDirectory.</p>
     *
     * @param file a {@link java.io.File} object.
     */
    protected void assertIsDirectory(File file) {
        assertTrue(file.exists(), "The File doesn't exists: " + file.getAbsolutePath());

        assertTrue(file.isDirectory(), "The File isn't a directory: " + file.getAbsolutePath());
    }

    /**
     * <p>assertIsFile.</p>
     *
     * @param file a {@link java.io.File} object.
     */
    protected void assertIsFile(File file) {
        assertTrue(file.exists(), "The File doesn't exists: " + file.getAbsolutePath());

        assertTrue(file.isFile(), "The File isn't a file: " + file.getAbsolutePath());
    }

    @BeforeEach
    void init(TestInfo testInfo) {
        this.testInfo = testInfo;
    }

    protected String getTestMethodName() {
        return testInfo.getTestMethod().map(Method::getName).orElse(null);
    }
}
