/*
 * This file is part of muCommander, http://www.mucommander.com
 *
 * muCommander is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * muCommander 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package com.mucommander.job.impl;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.mucommander.commons.file.AbstractFile;
import com.mucommander.commons.file.DummyFile;
import com.mucommander.commons.file.FileOperation;
import com.mucommander.commons.file.FilePermissions;
import com.mucommander.commons.file.FileURL;
import com.mucommander.commons.file.util.FileSet;
import com.mucommander.commons.io.BufferPool;
import com.mucommander.commons.io.ChecksumInputStream;
import com.mucommander.commons.io.FileTransferError;
import com.mucommander.commons.io.FileTransferException;
import com.mucommander.commons.io.StreamUtils;
import com.mucommander.desktop.ActionType;
import com.mucommander.job.FileJobAction;
import com.mucommander.job.FileJobState;
import com.mucommander.text.Translator;
import com.mucommander.ui.action.ActionProperties;
import com.mucommander.ui.action.impl.SplitFileAction;
import com.mucommander.ui.dialog.file.FileCollisionDialog;
import com.mucommander.ui.dialog.file.ProgressDialog;
import com.mucommander.ui.main.MainFrame;

/**
 * This job split the file into parts with given size.
 *
 * @author Mariusz Jakubowski
 */
public class SplitFileJob extends AbstractCopyJob {
    private static final Logger LOGGER = LoggerFactory.getLogger(SplitFileJob.class);

    private long partSize;
    private AbstractFile sourceFile;
    private InputStream origFileStream;
    private AbstractFile destFolder;
    private long sizeLeft;
    private boolean recalculateCRC;


    /**
     * A class for holding file name and size of one part.
     *
     * @author Mariusz Jakubowski
     */
    private static class DummyDestFile extends DummyFile {
        private long size;

        public DummyDestFile(FileURL url, long size) {
            super(url);
            this.size = size;
        }

        @Override
        public long getSize() {
            return size;
        }
    }


    public SplitFileJob(ProgressDialog progressDialog, MainFrame mainFrame,
                        AbstractFile file, AbstractFile destFolder, long partSize, int parts) {
        super(progressDialog, mainFrame, new FileSet(), destFolder, null, FileCollisionDialog.FileCollisionAction.ASK);
        this.partSize = partSize;
        this.setNbFiles(parts);
        this.sourceFile = file;
        this.destFolder = destFolder;
        this.errorDialogTitle = Translator.get("split_file_dialog.error_title");
        createInputStream();
        sizeLeft = sourceFile.getSize();
        for (int i = 1; i <= parts; i++) {
            addDummyFile(i, Math.min(partSize, sizeLeft));
            sizeLeft -= partSize;
        }
        sizeLeft = sourceFile.getSize();
    }

    /**
     * Adds a dummy output file (used in progress monitoring).
     *
     * @param i    index of a file
     * @param size size of a file
     */
    private void addDummyFile(int i, long size) {
        String num;
        if (i < 10) {
            num = "00" + Integer.toString(i);
        } else if (i < 100) {
            num = "0" + Integer.toString(i);
        } else {
            num = Integer.toString(i);
        }
        FileURL childURL = (FileURL) destFolder.getURL().clone();
        childURL.setPath(destFolder.addTrailingSeparator(childURL.getPath()) + sourceFile.getName() + "." + num);
        DummyDestFile fileHolder = new DummyDestFile(childURL, size);
        files.add(fileHolder);
    }


    @Override
    protected void jobStarted() {
        super.jobStarted();
        createInputStream();
    }

    /**
     * Creates an input stream from the file.
     */
    private void createInputStream() {
        try {
            origFileStream = sourceFile.getInputStream();
        } catch (IOException e) {
            LOGGER.debug("Caught exception", e);
            showErrorDialog(errorDialogTitle,
                    Translator.get("error_while_transferring", sourceFile.getName()),
                    Arrays.asList(FileJobAction.CANCEL)
            );
            setState(FileJobState.INTERRUPTED);
            return;
        }
        origFileStream = setCurrentInputStream(origFileStream);
        // init checksum calculation
        if (isIntegrityCheckEnabled()) {
            try {
                origFileStream = new ChecksumInputStream(origFileStream, MessageDigest.getInstance("CRC32"));
            } catch (NoSuchAlgorithmException e) {
                setIntegrityCheckEnabled(false);
                LOGGER.debug("Caught exception", e);
            }
        }
    }


