open Core.Std
open Async.Std
open RedisParser
open RedisLexer
open RedisOp

let store = Hashtbl.create ~hashable:String.hashable ()

let parse (s: string) : op option =
  try Some (op token (Lexing.from_string s)) with _ -> None

let or_else (o: 'a option) (d: 'a) =
  match o with
  | None -> d
  | Some x -> x

let get k w =
  Writer.write_line w (or_else (Hashtbl.find store k) (k ^ " not found"))

let set k v w =
  Hashtbl.replace store ~key:k ~data:v;
  Writer.write_line w "OK"

let append k v' w =
  let v = or_else (Hashtbl.find store k) "" in
  Hashtbl.replace store ~key:k ~data:(v ^ v');
  Writer.write_line w "OK"

let strlen k w =
  match Hashtbl.find store k with
  | None -> Writer.write_line w "-1"
  | Some v -> Writer.write_line w (Int.to_string (String.length v))

let serve _ r w =
  let rec loop r w =
    Reader.read_line r >>= function
    | `Eof -> return ()
    | `Ok s ->
      (match (parse s) with
       | Some (Get k)         -> get k w
       | Some (Set (k, v))    -> set k v w
       | Some (Append (k, v)) -> append k v w
       | Some (Strlen k)      -> strlen k w
       | None                 -> Writer.write_line w ("Error: could not parse " ^ s));
      loop r w
  in

  loop r w

let main () : unit Deferred.t =
  let port = 8080 in
  printf "listening on port %d\n" port;
  let where_to_listen = Tcp.on_port port in
  let _ = Tcp.Server.create where_to_listen serve in
  never ()

let () =
  Command.(run (async ~summary:"" Spec.empty main))