package lc.stack.zero.beacon;

import lc.mecha.lang.DangerousRunnable;
import lc.mecha.log.MechaLogger;
import lc.mecha.log.MechaLoggerFactory;
import lc.mecha.util.UniversalJob;
import lc.stack.zero.Beacon;

import java.net.*;
import java.nio.ByteBuffer;
import java.nio.channels.DatagramChannel;
import java.util.Collection;
import java.util.Collections;
import java.util.Enumeration;
import java.util.LinkedList;

/**
 * This class implements a trivial ZDP server which constantly beacons an authoritative packet
 * onto the local network.
 *
 * @author Alex Leigh
 */
public class BeaconDaemon implements DangerousRunnable {
    private static final MechaLogger logger = MechaLoggerFactory.getLogger(BeaconDaemon.class);
    // The current location of the ZAI, if any.
    private final String zaiUrl;

    public BeaconDaemon(String zaiUrl) {
        this.zaiUrl = zaiUrl;
    }

    public static void main(String[] args) throws Exception {
        if (args.length != 1) {
            logger.info("Usage: BeaconDaemon url");
            System.exit(UniversalJob.RET_BADARGS);
        }

        BeaconDaemon srv = new BeaconDaemon(args[0]);
        srv.run();
    }

    @Override
    public void runDangerously() throws Exception {
        ByteBuffer packet = ByteBuffer.allocate(1024);
        Beacon b = new Beacon(true, zaiUrl);
        b.write(packet);
        //noinspection InfiniteLoopStatement
        while (true) {
            for (InetAddress addr : getBroadcastAddresses()) {
                try (DatagramChannel dgc = DatagramChannel.open()) {
                    // dgc.bind(new InetSocketAddress((5200)));
                    dgc.socket().setBroadcast(true);
                    logger.info("Sending beacon... [pkt: {}] [dst: {}]", b, addr);
                    SocketAddress destination = new InetSocketAddress(addr, 5200);
                    packet.flip();
                    dgc.send(packet, destination);
                }
            }
            Thread.sleep(2500);
        }
    }

    @Override
    public void run() {
        try {
            runDangerously();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static Collection<InetAddress> getBroadcastAddresses() {
        try {
            Collection<InetAddress> result = new LinkedList<>();
            Enumeration<NetworkInterface> nets = NetworkInterface.getNetworkInterfaces();
            for (NetworkInterface netint : Collections.list(nets))
                for (InterfaceAddress address : netint.getInterfaceAddresses()) {
                    InetAddress broadcastAddress = address.getBroadcast();
                    if (broadcastAddress != null)
                        result.add(broadcastAddress);
                }
            return result;
        } catch (SocketException e) {
            throw new RuntimeException(e);
        }
    }
}

