/*
 * Copyright 2010-2015 Institut Pasteur.
 * 
 * This file is part of Icy.
 * 
 * Icy 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.
 * 
 * Icy 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 Icy. If not, see <http://www.gnu.org/licenses/>.
 */
package icy.gui.system;

import icy.gui.component.ExternalizablePanel;
import icy.gui.component.button.IcyButton;
import icy.gui.component.button.IcyToggleButton;
import icy.gui.frame.progress.ProgressFrame;
import icy.gui.util.GuiUtil;
import icy.resource.ResourceUtil;
import icy.resource.icon.IcyIcon;
import icy.system.IcyExceptionHandler;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.datatransfer.Clipboard;
import java.awt.datatransfer.ClipboardOwner;
import java.awt.datatransfer.StringSelection;
import java.awt.datatransfer.Transferable;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.PrintStream;
import java.util.EventListener;

import javax.swing.Box;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextPane;
import javax.swing.text.BadLocationException;
import javax.swing.text.SimpleAttributeSet;
import javax.swing.text.StyleConstants;
import javax.swing.text.StyledDocument;

/**
 * @author Stephane
 */
public class OutputConsolePanel extends ExternalizablePanel implements ClipboardOwner
{
    public static interface OutputConsoleChangeListener extends EventListener
    {
        public void outputConsoleChanged(OutputConsolePanel source, boolean isError);
    }

    private class WindowsOutPrintStream extends PrintStream
    {
        boolean isStdErr;

        public WindowsOutPrintStream(PrintStream out, boolean isStdErr)
        {
            super(out);

            this.isStdErr = isStdErr;
        }

        @Override
        public void write(byte[] buf, int off, int len)
        {
            try
            {
                super.write(buf, off, len);

                final String text = new String(buf, off, len);
                addText(text, isStdErr);
            }
            catch (Throwable t)
            {
                addText(t.getMessage(), isStdErr);
            }
        }
    }

    /**
     * 
     */
    private static final long serialVersionUID = 7142067146669860938L;

    private static final int MAX_SIZE = 4 * 1024 * 1024; // 4 MB

    final JTextPane textPane;
    final StyledDocument doc;
    final SimpleAttributeSet normalAttributes;
    final SimpleAttributeSet errorAttributes;

    final public IcyButton clearLogButton;
    final public IcyButton copyLogButton;
    final public IcyButton reportLogButton;
    final public IcyToggleButton scrollLockButton;
    final public JPanel bottomPanel;

