(* Ocsigen
 * http://www.ocsigen.org
 * lwt_lib.ml Copyright (C) 2007 Pierre Clairambault
 * Laboratoire PPS - CNRS Université Paris Diderot
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, with linking exceptions;
 * either version 2.1 of the License, or (at your option) any later version.
 * See COPYING file for details.
 *
 * This program 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 Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 *)

open Weak
open Unix
open Lwt

let switch_time = 30.

exception Not_in_table
(* We use a specific Not_in_table exception, because since we're caching
 * threads, we can't for the moment behave differently whether a request
 * is not found in the table or not found as a host.*)


module WeakHashtbl = Make(
  struct
    type t = string * (Unix.host_entry Lwt.t) * float
    let equal = (fun (a,b,c) -> fun (a',b',c') -> a=a')
    let hash = fun (a,b,c) -> Hashtbl.hash a
  end
)

open WeakHashtbl

let keeper : (((string*(Unix.host_entry Lwt.t)*float) list) *
                ((string*(Unix.host_entry Lwt.t)*float) list)) ref = ref ([],[])
let cache = create 0
let dummy_addr : Unix.host_entry =
  {h_name="dummy";
   h_aliases=[||];
   h_addrtype=Unix.PF_INET;
   h_addr_list = [||]}

let cache_find d = try
                        match (find cache (d,return dummy_addr,0.)) with (_,h,t) -> (h,t)
                   with
                       |Not_found -> raise Not_in_table
                       |e -> raise e

let gethostbyname d =
  Lwt.catch
    (fun _ ->
       let (h,t) = cache_find d
       and t' = Unix.time () in
       match (t'>t+.60.) with
         | true ->
             (remove cache) (d,h,t);
             raise_lwt Not_in_table
         | false -> h)
    (function
       | Not_in_table ->
           let t = Unix.time() and
               h = Lwt_preemptive.detach Unix.gethostbyname d in
           let entry =  (d,h,t) in
             add cache entry;
             (match !keeper with (a,b) -> keeper:= (entry::a,b));
             h
       | e -> raise_lwt e)
(* Begin getaddrinfo caching *)


module WeakAddrInfo = Make(
  struct
    type t = string*string*(Unix.getaddrinfo_option list)*((Unix.addr_info list) Lwt.t)*float
    let equal = (fun (h,s,o,i,t) -> fun (h',s',o',i',t') -> (h,s,o)=(h',s',o'))
    let hash = fun (h,s,o,i,t) -> Hashtbl.hash (h,s,o)
  end
)

let keeper6 : (((string*string*(Unix.getaddrinfo_option list)*((Unix.addr_info list) Lwt.t)*float) list) *
             ((string*string*(Unix.getaddrinfo_option list)*((Unix.addr_info list) Lwt.t)*float) list)) ref = ref
             ([],[])

let switch_thread : unit Lwt.t=
  let rec switch_worker () =
    Lwt_unix.sleep switch_time >>= fun () ->
    (match !keeper with (a,b) -> keeper:=([],a));
    (match !keeper6 with (a,b) -> keeper6:=([],a));
    switch_worker ()
  in
  switch_worker()

let cache6 = WeakAddrInfo.create 0

let cache_find6 d s o = try
                                match (WeakAddrInfo.find cache6 (d,s,o,return [],0.)) with (_,_,_,i,t) -> (i,t)
                        with
                                |Not_found -> raise Not_in_table
                                |e -> raise e


let getaddrinfo d s o =
  Lwt.catch
    (fun _ ->
       let (i,t) = cache_find6 d s o
       and t' = Unix.time() in
       match (t'>t+.60.) with
         | true ->
             WeakAddrInfo.remove cache6 (d,s,o,i,t);
             raise_lwt Not_in_table
         | false -> i)
    (function
         | Not_in_table ->
             let t = Unix.time () and
                 i = Lwt_preemptive.detach (Unix.getaddrinfo d s) o in
             let entry = (d,s,o,i,t) in
               WeakAddrInfo.add cache6 entry;
               (match !keeper6 with (a,b) -> keeper6 := (entry::a,b));
               i
         | e -> raise_lwt e)


let getnameinfo s l =
  (*VVV ŕ implémenter !!! *)
  Lwt_preemptive.detach (Unix.getnameinfo s) l
