package net.gegy1000.slyther.client.recording;

import java.io.DataInputStream;
import java.io.EOFException;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.lang.Thread.State;
import java.net.BindException;
import java.net.InetSocketAddress;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.ByteBuffer;

import net.gegy1000.slyther.util.Log;
import net.gegy1000.slyther.util.UIUtils;

import org.apache.commons.io.IOUtils;
import org.java_websocket.WebSocket;
import org.java_websocket.handshake.ClientHandshake;
import org.java_websocket.server.WebSocketServer;

public class GameReplayer implements Runnable {
    private WebSocketServer server;

    private File file;

    private Thread thread;

    private FileInputStream fin;

    private DataInputStream din;

    private ByteBuffer messageBuffer = ByteBuffer.allocate(1024);

    private boolean waitingForOpen = true;

    private boolean waitingForClose;

    public GameReplayer(File file) {
        this.file = file;
        server = new WebSocketServer(new InetSocketAddress(8004)) {
            @Override
            public void onOpen(WebSocket conn, ClientHandshake handshake) {
                if (waitingForOpen) {
                    thread = new Thread(GameReplayer.this, "Replayer");
                    thread.start();   
                    waitingForOpen = false;
                } else {
                    Log.warn("Connection was attempted to be made during playback: {}", conn.getRemoteSocketAddress());
                    conn.close();   
                }
            }

            @Override
            public void onClose(WebSocket conn, int code, String reason, boolean remote) {
                waitingForClose = true;
                // Interrupt a long message delay
                while (waitingForClose) {
                    if (thread.getState() == State.TIMED_WAITING) {
                        thread.interrupt();
                    }
                }
            }

            @Override
            public void onMessage(WebSocket conn, String message) {}

            @Override
            public void onError(WebSocket conn, Exception ex) {
                if (ex instanceof BindException) {
                    Log.catching(ex);
                }
            }
        };
        server.start();
    }

    public URI getURI() {
        try {
            InetSocketAddress addr = server.getAddress();
            return new URI("ws://" + addr.getHostString() + ":" + addr.getPort());
        } catch (URISyntaxException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public void run() {
        try {
            fin = new FileInputStream(file);
        } catch (IOException e) {
            UIUtils.displayException("Unable to open recording input file", e);
            return;
        }
        din = new DataInputStream(fin);
        long lastTime = System.currentTimeMillis();
        try {
            while (!waitingForClose) {
                int delta = din.readShort() & 0xFFFF;
                int length = din.readShort() & 0xFFFF;
                if (length > messageBuffer.capacity()) {
                    messageBuffer = ByteBuffer.allocate(length);
                }
                messageBuffer.rewind();
                messageBuffer.limit(length);
                byte[] payload = messageBuffer.array();
                din.read(payload, 0, length);
                long delay = System.currentTimeMillis() - (lastTime + delta);
                if (delay > 100) {
                    Thread.sleep(delay - 100);
                }
                while (System.currentTimeMillis() - (lastTime + delta) < 0);
                lastTime = System.currentTimeMillis();
                server.connections().forEach(conn -> conn.send(messageBuffer));
            }
        } catch (EOFException | InterruptedException e) {
            // Graceful finish
        } catch (Exception e) {
            UIUtils.displayException("A problem occured while playing back recording", e);
        } finally {
            waitingForClose = false;
            IOUtils.closeQuietly(fin);
            try {
                server.stop();
            } catch (IOException | InterruptedException e) {
                Log.error("Exception while stopping server");
                Log.catching(e);
            }
        }
    }
}