/*
 * JBoss, Home of Professional Open Source.
 * Copyright 2013, Red Hat, Inc., and individual contributors
 * as indicated by the @author tags. See the copyright.txt file in the
 * distribution for a full listing of individual contributors.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */

package org.jboss.as.patching;

import java.io.Closeable;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.StandardCopyOption;
import java.util.zip.ZipFile;

import org.jboss.as.patching.logging.PatchLogger;
import org.wildfly.common.Assert;

/**
 * @author Emanuel Muckenhuber
 * @author Brian Stansberry (c) 2012 Red Hat Inc.
 * @author <a href="http://jmesnil/net/">Jeff Mesnil</a> (c) 2012 Red Hat Inc
 */
public class IoUtils {

    public static byte[] NO_CONTENT = new byte[0];

    private static final int DEFAULT_BUFFER_SIZE = 65536;

    /**
     * Copy input stream to output stream and close them both
     *
     * @param is input stream
     * @param os output stream
     *
     * @throws IOException for any error
     */
    public static void copyStreamAndClose(InputStream is, OutputStream os) throws IOException {
        try {
            copyStream(is, os, DEFAULT_BUFFER_SIZE);
            // throw an exception if the close fails since some data might be lost
            is.close();
            os.close();
        }
        finally {
            // ...but still guarantee that they're both closed
            safeClose(is);
            safeClose(os);
        }
    }

    /**
     * Copy input stream to output stream without closing streams. Flushes output stream when done.
     *
     * @param is input stream
     * @param os output stream
     *
     * @throws IOException for any error
     */
    public static void copyStream(InputStream is, OutputStream os) throws IOException {
        copyStream(is, os, DEFAULT_BUFFER_SIZE);
    }

    /**
     * Copy input stream to output stream without closing streams. Flushes output stream when done.
     *
     * @param is input stream
     * @param os output stream
     * @param bufferSize the buffer size to use
     *
     * @throws IOException for any error
     */
    private static void copyStream(InputStream is, OutputStream os, int bufferSize)
            throws IOException {
        Assert.checkNotNullParam("is", is);
        Assert.checkNotNullParam("os", os);
        byte[] buff = new byte[bufferSize];
        int rc;
        while ((rc = is.read(buff)) != -1) os.write(buff, 0, rc);
        os.flush();
    }

    public static void copyFile(File sourceFile, File targetFile) throws IOException {
        if (sourceFile.isDirectory()) {
            copyDir(sourceFile, targetFile);
        } else {
            File parent = targetFile.getParentFile();
            if (!parent.exists()) {
                if (!parent.mkdirs()) {
                    throw PatchLogger.ROOT_LOGGER.cannotCreateDirectory(parent.getAbsolutePath());
                }
            }
            try {
                Files.copy(sourceFile.toPath(), targetFile.toPath(), StandardCopyOption.REPLACE_EXISTING);
            } catch (IOException e) {
                throw PatchLogger.ROOT_LOGGER.cannotCopyFiles(sourceFile.getAbsolutePath(), targetFile.getAbsolutePath(),
                        e.getMessage(), e);
            }
        }
    }

    private static void copyDir(File sourceDir, File targetDir) throws IOException {
        if (targetDir.exists()) {
            if (!targetDir.isDirectory()) {
                throw PatchLogger.ROOT_LOGGER.notADirectory(targetDir.getAbsolutePath());
            }
        } else if (!targetDir.mkdirs()) {
            throw PatchLogger.ROOT_LOGGER.cannotCreateDirectory(targetDir.getAbsolutePath());
        }

        File[] children = sourceDir.listFiles();
        if (children != null) {
            for (File child : children) {
                copyFile(child, new File(targetDir, child.getName()));
            }
        }
    }

    public static byte[] copy(final InputStream is, final File target) throws IOException {
        if(! target.getParentFile().exists()) {
            target.getParentFile().mkdirs(); // Hmm
        }
        try (final OutputStream os = new FileOutputStream(target)){
            return HashUtils.copyAndGetHash(is, os);
        } catch (IOException e) {
            throw PatchLogger.ROOT_LOGGER.cannotCopyFiles(is.toString(), target.getAbsolutePath(),
                    e.getMessage(), e);
        }
    }

    public static byte[] copy(File source, File target) throws IOException {
        try (final FileInputStream is = new FileInputStream(source)){
            return copy(is, target);
        }
    }

    public static void safeClose(final Closeable closeable) {
        if(closeable != null) {
            try {
                closeable.close();
            } catch (IOException e) {
                //
            }
        }
    }

    public static void safeClose(final ZipFile closeable) {
        if(closeable != null) {
            try {
                closeable.close();
            } catch (IOException e) {
                //
            }
        }
    }

    public static boolean recursiveDelete(File root) {
        if (root == null) {
            return true;
        }
        boolean ok = true;
        if (root.isDirectory()) {
            final File[] files = root.listFiles();
            for (File file : files) {
                ok &= recursiveDelete(file);
            }
            return ok && (root.delete() || !root.exists());
        } else {
            ok &= root.delete() || !root.exists();
        }
        return ok;
    }

    public static File mkdir(File parent, String... segments) throws IOException {
        File dir = parent;
        for (String segment : segments) {
            dir = new File(dir, segment);
        }
        dir.mkdirs();
        return dir;
    }

    /**
     * Return a new File object based on the baseDir and the segments.
     *
     * This method does not perform any operation on the file system.
     */
    public static File newFile(File baseDir, String... segments) {
        File f = baseDir;
        for (String segment : segments) {
            f = new File(f, segment);
        }
        return f;
    }

}
