package com.knutejohnson.pi;

import java.awt.*;
import java.awt.event.*;
import java.io.*;
import java.util.*;
import java.util.concurrent.*;
import static java.util.stream.Collectors.*;
import javax.swing.*;

/**
 * Java GUI program to display the results of the arp command.
 */
public class ARP extends JPanel implements Runnable {
    /** Main thread */
    private volatile Thread thread;

    /** Main thread run flag */
    private volatile boolean runFlag;

    /** List to hold lines returned from the arp command */
    private final java.util.List<String> lines = new ArrayList<>();

    /** Parent window of this component */
    private Window window;

    /**
     * Create a new ARP component and initialize its font and size
     *
     * @param   comp    the parent component
     */
    public ARP(Component comp) {
        if (comp instanceof Window)
            window = (Window)comp;
        setFont(new Font(Font.MONOSPACED,Font.PLAIN,12));
        setPreferredSize(new Dimension(600,250));
    }

    /**
     * If the main thread is not already running, sets the run flag, creates a
     * new main thread and starts it running
     */
    public void start() {
        if (!runFlag) {
            runFlag = true;
            thread = new Thread(this,"ARP");
            thread.start();
        }
    }

    /**
     * Method executed by main thread, creates a new process to call the arp
     * command, executes it, reads the data returned and stores it in the lines
     * list.
     */
    @Override public void run() {
        boolean WIN =
         System.getProperty("os.name").toLowerCase().contains("windows");
        String[] command =
         WIN ? new String[] {"arp","-a"} : new String[] {"arp"};
        ProcessBuilder pb = new ProcessBuilder(command);
        while (runFlag) {
            BufferedReader br = null;
            try {
                Process proc = pb.start();
                br = new BufferedReader(new InputStreamReader(
                 proc.getInputStream()));
                lines.clear();
                lines.addAll(br.lines().collect(toList()));
                updatePreferredSize();
                repaint();

                Thread.sleep(10000);
            } catch (IOException|InterruptedException ex) {
                lines.clear();
                lines.add(ex.toString());
            } finally {
                if (br != null)
                    try {
                        br.close();
                    } catch (IOException ioe) {
                        ioe.printStackTrace();
                    }
            }
        }
    }

    /**
     * Clears the run flag and interrupts the main thread
     */
    public void stop() {
        runFlag = false;
        thread.interrupt();
    }

    /**
     * Draws the data returned from the arp command on the component.
     *
     * @param   g2D the Graphics context
     */
    @Override public void paintComponent(Graphics g2D) {
        Graphics2D g = (Graphics2D)g2D;

        g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
         RenderingHints.VALUE_ANTIALIAS_ON);

        g.setColor(Color.WHITE);
        g.fillRect(0,0,getWidth(),getHeight());

        g.setColor(Color.BLACK);
        FontMetrics fm = g.getFontMetrics();
        int x = 10;
        int y = fm.getHeight();

        for (int i=0; i<lines.size(); i++) {
            g.drawString(lines.get(i),x,y);
            y += fm.getHeight();
        }
    }

    /**
     * Calculates and sets the preferred size of the ARP component by comparing
     * the length of the lines and the number of lines returned from the arp
     * command, updates the preferred size of the component and calls pack() on
     * the parent component if it is a Window
     */
    private void updatePreferredSize() {
        FontMetrics fm = getGraphics().getFontMetrics();
        Optional<String> opt = lines.stream().max(new StringLengthComparator());
        if (opt.isPresent()) {
            Dimension newSize = new Dimension(
             fm.stringWidth(opt.get()) + 20,fm.getHeight()*lines.size() + 10);
            if (!newSize.equals(getPreferredSize())) {
                setPreferredSize(newSize);
                if (window != null)
                    window.pack();
            }
        }
    }

    /**
     * String length comparator.
     */
    private class StringLengthComparator implements Comparator<String> {
        /**
         * Compares two Strings by their length
         *
         * @param   o1  the first String to compare
         * @param   o2  the second String to compare
         *
         * @return  a negative integer, zero, or a positive integer if the
         *          first argument is less than, equal to, or greater than the
         *          second.
         */
        @Override public int compare(String o1, String o2) {
            return o1.length() - o2.length();
        }
    }

    /**
     * Program entry point, creates a new ARP component, a JFrame to hold it
     * and starts the ARP running.
     *
     * @param   args    command line arguments - not used
     */
    public static void main(String... args) {
        EventQueue.invokeLater(() -> {
            JFrame f = new JFrame("ARP");
            f.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
            ARP arp = new ARP(f);
            f.addWindowListener(new WindowAdapter() {
                public void windowClosing(WindowEvent we) {
                    arp.stop();
                }
            });
            f.add(arp,BorderLayout.CENTER);
            f.pack();
            f.setLocationRelativeTo(null);
            f.setVisible(true);
            arp.start();
        });
    }
}