package net.yak.kiwi;

import org.java_websocket.client.WebSocketClient;
import org.java_websocket.handshake.ServerHandshake;

import java.net.URI;
import java.nio.ByteBuffer;
import java.util.concurrent.BlockingDeque;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;

public class KiwiClient extends RunStoppable {
    private RadioHarness harness;
    private RadioHarness.Config config;
    private String hostname;
    private RadioSink sink;
    private Resampler resampler = new Resampler();
    private ImaAdpcmDecoder decoder = new ImaAdpcmDecoder();
    private BlockingQueue<RadioHarness.Config> commands = new LinkedBlockingQueue<>();

    public KiwiClient(RadioHarness harness, RadioHarness.Config config, RadioSink sink) {
        this.harness = harness;
        this.config = config;
        this.hostname = config.server;
        this.sink = sink;
        if (this.hostname.indexOf(':') < 0) {
            this.hostname += ":8073";
        }
    }

    public void addCommand(RadioHarness.Config cfg) {
        commands.add(cfg);
    }

    public void run() {
        long now = System.currentTimeMillis() / 1000;
        MyWebSocketClient cl = null;
        try {
            URI url = new URI("ws://"+this.hostname+"/no_wf/" + now + "/SND");  // SEND
            log.info("KiwiClient connecting to URL: " + url);
            cl = new MyWebSocketClient(url);
            cl.connectBlocking();
            cl.send("SET auth t=kiwi p=xyzzy");
            cl.send("SET AR OK in=12000 out=44100");  // TODO: what does this mean?
            cl.send("SET squelch=0 max=0");
            cl.send("SET lms_autonotch=0");
            cl.send("SET genattn=0");
            cl.send("SET gen=0 mix=-1");
            cl.send("SET ident_user=/android/");

            cl.send(String.format("SET mod=%s low_cut=%d high_cut=%d freq=%.3f",
                    config.mode.name,
                    config.mode.low_cut,
                    config.mode.high_cut,
                    (double)(config.frequency+config.mode.offset) / 1000.0));

            cl.send("SET agc=1 hang=0 thresh=-100 slope=6 decay=1000 manGain=50");
            cl.send(String.format("SET compression=%d", config.compress ? 1 : 0));
            cl.send("SET OVERRIDE inactivity_timeout=0");

            for (int i = 0; i < 3600; i++ ) {
                if (getStopping()) break;
                try {
                    RadioHarness.Config cf = commands.poll(1, TimeUnit.MILLISECONDS);
                    if (cf != null) {
                        cl.send(String.format("SET mod=%s low_cut=%d high_cut=%d freq=%.3f",
                                config.mode.name,
                                config.mode.low_cut,
                                config.mode.high_cut,
                                (double)(config.frequency+config.mode.offset) / 1000.0));
                        config = cf;
                    }

                } catch (InterruptedException ex) {

                }
                cl.send("SET keepalive");
                Thread.sleep(1000);
            }

        } catch (java.lang.Exception ex) {
            log.info("KiwiClient stops on exception: " + ex);
        } finally {
            if (cl != null) {
                try {
                    cl.closeConnection(666, "closeConnection");
                    log.info("closeConnection succeeded.");
                } catch (java.lang.Exception ex) {
                    log.info("closeConnection had exception: " + ex);
                }
            }
        }
    }


    class MyWebSocketClient extends WebSocketClient {

        //private Map<String,String> serverValues = new HashMap<>();

        public MyWebSocketClient(URI url) {
            super(url);
        }

        @Override
        public void onOpen(ServerHandshake handshakedata) {
            System.err.println("WebSocketClient onOpen: status=" + handshakedata.getHttpStatus());
            System.err.println("WebSocketClient onOpen: status_message=" + handshakedata.getHttpStatusMessage());
        }

        @Override
        public void onMessage(String message) {  // TEXT message
            System.err.println("WebSocketClient onMessage TEXT: " + message);
            throw new RuntimeException("unexpected text message");
        }

        private void parseAndSaveServerValues(String s) {
            String[] items = s.split(" ");
            for (String e : items) {
                System.err.println("KV: " + e);
                if (e.contains("=")) {
                    String[] kv = e.split("=");
                    if (kv.length >= 2) {
                        harness.serverValues.put(kv[0], kv[1]);
                    }
                } else {
                    harness.serverValues.put(e, "+");
                }
            }
        }

        @Override
        public void onMessage(ByteBuffer buf) {  // BINARY message
            int n = buf.remaining();
            byte[] bb = new byte[n];
            buf.get(bb);
            if (bb.length < 5) {
                throw new RuntimeException("short message: " + new String(bb));
            }
            String label = new String(bb, 0, 3);
            if (label.equals("MSG")) {
                String argument = new String(bb, 4, bb.length-4);
                parseAndSaveServerValues(argument);
            } else if (label.equals("SND")) {
                doAudio(bb);
            } else {
                log.error("Unknown label: <" + log.curly(label) + "> totlen=" + bb.length);
            }
        }

        private void doAudio(byte[] buf) {
            // First 10 bytes are header:
            //   0..2 : 'SND'
            //   3: flag: (unsigned byte)
            //   4..7: sequence: little-endian signed int32
            //   8..9: smeter: big-endian unsigned uint16
            // Rest are big-endian signed int16 audio samples.
            int n = (buf.length-10) / 2;
            short[] a;
            if (config.compress) {
                a = decoder.decode(buf, 10);
            } else {
                a = new short[n];
                for (int i = 0; i < n; i++) {
                    // 16 bit samples arrive big-endian.
                    int x = ((255 & buf[2 * i + 10]) << 8) | (255 & buf[2 * i + 11]);
                    a[i] = (short) (x);
                }
            }

            a = resampler.crudelyResample(12000, config.rate, a);
            resampler.applyGain(a, config.gain);

            sink.addAudio(a);
        }

        @Override
        public void onClose(int code, String reason, boolean remote) {
            System.err.println("WebSocketClient onClose: " + reason);
            sink.stop();
            KiwiClient.this.stop();
        }

        @Override
        public void onError(Exception ex) {
            System.err.println("WebSocketClient onError: " + ex);
        }
    }
}