    @Override
    protected boolean processFile(AbstractFile file, Object recurseParams) {
        if (getState() == FileJobState.INTERRUPTED)
            return false;

        // Create destination AbstractFile instance
        AbstractFile destFile = createDestinationFile(file, baseDestFolder, file.getName());
        if (destFile == null)
            return false;

        destFile = checkForCollision(sourceFile, baseDestFolder, destFile, false);
        if (destFile == null)
            return false;

        OutputStream out = null;
        try {
            out = destFile.getOutputStream();

            try {
                long written = StreamUtils.copyStream(origFileStream, out, BufferPool.getDefaultBufferSize(), partSize);
                sizeLeft -= written;
            } catch (FileTransferException e) {
                if (e.getReason() == FileTransferError.WRITING_DESTINATION) {
                    // out of disk space - ask a user for a new disk
                    recalculateCRC = true;        // recalculate CRC (DigestInputStream doesn't support mark() and reset())
                    out.close();
                    out = null;
                    sizeLeft -= e.getBytesWritten();
                    showErrorDialog(ActionProperties.getActionLabel(ActionType.SplitFile),
                            Translator.get("split_file_dialog.insert_new_media"),
                            Arrays.asList(FileJobAction.OK, FileJobAction.CANCEL));
                    if (getState() == FileJobState.INTERRUPTED) {
                        return false;
                    }
                    // create new output file if necessary
                    if (sizeLeft > 0 && getCurrentFileIndex() == getNbFiles() - 1) {
                        setNbFiles(getNbFiles() + 1);
                        addDummyFile(getNbFiles(), sizeLeft);
                    }
                } else {
                    throw e;
                }
            }

            // Preserve source file's date
            if (destFile.isFileOperationSupported(FileOperation.CHANGE_DATE)) {
                try {
                    destFile.changeDate(sourceFile.getDate());
                } catch (IOException e) {
                    LOGGER.debug("failed to change date of " + destFile, e);
                    // Fail silently
                }
            }

            // Preserve source file's permissions: preserve only the permissions bits that are supported by the source
            // file and use default permissions for the rest of them.
            if (destFile.isFileOperationSupported(FileOperation.CHANGE_PERMISSION)) {
                try {
                    // use #importPermissions(AbstractFile, int) to avoid isDirectory test
                    destFile.importPermissions(sourceFile, FilePermissions.DEFAULT_FILE_PERMISSIONS);
                } catch (IOException e) {
                    LOGGER.debug("failed to import " + sourceFile + " permissions into " + destFile, e);
                    // Fail silently
                }
            }
        } catch (IOException e) {
            LOGGER.debug("Caught exception", e);

            showErrorDialog(errorDialogTitle,
                    Translator.get("error_while_transferring", destFile.getName()),
                    Arrays.asList(FileJobAction.CANCEL)
            );
            return false;

        } finally {
            try {
                if (out != null)
                    out.close();
            } catch (IOException e2) {
            }
        }

        return true;
    }


    // This job modifies baseDestFolder and its subfolders
    @Override
    protected boolean hasFolderChangedImpl(AbstractFile folder) {
        return baseDestFolder.isParentOf(folder);
    }

    @Override
    protected void jobCompleted() {
        // create checksum file
        if (isIntegrityCheckEnabled()) {
            if (origFileStream != null && (origFileStream instanceof ChecksumInputStream)) {
                String crcFileName = sourceFile.getName() + ".sfv";
                try {
                    String sourceChecksum;
                    if (recalculateCRC) {
                        origFileStream = sourceFile.getInputStream();
                        sourceChecksum = AbstractFile.calculateChecksum(origFileStream, MessageDigest.getInstance("CRC32"));
                        origFileStream.close();
                    } else {
                        sourceChecksum = ((ChecksumInputStream) origFileStream).getChecksumString();
                    }
                    AbstractFile crcFile = baseDestFolder.getDirectChild(crcFileName);
                    OutputStream crcStream = crcFile.getOutputStream();
                    String line = sourceFile.getName() + " " + sourceChecksum;
                    crcStream.write(line.getBytes("utf-8"));
                    crcStream.close();
                } catch (Exception e) {
                    LOGGER.debug("Caught exception", e);

                    showErrorDialog(errorDialogTitle,
                            Translator.get("error_while_transferring", crcFileName),
                            Arrays.asList(FileJobAction.CANCEL)
                    );
                }
            }
        }
        super.jobCompleted();
    }


}
