package com.knutejohnson.pi;

import java.awt.*;
import java.awt.event.*;
import java.io.*;
import java.util.*;
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 =
     Collections.synchronizedList(new ArrayList<>());

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

    /**
     * Create a new ARP component and initialize its font and size
     *
     * @param   comp    the parent component
     */
    public ARP(Component comp) {
        window = comp instanceof Window ? (Window)comp : null;
        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) {
            try {
                Process proc = pb.start();
                try (BufferedReader 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());
                repaint();
            }
        }
    }

    /**
     * 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();

        synchronized (lines) {
            for (String line : lines) {
                g.drawString(line,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((s1,s2) -> s1.length() - s2.length());
        if (opt.isPresent()) {
            Dimension newSize = new Dimension(
             fm.stringWidth(opt.get()) + 20,fm.getHeight()*lines.size() + 10);
            if (!newSize.equals(getSize())) {
                setPreferredSize(newSize);
                if (window != null)
                    window.pack();
            }
        }
    }

    /**
     * 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();
        });
    }
}