/*
 * SK's Minecraft Launcher
 * Copyright (C) 2010-2014 Albert Pham <http://www.sk89q.com> and contributors
 * Please see LICENSE.txt for license information.
 */

package com.skcraft.launcher.dialog;

import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.skcraft.concurrency.ObservableFuture;
import com.skcraft.concurrency.ProgressObservable;
import com.skcraft.launcher.swing.LinedBoxPanel;
import com.skcraft.launcher.swing.SwingHelper;
import com.skcraft.launcher.util.SwingExecutor;
import lombok.extern.java.Log;

import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.lang.ref.WeakReference;
import java.util.Timer;
import java.util.TimerTask;

import static com.skcraft.launcher.util.SharedLocale._;

@Log
public class ProgressDialog extends JDialog {

    private static WeakReference<ProgressDialog> lastDialogRef;

    private final String defaultTitle;
    private final String defaultMessage;
    private final JLabel label = new JLabel();
    private final JPanel progressPanel = new JPanel(new BorderLayout(0, 5));
    private final JPanel textAreaPanel = new JPanel(new BorderLayout());
    private final JProgressBar progressBar = new JProgressBar();
    private final LinedBoxPanel buttonsPanel = new LinedBoxPanel(true);
    private final JTextArea logText = new JTextArea();
    private final JScrollPane logScroll = new JScrollPane(logText);
    private final JButton detailsButton = new JButton();
    private final JButton logButton = new JButton(_("progress.viewLog"));
    private final JButton cancelButton = new JButton(_("button.cancel"));

    public ProgressDialog(Window owner, String title, String message) {
        super(owner, title, ModalityType.DOCUMENT_MODAL);

        setResizable(false);
        initComponents();
        label.setText(message);
        defaultTitle = title;
        defaultMessage = message;
        setCompactSize();
        setLocationRelativeTo(owner);

        setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
        addWindowListener(new WindowAdapter() {
            @Override
            public void windowClosing(WindowEvent event) {
                if (confirmCancel()) {
                    cancel();
                    dispose();
                }
            }
        });
    }

    private void setCompactSize() {
        detailsButton.setText(_("progress.details"));
        logButton.setVisible(false);
        setMinimumSize(new Dimension(400, 100));
        pack();
    }

    private void setDetailsSize() {
        detailsButton.setText(_("progress.less"));
        logButton.setVisible(true);
        setSize(400, 350);
    }

    private void initComponents() {
        progressBar.setMaximum(1000);
        progressBar.setMinimum(0);
        progressBar.setIndeterminate(true);
        progressBar.setPreferredSize(new Dimension(0, 18));

        buttonsPanel.addElement(detailsButton);
        buttonsPanel.addElement(logButton);
        buttonsPanel.addGlue();
        buttonsPanel.addElement(cancelButton);
        buttonsPanel.setBorder(BorderFactory.createEmptyBorder(30, 13, 13, 13));

        logScroll.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS);
        logText.setBackground(getBackground());
        logText.setEditable(false);
        logText.setLineWrap(true);
        logText.setWrapStyleWord(false);
        logText.setFont(new JLabel().getFont());

        progressPanel.add(label, BorderLayout.NORTH);
        progressPanel.setBorder(BorderFactory.createEmptyBorder(13, 13, 0, 13));
        progressPanel.add(progressBar, BorderLayout.CENTER);
        textAreaPanel.setBorder(BorderFactory.createEmptyBorder(10, 13, 0, 13));
        textAreaPanel.add(logScroll, BorderLayout.CENTER);

        add(progressPanel, BorderLayout.NORTH);
        add(textAreaPanel, BorderLayout.CENTER);
        add(buttonsPanel, BorderLayout.SOUTH);

        textAreaPanel.setVisible(false);
        cancelButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                if (confirmCancel()) {
                    cancel();
                    dispose();
                }
            }
        });

        detailsButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                toggleDetails();
            }
        });

        logButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                ConsoleFrame.showMessages();
            }
        });
    }

    private boolean confirmCancel() {
        return SwingHelper.confirmDialog(this, _("progress.confirmCancel"), _("progress.confirmCancelTitle"));
    }

    protected void cancel() {
    }

    private void toggleDetails() {
        if (textAreaPanel.isVisible()) {
            textAreaPanel.setVisible(false);
            setCompactSize();
        } else {
            textAreaPanel.setVisible(true);
            setDetailsSize();
        }
        setLocationRelativeTo(getOwner());
    }

    public static void showProgress(final Window owner, final ObservableFuture<?> future, String title, String message) {
        final ProgressDialog dialog = new ProgressDialog(owner, title, message) {
            @Override
            protected void cancel() {
                future.cancel(true);
            }
        };

        lastDialogRef = new WeakReference<ProgressDialog>(dialog);

        final Timer timer = new Timer();
        timer.scheduleAtFixedRate(new UpdateProgress(dialog, future), 400, 400);

        Futures.addCallback(future, new FutureCallback<Object>() {
            @Override
            public void onSuccess(Object result) {
                timer.cancel();
                dialog.dispose();
            }

            @Override
            public void onFailure(Throwable t) {
                timer.cancel();
                dialog.dispose();
            }
        }, SwingExecutor.INSTANCE);

        dialog.setVisible(true);
    }

    public static ProgressDialog getLastDialog() {
        WeakReference<ProgressDialog> ref = lastDialogRef;
        if (ref != null) {
            ProgressDialog dialog = ref.get();
            if (!dialog.isVisible()) {
                return dialog;
            }
        }

        return null;
    }

    private static class UpdateProgress extends TimerTask {
        private final ProgressDialog dialog;
        private final ProgressObservable observable;

        public UpdateProgress(ProgressDialog dialog, ProgressObservable observable) {
            this.dialog = dialog;
            this.observable = observable;
        }

        @Override
        public void run() {
            SwingUtilities.invokeLater(new Runnable() {
                @Override
                public void run() {
                    JProgressBar progressBar = dialog.progressBar;
                    JTextArea logText = dialog.logText;
                    JLabel label = dialog.label;

                    double progress = observable.getProgress();
                    if (progress >= 0) {
                        dialog.setTitle(_("progress.percentTitle",
                                Math.round(progress * 100 * 100) / 100.0, dialog.defaultTitle));
                        progressBar.setValue((int) (progress * 1000));
                        progressBar.setIndeterminate(false);
                    } else {
                        dialog.setTitle( dialog.defaultTitle);
                        progressBar.setIndeterminate(true);
                    }

                    String status = observable.getStatus();
                    if (status == null) {
                        status = _("progress.defaultStatus");
                        label.setText(dialog.defaultMessage);
                    } else {
                        int index = status.indexOf('\n');
                        if (index == -1) {
                            label.setText(status);
                        } else {
                            label.setText(status.substring(0, index));
                        }
                    }
                    logText.setText(status);
                    logText.setCaretPosition(0);
                }
            });
        }
    }

}
