/*
 * Copyright 2008 Perry Nguyen <pfnguyen@hanhuy.com>
 *
 * Licensed under the GPLv2
 */
package com.hanhuy.clipboard;

import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;
import java.awt.datatransfer.Clipboard;
import java.awt.datatransfer.Transferable;
import java.awt.Toolkit;
import javax.swing.JFrame;
import javax.swing.JTextArea;
import javax.swing.JScrollPane;
import javax.swing.ScrollPaneConstants;
import java.awt.SystemTray;
import java.awt.TrayIcon;
import java.awt.EventQueue;
import java.net.ServerSocket;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.Socket;
import javax.swing.JOptionPane;
import javax.swing.ImageIcon;
import java.awt.PopupMenu;
import java.awt.MenuItem;
import java.awt.AWTException;
import java.awt.Frame;
import java.awt.Font;
import java.net.InetAddress;
import java.util.concurrent.Executors;
import java.util.concurrent.ExecutorService;

public class Main {
    private final Clipboard cb;
    private final JTextArea text;
    private final JFrame frame;
    private TrayIcon trayIcon;
    private SystemTray tray;
    private String lastSelection;
    public static void main(String[] args) {
        EventQueue.invokeLater(new Runnable() {
            public void run() {
                new Main();
            }
        });
    }
    Main() {
        cb = Toolkit.getDefaultToolkit().getSystemClipboard();
        frame = new JFrame("Clipboard Listener");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        text = new JTextArea(10, 80);
        text.setFont(new Font(Font.MONOSPACED, Font.PLAIN, 12));
        text.setEditable(false);
        JScrollPane pane = new JScrollPane(text,
                ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED,
                ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
        ImageIcon icon = new ImageIcon(
                getClass().getClassLoader().getResource(
                        "com/hanhuy/clipboard/paste_plain.png"));
        frame.setIconImage(icon.getImage());
        frame.add(pane);
        frame.pack();
        //frame.setVisible(true);
        final ExecutorService es = Executors.newFixedThreadPool(3);

        Thread t = new Thread(new Runnable() {
            public void run() {
                ServerSocket server = null;
                try {
                    server = new ServerSocket(4573);
                    while (true) {
                        Socket socket = server.accept();
                        es.submit(makeSocketReader(socket));
                    }
                }
                catch (IOException e) {
                    error(e.getMessage());
                }
                finally {
                    if (server != null) {
                        try {
                            server.close();
                        }
                        catch (IOException ex) { } // ignore
                    }
                }
            }
        }, "Socket Listener/Reader");
        t.setDaemon(true);
        t.start();
        initSystemTray(icon);
    }

    private Runnable makeSocketReader(final Socket socket) {
        return new Runnable() {
            public void run() {
                try {
                    InputStream in = socket.getInputStream();
                    InputStreamReader reader =
                            new InputStreamReader(in);
                    StringBuilder b = new StringBuilder();
                    char[] buf = new char[8192];
                    int read;
                    while ((read = reader.read(buf, 0, 8192)) != -1)
                        b.append(buf, 0, read);
    
                    reader.close();
                    copyToClipboard(b.toString());
                }
                catch (IOException e) {
                    error(e.getMessage());
                }
                finally {
                    if (socket != null) {
                        try {
                            socket.close();
                        }
                        catch (IOException ex) { } // ignore
                    }
                }
            }
        };
    }

    private void initSystemTray(ImageIcon icon) {
        if (SystemTray.isSupported()) {
            tray = SystemTray.getSystemTray();
            ActionListener l = new ActionListener() {
                public void actionPerformed(ActionEvent e) {
                    if (!frame.isVisible()) {
                        frame.setVisible(true);
                        frame.setState(Frame.NORMAL);
                        EventQueue.invokeLater(new Runnable() {
                            public void run() {
                                text.setText(lastSelection);
                                text.setCaretPosition(0);
                            }
                        });
                    } else {
                        frame.requestFocusInWindow();
                        frame.toFront();
                    }
                }
            };
            PopupMenu popup = new PopupMenu();
            MenuItem item = new MenuItem("Open Clipboard Listener Window");
            item.addActionListener(l);
            popup.add(item);
            trayIcon = new TrayIcon(icon.getImage(),
                    "Clipboard Listener", popup);
            trayIcon.addActionListener(l);
            try {
                tray.add(trayIcon);
                frame.addWindowListener(new WindowAdapter() {
                    @Override public void windowIconified(WindowEvent e) {
                        frame.setVisible(false);
                    }
                    @Override public void windowClosing(WindowEvent e) {
                        tray.remove(trayIcon);
                    }
                });
            }
            catch (AWTException e) {
                error(e.getMessage());
            }
        }
    }
    void copyToClipboard(final String selection) {
        Runnable r = new Runnable() {
            public void run() {
                StringSelection s = new StringSelection(selection);
                cb.setContents(s, s);
                // make sure this happens after clipboardChanged
                EventQueue.invokeLater(new Runnable() {
                    public void run() {
                        lastSelection = selection;
                        if (!frame.isVisible() && trayIcon != null) {
                            trayIcon.displayMessage("Clipboard Updated",
                                    selection, TrayIcon.MessageType.INFO);
                        } else {
                            text.setText(selection);
                            text.setCaretPosition(0);
                        }
                    }
                });
            }
        };
        invokeOnEDT(r);
    }
    void clipboardChanged() {
        Runnable r = new Runnable() {
            public void run() {
                text.setText("");
                lastSelection = "";
            }
        };
        invokeOnEDT(r);
    }
    private class StringSelection
    extends java.awt.datatransfer.StringSelection {
        StringSelection(String s) {
            super(s);
        }
        @Override public void lostOwnership(Clipboard cb, Transferable t) {
            super.lostOwnership(cb, t);
            clipboardChanged();
        }
    }
    void error(final String error) {
        Runnable r = new Runnable() {
            public void run() {
                JOptionPane.showMessageDialog(frame, error);
            }
        };
        invokeOnEDT(r);
    }
    void invokeOnEDT(Runnable r) {
        if (!EventQueue.isDispatchThread())
            EventQueue.invokeLater(r);
        else
            r.run();
    }
}

