package org.ethereum.net.rlpx.discover;

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioDatagramChannel;
import org.ethereum.config.SystemProperties;
import org.ethereum.net.rlpx.Node;
import org.slf4j.LoggerFactory;
import org.spongycastle.util.encoders.Hex;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

@Component
public class UDPListener {
    private static final org.slf4j.Logger logger = LoggerFactory.getLogger("discover");

    private int port;
    private String address;
    private String[] bootPeers;

    @Autowired
    private NodeManager nodeManager;

    @Autowired
    SystemProperties config = SystemProperties.CONFIG;

    public UDPListener() {
    }

    public UDPListener(String address, int port) {
        this.address = address;
        this.port = port;
    }

    @PostConstruct
    void init() {
        this.address = config.bindIp();
        port = config.listenPort();
        if (config.peerDiscovery()) {
            bootPeers = config.peerDiscoveryIPList().toArray(new String[0]);
        }
        if (config.peerDiscovery()) {
            new Thread("UDPListener") {
                @Override
                public void run() {
                    try {
                        UDPListener.this.start(bootPeers);
                    } catch (Exception e) {
                        e.printStackTrace();
                        throw new RuntimeException(e);
                    }
                }
            }.start();
        }
    }

    public static Node parseNode(String s) {
        int idx1 = s.indexOf('@');
        int idx2 = s.indexOf(':');
        String id = s.substring(0, idx1);
        String host = s.substring(idx1 + 1, idx2);
        int port = Integer.parseInt(s.substring(idx2+1));
        return new Node(Hex.decode(id), host, port);
    }

    public void start(String[] args) throws Exception {

        logger.info("Discovery UDPListener started");
        NioEventLoopGroup group = new NioEventLoopGroup(1);

        final List<Node> bootNodes = new ArrayList<>();

        // FIXME: setting nodes from ip.list and attaching node nodeId [f35cc8] constantly
        for (String boot: args) {
            bootNodes.add(new Node("enode://f35cc8a29929c7dc36bd46472d6cc68f104ede7fd42f1749c3533eb33a0fb6b45f2182c009271b83ca7f1d08ea5b1329056caf34c61e8f9e06314e39ec6f80b1" +
                    "@" + boot));
        }

        nodeManager.setBootNodes(bootNodes);


        try {
            DiscoveryExecutor discoveryExecutor = new DiscoveryExecutor(nodeManager);
            discoveryExecutor.start();

            while(true) {
                Bootstrap b = new Bootstrap();
                b.group(group)
                        .channel(NioDatagramChannel.class)
                        .handler(new ChannelInitializer<NioDatagramChannel>() {
                            @Override
                            public void initChannel(NioDatagramChannel ch)
                                    throws Exception {
                                ch.pipeline().addLast(new PacketDecoder());
                                MessageHandler messageHandler = new MessageHandler(ch, nodeManager);
                                nodeManager.setMessageSender(messageHandler);
                                ch.pipeline().addLast(messageHandler);
                            }
                        });

                Channel channel = b.bind(address, port).sync().channel();

                channel.closeFuture().sync();
                logger.warn("UDP channel closed. Recreating after 5 sec pause...");
                Thread.sleep(5000);
            }
        } catch (Exception e) {
            logger.error("{}", e);
        } finally {
            group.shutdownGracefully().sync();
        }
    }

    public static void main(String[] args) throws Exception {
        String address = "0.0.0.0";
        int port = 30303;
        if (args.length >= 2) {
            address = args[0];
            port = Integer.parseInt(args[1]);
        }
        new UDPListener(address, port).start(Arrays.copyOfRange(args, 2, args.length));
    }
}

@@ -1336,32 +1336,169 @@
 rDiscovery()) %7B%0A
+            if (port == 0) %7B%0A                logger.error(%22Discovery can't be started while listen port == 0%22);%0A            %7D else %7B%0A    
             new 
@@ -1529,32 +1529,36 @@
                 
+    
 @Override%0A      
@@ -1559,32 +1559,36 @@
                 
+   
+ 
 public void run(
@@ -1603,38 +1603,46 @@
                 
+    
 try %7B%0A
+    
                 
@@ -1696,32 +1696,36 @@
                 
+   
+ 
 %7D catch (Excepti
@@ -1724,32 +1724,36 @@
 (Exception e) %7B%0A
+    
                 
@@ -1801,24 +1801,28 @@
             
+    
 throw new Ru
@@ -1852,34 +1852,42 @@
                 
+   
+ 
 %7D%0A
+    
                 
@@ -1900,16 +1900,20 @@
         
+    
 %7D.start(
@@ -1911,24 +1911,38 @@
  %7D.start();%0A
+            %7D%0A
         %7D%0A  

Add discover warn if port == 0 (UDP packets can't be received back)