package net.yeputons.spbau.spring2016.torrent;

import net.yeputons.spbau.spring2016.torrent.protocol.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.*;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.nio.file.Path;
import java.util.*;

public class TrackerServer {
    public static final int DEFAULT_PORT = 8081;
    private static final Logger LOG = LoggerFactory.getLogger(TrackerServer.class);

    private static class State implements Serializable {
        private static final long serialVersionUID = 1L;

        private final List<FileEntry> files = new ArrayList<>();
        private int freeFileId = 1;
    }

    private final StateHolder<State> stateHolder;
    private final SeedersTracker seeders = new SeedersTracker(60 * 1000);

    private ServerSocket server;
    private Thread thread;
    private final int port;
    private boolean shuttedDown = false;

    public TrackerServer() {
        this(DEFAULT_PORT, null);
    }
    public TrackerServer(int port) {
        this(port, null);
    }
    public TrackerServer(Path storage) {
        this(DEFAULT_PORT, storage);
    }
    public TrackerServer(int port, Path storage) {
        this.port = port;
        if (storage != null) {
            stateHolder = new StateFileHolder<State>(storage, new State());
        } else {
            stateHolder = new StateMemoryHolder<State>(new State());
        }
    }

    public void start() throws IOException {
        synchronized (this) {
            if (thread != null || server != null) {
                throw new IllegalStateException("Already started");
            }
            if (shuttedDown) {
                return;
            }
            server = new ServerSocket();
            server.bind(new InetSocketAddress("0.0.0.0", port));
        }
        thread = new Thread(() -> {
            while (!Thread.interrupted()) {
                final Socket client;
                try {
                    client = server.accept();
                } catch (IOException ignored) {
                    break;
                }
                new Thread(() -> {
                    try (SocketDataStreamsWrapper wrapper = new SocketDataStreamsWrapper(client)) {
                        final DataInputStream in = wrapper.getInputStream();
                        final DataOutputStream out = wrapper.getOutputStream();
                        for (;;) {
                            ServerRequest<?> request;
                            try {
                                request = ServerRequest.readRequest(in);
                            } catch (NoRequestException ignored) {
                                break;
                            }
                            request.visit(new ServerRequestVisitor() {
                                @Override
                                public void accept(ListRequest r) throws IOException {
                                    State s = stateHolder.getState();
                                    synchronized (s) {
                                        r.answerTo(out, s.files);
                                    }
                                }

                                @Override
                                public void accept(UploadRequest r) throws IOException {
                                    int id;
                                    State s = stateHolder.getState();
                                    synchronized (s) {
                                        id = s.freeFileId;
                                        s.freeFileId++;
                                        s.files.add(new FileEntry(id, r.getFileName(), r.getSize()));
                                        stateHolder.save();
                                    }
                                    r.answerTo(out, id);
                                }

                                @Override
                                public void accept(SourcesRequest r) throws IOException {
                                    r.answerTo(out, seeders.getSources(r.getFileId()));
                                }

                                @Override
                                public void accept(UpdateRequest r) throws IOException {
                                    InetSocketAddress address =
                                            new InetSocketAddress(client.getInetAddress(), r.getSeedPort());
                                    for (int fileId : r.getSeedingFiles()) {
                                        seeders.updateSource(fileId, address);
                                    }
                                    r.answerTo(out, true);
                                }
                            });
                        }
                    } catch (IOException e) {
                        LOG.warn("Error while processing client request", e);
                    }
                }).start();
            }
        });
        thread.start();
    }

    public void shutdown() throws IOException {
        synchronized (this) {
            if (server != null) {
                server.close();
            }
            shuttedDown = true;
        }
    }

    public void join() throws InterruptedException {
        if (thread != null) {
            thread.join();
        }
    }
}

@@ -1847,32 +1847,106 @@
 rt));%0A        %7D%0A
+        LOG.info(%22Started server on %7B%7D%22, server.getLocalSocketAddress());%0A
         thread =
@@ -2199,32 +2199,113 @@
               %7D%0A
+                LOG.info(%22New client from %7B%7D%22, client.getRemoteSocketAddress());%0A
                 
@@ -2904,32 +2904,104 @@
               %7D%0A
+                            LOG.debug(%22Incoming request: %7B%7D%22, request);%0A
                 
@@ -3301,32 +3301,113 @@
 chronized (s) %7B%0A
+                                        LOG.debug(%22Answering with %7B%7D%22, s.files);%0A
                 
@@ -4136,32 +4136,104 @@
               %7D%0A
+                                    LOG.debug(%22Answering with %7B%7D%22, id);%0A
                 
@@ -4476,31 +4476,48 @@
         
-r.answerTo(out,
+List%3CInetSocketAddress%3E answer =
  seeders
@@ -4542,16 +4542,152 @@
 ileId())
+;%0A                                    LOG.debug(%22Answering with %7B%7D%22, answer);%0A                                    r.answerTo(out, answer
 );%0A     
@@ -5206,32 +5206,106 @@
               %7D%0A
+                                    LOG.debug(%22Answering with %7B%7D%22, true);%0A
                 
@@ -5605,33 +5605,170 @@
     
-%7D).start();%0A            %7D
+    LOG.info(%22Client disconnected%22);%0A                %7D).start();%0A            %7D%0A            LOG.info(%22Server on %7B%7D is terminated%22, server.getLocalSocketAddress());
 %0A   
@@ -5847,32 +5847,112 @@
 s IOException %7B%0A
+        LOG.info(%22Shutting down server on %7B%7D%22, server.getLocalSocketAddress());%0A
         synchron

add some logging