/*
 * Copyright (c) 2006-2015 DMDirc Developers
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

package com.dmdirc.addons.dcc;

import com.dmdirc.DMDircMBassador;
import com.dmdirc.FrameContainer;
import com.dmdirc.addons.dcc.events.DccChatRequestSentEvent;
import com.dmdirc.addons.dcc.events.DccSendRequestEvent;
import com.dmdirc.addons.dcc.io.DCC;
import com.dmdirc.addons.dcc.io.DCCChat;
import com.dmdirc.addons.dcc.io.DCCTransfer;
import com.dmdirc.addons.dcc.kde.KFileChooser;
import com.dmdirc.addons.ui_swing.UIUtilities;
import com.dmdirc.addons.ui_swing.injection.MainWindow;
import com.dmdirc.commandparser.BaseCommandInfo;
import com.dmdirc.commandparser.CommandArguments;
import com.dmdirc.commandparser.CommandInfo;
import com.dmdirc.commandparser.CommandType;
import com.dmdirc.commandparser.commands.Command;
import com.dmdirc.commandparser.commands.IntelligentCommand;
import com.dmdirc.commandparser.commands.context.CommandContext;
import com.dmdirc.commandparser.commands.context.ServerCommandContext;
import com.dmdirc.interfaces.CommandController;
import com.dmdirc.interfaces.Connection;
import com.dmdirc.interfaces.User;
import com.dmdirc.parser.interfaces.Parser;
import com.dmdirc.ui.WindowManager;
import com.dmdirc.ui.input.AdditionalTabTargets;
import com.dmdirc.ui.input.TabCompleterFactory;
import com.dmdirc.ui.input.TabCompletionType;
import com.dmdirc.ui.messages.BackBufferFactory;
import com.dmdirc.ui.messages.sink.MessageSinkManager;
import com.dmdirc.util.URLBuilder;

import java.awt.Window;
import java.io.File;

import javax.annotation.Nonnull;
import javax.inject.Inject;
import javax.swing.JFileChooser;
import javax.swing.JOptionPane;

/**
 * This command allows starting dcc chats/file transfers.
 */
public class DCCCommand extends Command implements IntelligentCommand {

    /** A command info object for this command. */
    public static final CommandInfo INFO = new BaseCommandInfo("dcc",
            "dcc <SEND|CHAT> <target> [params] - starts a DCC",
            CommandType.TYPE_SERVER);
    /** My Plugin. */
    private final DCCManager myPlugin;
    /** Main window used as the parent for dialogs. */
    private final Window mainWindow;
    /** Window management. */
    private final WindowManager windowManager;
    /** The sink manager to use to dispatch messages. */
    private final MessageSinkManager messageSinkManager;
    /** The factory to use for tab completers. */
    private final TabCompleterFactory tabCompleterFactory;
    /** The URL builder to use when finding icons. */
    private final URLBuilder urlBuilder;
    /** The bus to dispatch events on. */
    private final DMDircMBassador eventBus;
    private final BackBufferFactory backBufferFactory;

    /**
     * Creates a new instance of DCCCommand.
     */
    @Inject
    public DCCCommand(
            final CommandController controller,
            @MainWindow final Window mainWindow,
            final DCCManager plugin,
            final MessageSinkManager messageSinkManager,
            final WindowManager windowManager,
            final TabCompleterFactory tabCompleterFactory,
            final URLBuilder urlBuilder,
            final DMDircMBassador eventBus,
            final BackBufferFactory backBufferFactory) {
        super(controller);
        this.mainWindow = mainWindow;
        myPlugin = plugin;
        this.messageSinkManager = messageSinkManager;
        this.windowManager = windowManager;
        this.tabCompleterFactory = tabCompleterFactory;
        this.urlBuilder = urlBuilder;
        this.eventBus = eventBus;
        this.backBufferFactory = backBufferFactory;
    }

