(ns net.bowmap.core
  (:require [clojure.core.async :refer [<!! >!! chan]]
            [clojure.core.rrb-vector :as v]
            [clojure.pprint :as pp]
            [net.bowmap.codecs :refer [<varint <string <ushort <enum <<]])
  (:import (java.io InputStream)
           (java.net ServerSocket Socket))
  (:gen-class))

(defn packet-splitter-xf
  "A stateful transducer that splits packets using their length.
   The packet's varint length is read, and incoming bytes are buffered until a full packet has arrived."
  [rf]
  (let [buf (volatile! (v/vector-of :byte))]
    (fn
      ([] (rf))
      ([result] (rf result))
      ([result input]
       (vswap! buf v/catvec input)
       (loop [res result]
         (let [[len rst] (<varint @buf)]
           (if (>= (count rst) len)
             (let [pk (v/subvec rst 0 len)]
               (vreset! buf (v/subvec rst len))
               (recur (rf res pk)))
             res)))))))

(defn packet-decoder-xf
  "A stateful transducer that decodes packets.
   Receives pre-split packet bytes, and yields the decoded packet for every packet that comes in, keeping track of client state."
  [rf]
  (let [client-state (volatile! :handshaking)]
    (fn
      ([] (rf))
      ([result] (rf result))
      ([result encoded-pk]
       (let [[pid bs] (<varint encoded-pk)]
         ;; ofc I'll move this out to something other than a switch statement, but you get the idea :p
         (case [@client-state pid]
           ;; Also need to transition the state here
           [:handshaking 0x00] (rf result (<< [:protocol-version <varint
                                               :server-address <string
                                               :server-port <ushort
                                               :intent (<enum <varint {1 :status 2 :login 3 :transfer})]
                                              bs))

           result))))))

(defn read-bytes!
  "Reads bytes as they come in from the client, and puts them onto out-chan as rrb vectors."
  [socket out-chan]
  (let [input-stream (Socket/.getInputStream socket)
        arr (byte-array 4096)]
    (loop []
      (let [n (InputStream/.read input-stream arr)]
        (when (pos? n)
          (>!! out-chan (v/subvec (v/vec arr) 0 n))
          (recur))))))

(defmacro thread [& args]
  `(Thread/startVirtualThread (fn [] ~@args)))

(defn handle [socket]
  (let [xf (comp packet-splitter-xf packet-decoder-xf)
        packet-chan (chan 1024 xf)]
    (thread (read-bytes! socket packet-chan))
    ;; Another transducer could read these and decide what packets to send back to the client :D
    (thread (loop []
              (tap> (<!! packet-chan))
              (recur)))))

(defn -main [& _]
  (add-tap pp/pprint)
  (let [server (ServerSocket. 25565)]
    (tap> "Server Started!")
    (while true
      (handle (ServerSocket/.accept server)))))