(* Copyright 2012 Tony Garnock-Jones <tonygarnockjones@gmail.com>. *)

(* This file is part of Hop. *)

(* Hop is free software: you can redistribute it and/or modify it *)
(* under the terms of the GNU General Public License as published by the *)
(* Free Software Foundation, either version 3 of the License, or (at your *)
(* option) any later version. *)

(* Hop is distributed in the hope that it will be useful, but *)
(* WITHOUT ANY WARRANTY; without even the implied warranty of *)
(* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU *)
(* General Public License for more details. *)

(* You should have received a copy of the GNU General Public License *)
(* along with Hop.  If not, see <http://www.gnu.org/licenses/>. *)

open Lwt
open Unix
open Printf
open Sexp

let send_error ch message details =
  let m = Message.error (Str message, details) in
  ignore (Log.warn "Sending error" [m]);
  ch m

let send_sexp_syntax_error ch explanation =
  send_error ch explanation (Str "http://people.csail.mit.edu/rivest/Sexp.txt")

let dispatch_message n ch =
  let lookup = Node.caching_name_of_string () in
  function
    | Message.Post (Str name, body, token) ->
      Node.send_ignore (lookup name) body
    | Message.Subscribe (Str filter, sink, name, Str reply_sink, Str reply_name) ->
      (match_lwt Node.bind (Node.name_of_string filter, n) with
	| true ->
	  Node.post_ignore'
	    reply_sink
	    (Str reply_name)
	    (Message.subscribe_ok (Str filter))
	    (Str "")
	| false ->
	  Log.warn "Bind failed" [Str filter])
    | Message.Unsubscribe (Str token) ->
      (match_lwt Node.unbind (Node.name_of_string token) with
	| true -> return ()
	| false -> Log.warn "Unbind failed" [Str token])
    | m ->
      send_error ch "Message not understood" (Message.sexp_of_message m)

let issue_banner cin cout =
  lwt () = output_sexp cout (Arr [Str "hop"; Str ""]) in
  lwt () = Lwt_io.flush cout in
  lwt () = output_sexp cout (Message.subscribe (Str (Node.local_container_name()),
						Str "", Str "",
						Str "", Str "")) in
  lwt () = Lwt_io.flush cout in
  return true

let relay_boot (peername, cin, cout) = return (peername, Lwt_mutex.create (), cin, cout)

let relay_handler (_, mtx, _, cout) _ m =
  Lwt_mutex.with_lock mtx (fun () -> output_sexp cout m)

let relay_mainloop (peername, mtx, cin, cout) n =
  let write_sexp sexp = Lwt_mutex.with_lock mtx (fun () -> output_sexp cout sexp) in
  (try_lwt
     let dispatcher = dispatch_message n write_sexp in
     while_lwt true do
       lwt message_sexp = Sexp.input_sexp cin in
       dispatcher (Message.message_of_sexp message_sexp)
     done
   with
     | Sexp.Syntax_error explanation ->
       lwt () = send_sexp_syntax_error write_sexp explanation in
       Log.info "Disconnected relay for syntax error"
	 [Str (Connections.endpoint_name peername); Str explanation])

let start (s, peername) =
  Connections.start_connection "relay" issue_banner
    relay_boot relay_handler relay_mainloop (s, peername)

let init () =
  let port = Config.get_int "hop.port" 5671 in
  Util.create_daemon_thread "Hop listener" None (Net.start_net "Hop" port) start