    @Override
    public void execute(@Nonnull final FrameContainer origin,
            final CommandArguments args, final CommandContext context) {
        if (args.getArguments().length > 1) {
            final String target = args.getArguments()[1];
            final Connection connection = ((ServerCommandContext) context).getConnection();
            final Parser parser = connection.getParser().get();
            final String myNickname = connection.getLocalUser().map(User::getNickname).orElse("Unknown");

            if (parser.isValidChannelName(target)
                    || parser.getStringConverter().equalsIgnoreCase(target,
                            myNickname)) {
                new Thread(() -> {
                    if (parser.getStringConverter().equalsIgnoreCase(target,
                            myNickname)) {
                        JOptionPane.showMessageDialog(null,
                                "You can't DCC yourself.", "DCC Error",
                                JOptionPane.ERROR_MESSAGE);
                    } else {
                        JOptionPane.showMessageDialog(null,
                                "You can't DCC a channel.", "DCC Error",
                                JOptionPane.ERROR_MESSAGE);
                    }
                }, "DCC-Error-Message").start();
                return;
            }
            final String type = args.getArguments()[0];
            if ("chat".equalsIgnoreCase(type)) {
                startChat(parser, connection, origin, myNickname, target, true);
            } else if ("send".equalsIgnoreCase(type)) {
                sendFile(target, origin, connection, true,
                        args.getArgumentsAsString(2));
            } else {
                sendLine(origin, args.isSilent(), FORMAT_ERROR,
                        "Unknown DCC Type: '" + type + "'");
            }
        } else {
            showUsage(origin, true, INFO.getName(), INFO.getHelp());
        }
    }

    /**
     * Starts a DCC Chat.
     *
     * @param parser     Parser from which command originated
     * @param connection Server from which command originated
     * @param origin     Frame container from which command originated
     * @param myNickname My current nickname
     * @param target     Target of the command
     * @param isSilent   Is this a silent command
     */
    private void startChat(final Parser parser, final Connection connection,
            final FrameContainer origin, final String myNickname,
            final String target, final boolean isSilent) {
        final DCCChat chat = new DCCChat();
        if (myPlugin.listen(chat)) {
            final ChatContainer window = new ChatContainer(
                    myPlugin.getContainer(),
                    chat,
                    origin.getConfigManager(),
                    backBufferFactory,
                    getController(),
                    "*Chat: " + target,
                    myNickname,
                    target,
                    tabCompleterFactory,
                    messageSinkManager,
                    urlBuilder,
                    eventBus);
            windowManager.addWindow(myPlugin.getContainer(), window);
            parser.sendCTCP(target, "DCC", "CHAT chat " + DCC.ipToLong(
                    myPlugin.getListenIP(parser)) + " " + chat.getPort());
            eventBus.publish(new DccChatRequestSentEvent(connection, target));
            sendLine(origin, isSilent, "DCCChatStarting", target, chat.getHost(), chat.getPort());
            window.addLine("DCCChatStarting", target, chat.getHost(), chat.getPort());
        } else {
            sendLine(origin, isSilent, "DCCChatError",
                    "Unable to start chat with " + target
                    + " - unable to create listen socket");
        }
    }

