/**
 * $RCSfile$
 * $Revision: $
 * $Date: $
 *
 * Copyright 2003-2006 Jive Software.
 *
 * All rights reserved. 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.
 */
package org.jivesoftware.smackx.filetransfer;

import org.jivesoftware.smack.XMPPException;

import java.io.*;
import java.util.concurrent.*;

/**
 * An incoming file transfer is created when the
 * {@link FileTransferManager#createIncomingFileTransfer(FileTransferRequest)}
 * method is invoked. It is a file being sent to the local user from another
 * user on the jabber network. There are two stages of the file transfer to be
 * concerned with and they can be handled in different ways depending upon the
 * method that is invoked on this class.
 * <p/>
 * The first way that a file is recieved is by calling the
 * {@link #recieveFile()} method. This method, negotiates the appropriate stream
 * method and then returns the <b><i>InputStream</b></i> to read the file
 * data from.
 * <p/>
 * The second way that a file can be recieved through this class is by invoking
 * the {@link #recieveFile(File)} method. This method returns immediatly and
 * takes as its parameter a file on the local file system where the file
 * recieved from the transfer will be put.
 *
 * @author Alexander Wenckus
 */
public class IncomingFileTransfer extends FileTransfer {

    private FileTransferRequest recieveRequest;

    private InputStream inputStream;

    protected IncomingFileTransfer(FileTransferRequest request,
                                   FileTransferNegotiator transferNegotiator) {
        super(request.getRequestor(), request.getStreamID(), transferNegotiator);
        this.recieveRequest = request;
    }

    /**
     * Negotiates the stream method to transfer the file over and then returns
     * the negotiated stream.
     *
     * @return The negotiated InputStream from which to read the data.
     * @throws XMPPException If there is an error in the negotiation process an exception
     *                       is thrown.
     */
    public InputStream recieveFile() throws XMPPException {
        if (inputStream != null) {
            throw new IllegalStateException("Transfer already negotiated!");
        }

        try {
            inputStream = negotiateStream();
        } catch (XMPPException e) {
            setException(e);
            throw e;
        }

        return inputStream;
    }

    /**
     * This method negotitates the stream and then transfer's the file over the
     * negotiated stream. The transfered file will be saved at the provided
     * location.
     * <p/>
     * This method will return immedialtly, file transfer progress can be
     * monitored through several methods:
     * <p/>
     * <UL>
     * <LI>{@link FileTransfer#getStatus()}
     * <LI>{@link FileTransfer#getProgress()}
     * <LI>{@link FileTransfer#isDone()}
     * </UL>
     *
     * @param file The location to save the file.
     * @throws XMPPException            when the file transfer fails
     * @throws IllegalArgumentException This exception is thrown when the the provided file is
     *                                  either null, or cannot be written to.
     */
    public void recieveFile(final File file) throws XMPPException {
        if (file != null) {
            if (!file.exists()) {
                try {
                    file.createNewFile();
                } catch (IOException e) {
                    throw new XMPPException(
                            "Could not create file to write too", e);
                }
            }
            if (!file.canWrite()) {
                throw new IllegalArgumentException("Cannot write to provided file");
            }
        } else {
            throw new IllegalArgumentException("File cannot be null");
        }

        Thread transferThread = new Thread(new Runnable() {
            public void run() {
                try {
                    inputStream = negotiateStream();
                } catch (XMPPException e) {
                    handleXMPPException(e);
                    return;
                }

                OutputStream outputStream = null;
                try {
                    outputStream = new FileOutputStream(file);
                    setStatus(Status.in_progress);
                    writeToStream(inputStream, outputStream);
                } catch (XMPPException e) {
                    setStatus(Status.error);
                    setError(Error.stream);
                    setException(e);
                } catch (FileNotFoundException e) {
                    setStatus(Status.error);
                    setError(Error.bad_file);
                    setException(e);
                }

                if (getStatus().equals(Status.in_progress)) {
                    setStatus(Status.complete);
                }
                if (inputStream != null) {
                    try {
                        inputStream.close();
                    } catch (Throwable io) {
                        /* Ignore */
                    }
                }
                if (outputStream != null) {
                    try {
                        outputStream.close();
                    } catch (Throwable io) {
                        /* Ignore */
                    }
                }
            }
        }, "File Transfer " + streamID);
        transferThread.start();
    }

    private void handleXMPPException(XMPPException e) {
        setStatus(FileTransfer.Status.error);
        setException(e);
    }

    private InputStream negotiateStream() throws XMPPException {
        setStatus(Status.negotiating_transfer);
        final StreamNegotiator streamNegotiator = negotiator
                .selectStreamNegotiator(recieveRequest);
        setStatus(Status.negotiating_stream);
        FutureTask<InputStream> streamNegotiatorTask = new FutureTask<InputStream>(
                new Callable<InputStream>() {

                    public InputStream call() throws Exception {
                        return streamNegotiator
                                .createIncomingStream(recieveRequest.getStreamInitiation());
                    }
                });
        streamNegotiatorTask.run();
        InputStream inputStream;
        try {
            inputStream = streamNegotiatorTask.get(15, TimeUnit.SECONDS);
        } catch (InterruptedException e) {
            throw new XMPPException("Interruption while executing", e);
        } catch (ExecutionException e) {
            throw new XMPPException("Error in execution", e);
        } catch (TimeoutException e) {
            throw new XMPPException("Request timed out", e);
        } finally {
            streamNegotiatorTask.cancel(true);
        }
        setStatus(Status.negotiated);
        return inputStream;
    }

    public void cancel() {
        setStatus(Status.cancelled);
    }

}