    public OutputConsolePanel()
    {
        super("Output", "outputConsole");

        textPane = new JTextPane();
        doc = textPane.getStyledDocument();

        errorAttributes = new SimpleAttributeSet();
        normalAttributes = new SimpleAttributeSet();

        StyleConstants.setFontFamily(errorAttributes, "arial");
        StyleConstants.setFontSize(errorAttributes, 11);
        StyleConstants.setForeground(errorAttributes, Color.red);

        StyleConstants.setFontFamily(normalAttributes, "arial");
        StyleConstants.setFontSize(normalAttributes, 11);
        StyleConstants.setForeground(normalAttributes, Color.black);

        clearLogButton = new IcyButton(new IcyIcon(ResourceUtil.ICON_DELETE));
        copyLogButton = new IcyButton(new IcyIcon(ResourceUtil.ICON_DOC_COPY));
        reportLogButton = new IcyButton(new IcyIcon(ResourceUtil.ICON_DOC_EXPORT));
        scrollLockButton = new IcyToggleButton(new IcyIcon(ResourceUtil.ICON_LOCK_OPEN));

        // ComponentUtil.setFontSize(textPane, 10);
        textPane.setEditable(false);

        clearLogButton.setFlat(true);
        copyLogButton.setFlat(true);
        reportLogButton.setFlat(true);
        scrollLockButton.setFlat(true);

        clearLogButton.setToolTipText("Clear all");
        copyLogButton.setToolTipText("Copy to clipboard");
        reportLogButton.setToolTipText("Report content to dev team");
        scrollLockButton.setToolTipText("Scroll Lock");

        clearLogButton.addActionListener(new ActionListener()
        {
            @Override
            public void actionPerformed(ActionEvent e)
            {
                textPane.setText("");
            }
        });
        copyLogButton.addActionListener(new ActionListener()
        {
            @Override
            public void actionPerformed(ActionEvent e)
            {
                final Clipboard clipboard = getToolkit().getSystemClipboard();

                clipboard.setContents(new StringSelection(getText()), OutputConsolePanel.this);
            }
        });
        reportLogButton.addActionListener(new ActionListener()
        {
            @Override
            public void actionPerformed(ActionEvent e)
            {
                final ProgressFrame progressFrame = new ProgressFrame("Sending report...");

                try
                {
                    // send report
                    IcyExceptionHandler.report(getText());
                }
                finally
                {
                    progressFrame.close();
                }
            }
        });
        scrollLockButton.addActionListener(new ActionListener()
        {
            @Override
            public void actionPerformed(ActionEvent e)
            {
                if (scrollLockButton.isSelected())
                    scrollLockButton.setIconImage(ResourceUtil.ICON_LOCK_CLOSE);
                else
                    scrollLockButton.setIconImage(ResourceUtil.ICON_LOCK_OPEN);
            }
        });

        bottomPanel = GuiUtil.createPageBoxPanel(
                Box.createVerticalStrut(4),
                GuiUtil.createLineBoxPanel(clearLogButton, Box.createHorizontalStrut(4), copyLogButton,
                        Box.createHorizontalStrut(4), reportLogButton, Box.createHorizontalGlue(),
                        Box.createHorizontalStrut(4), scrollLockButton));

        final JPanel panel = new JPanel();
        panel.setLayout(new BorderLayout());
        panel.add(textPane, BorderLayout.CENTER);

        final JScrollPane scrollPane = new JScrollPane(panel);

        setLayout(new BorderLayout());

        add(scrollPane, BorderLayout.CENTER);
        add(bottomPanel, BorderLayout.SOUTH);

        validate();

        // redirect standard output
        System.setOut(new WindowsOutPrintStream(System.out, false));
        System.setErr(new WindowsOutPrintStream(System.err, true));
    }

    public void addText(String text, boolean isError)
    {
        try
        {
            if (isError)
                doc.insertString(doc.getLength(), text, errorAttributes);
            else
                doc.insertString(doc.getLength(), text, normalAttributes);

            // limit to maximum size
            if (doc.getLength() > MAX_SIZE)
                doc.remove(0, doc.getLength() - MAX_SIZE);

            // scroll lock feature
            if (!scrollLockButton.isSelected())
                textPane.setCaretPosition(doc.getLength());
        }
        catch (Exception e)
        {
            // ignore
        }

        changed(isError);
    }

    /**
     * Get console content.
     */
    public String getText()
    {
        try
        {
            return doc.getText(0, doc.getLength());
        }
        catch (BadLocationException e)
        {
            return "";
        }
    }

    private void changed(boolean isError)
    {
        fireChangedEvent(isError);
    }

    public void fireChangedEvent(boolean isError)
    {
        for (OutputConsoleChangeListener listener : listenerList.getListeners(OutputConsoleChangeListener.class))
            listener.outputConsoleChanged(this, isError);
    }

    public void addOutputConsoleChangeListener(OutputConsoleChangeListener listener)
    {
        listenerList.add(OutputConsoleChangeListener.class, listener);
    }

    public void removeOutputConsoleChangeListener(OutputConsoleChangeListener listener)
    {
        listenerList.remove(OutputConsoleChangeListener.class, listener);
    }

    @Override
    public void lostOwnership(Clipboard clipboard, Transferable contents)
    {
        // ignore
    }
}