    /**
     * Ask for the file to send, then start the send.
     *
     * @param target     Person this dcc is to.
     * @param origin     The InputWindow this command was issued on
     * @param connection The server instance that this command is being executed on
     * @param isSilent   Whether this command is silenced or not
     * @param filename   The file to send
     *
     * @since 0.6.3m1
     */
    public void sendFile(final String target, final FrameContainer origin,
            final Connection connection, final boolean isSilent, final String filename) {
        // New thread to ask the user what file to send
        final File givenFile = new File(filename);
        final File selectedFile = UIUtilities.invokeAndWait(() -> {
            final JFileChooser jc = givenFile.exists()
                    ? KFileChooser.getFileChooser(origin.getConfigManager(),
                            myPlugin, givenFile)
                    : KFileChooser.getFileChooser(origin.getConfigManager(),
                            myPlugin);
            final int result = showFileChooser(givenFile, target, jc);

            if (result != JFileChooser.APPROVE_OPTION
                    || !handleInvalidItems(jc)) {
                return null;
            }
            return jc.getSelectedFile();
        });
        if (selectedFile == null) {
            return;
        }
        new Thread(() -> {
            final DCCTransfer send = new DCCTransfer(origin
                    .getConfigManager().getOptionInt(myPlugin.getDomain(),
                            "send.blocksize"));
            send.setTurbo(origin.getConfigManager().getOptionBool(
                    myPlugin.getDomain(), "send.forceturbo"));
            send.setType(DCCTransfer.TransferType.SEND);

            eventBus.publish(new DccSendRequestEvent(connection, target, selectedFile.
                    getAbsolutePath()));

            sendLine(origin, isSilent, FORMAT_OUTPUT,
                    "Starting DCC Send with: " + target);

            send.setFileName(selectedFile.getAbsolutePath());
            send.setFileSize(selectedFile.length());

            if (origin.getConfigManager().getOptionBool(
                    myPlugin.getDomain(), "send.reverse")) {
                final Parser parser = connection.getParser().get();
                final TransferContainer container = new TransferContainer(myPlugin, send,
                        origin.getConfigManager(), backBufferFactory, "Send: " + target,
                        target, connection, urlBuilder, eventBus);
                windowManager.addWindow(myPlugin.getContainer(), container);
                parser.sendCTCP(target, "DCC", "SEND \""
                        + selectedFile.getName() + "\" "
                        + DCC.ipToLong(myPlugin.getListenIP(parser))
                        + " 0 " + send.getFileSize() + " "
                        + send.makeToken()
                        + (send.isTurbo() ? " T" : ""));
            } else {
                final Parser parser = connection.getParser().get();
                if (myPlugin.listen(send)) {
                    final TransferContainer container = new TransferContainer(myPlugin, send,
                            origin.getConfigManager(), backBufferFactory, "*Send: "
                            + target, target, connection, urlBuilder, eventBus);
                    windowManager.addWindow(myPlugin.getContainer(), container);
                    parser.sendCTCP(target, "DCC", "SEND \""
                            + selectedFile.getName() + "\" "
                            + DCC.ipToLong(myPlugin.getListenIP(parser))
                            + " " + send.getPort() + " " + send.getFileSize()
                            + (send.isTurbo() ? " T" : ""));
                } else {
                    sendLine(origin, isSilent, "DCCSendError",
                            "Unable to start dcc send with " + target
                            + " - unable to create listen socket");
                }
            }
        }, "openFileThread").start();
    }

    /**
     * Checks for invalid items.
     *
     * @param jc File chooser to check
     *
     * @return true iif the selection was valid
     */
    private boolean handleInvalidItems(final JFileChooser jc) {
        if (jc.getSelectedFile().length() == 0) {
            JOptionPane.showMessageDialog(null,
                    "You can't send empty files over DCC.", "DCC Error",
                    JOptionPane.ERROR_MESSAGE);
            return false;
        } else if (!jc.getSelectedFile().exists()) {
            JOptionPane.showMessageDialog(null, "Invalid file specified",
                    "DCC Error", JOptionPane.ERROR_MESSAGE);
            return false;
        }
        return true;
    }

    /**
     * Sets up and display a file chooser.
     *
     * @param givenFile File to display
     * @param target    DCC target
     * @param jc        File chooser
     *
     * @return the return state of the file chooser on popdown:
     * <ul>
     * <li>JFileChooser.CANCEL_OPTION
     * <li>JFileChooser.APPROVE_OPTION
     * <li>JFileChooser.ERROR_OPTION if an error occurs or the dialog is dismissed
     * </ul>
     */
    private int showFileChooser(final File givenFile, final String target,
            final JFileChooser jc) {
        if (givenFile.exists() && givenFile.isFile()) {
            jc.setSelectedFile(givenFile);
            return JFileChooser.APPROVE_OPTION;
        } else {
            jc.setDialogTitle("Send file to " + target + " - DMDirc ");
            jc.setFileSelectionMode(JFileChooser.FILES_ONLY);
            jc.setMultiSelectionEnabled(false);
            return jc.showOpenDialog(mainWindow);
        }
    }

    @Override
    public AdditionalTabTargets getSuggestions(final int arg,
            final IntelligentCommandContext context) {
        final AdditionalTabTargets res = new AdditionalTabTargets();

        if (arg == 0) {
            res.add("SEND");
            res.add("CHAT");
            res.excludeAll();
        } else if (arg == 1) {
            res.exclude(TabCompletionType.COMMAND);
            res.exclude(TabCompletionType.CHANNEL);
        } else {
            res.excludeAll();
        }

        return res;
    }

}
