(* $Id$ *)

type transport_layer_id = int
 
type inactive_data =
    { conn_trans : transport_layer_id;
      tls_stashed_endpoint : exn option;
    }

type conn_state = [ `Inactive of inactive_data | `Active of < > ]

type peer =
    [ `Direct of string * int
    | `Direct_name of string * int
    | `Http_proxy of string * int
    | `Http_proxy_connect of (string * int) * (string * int)
    | `Socks5 of (string * int) * (string * int)
    ]

class type connection_cache =
object
  method get_connection_state : Unix.file_descr -> conn_state
  method set_connection_state : Unix.file_descr -> peer -> conn_state -> unit
  method find_inactive_connection : peer -> transport_layer_id -> Unix.file_descr * inactive_data
  method find_my_connections : < > -> Unix.file_descr list
  method close_connection : Unix.file_descr -> unit
  method close_all : unit -> unit
end



class restrictive_cache() : connection_cache =
object(self)
  val mutable active_conns = Hashtbl.create 10
  val mutable rev_active_conns = Hashtbl.create 10

  method get_connection_state fd =
    `Active(Hashtbl.find active_conns fd)

  method set_connection_state fd peer state =
    match state with
      | `Active owner ->
	  Hashtbl.replace active_conns fd owner;
	  let fd_list = 
	    try Hashtbl.find rev_active_conns owner with Not_found -> [] in
	  if not (List.mem fd fd_list) then
	    Hashtbl.replace rev_active_conns owner (fd :: fd_list);
	  
      | `Inactive _ ->
	  self # remove_connection fd;
	  raise Not_found

  method find_inactive_connection _ _ = raise Not_found

  method find_my_connections owner =
    try
      Hashtbl.find rev_active_conns owner
    with
	Not_found -> []

  method private remove_connection fd =
    ( try
	let owner = Hashtbl.find active_conns fd in
	let fd_list = 
	  try Hashtbl.find rev_active_conns owner with Not_found -> [] in
	let fd_list' =
	  List.filter (fun fd' -> fd' <> fd) fd_list in
	Hashtbl.replace rev_active_conns owner fd_list'
      with
	  Not_found -> ()
    );
    Hashtbl.remove active_conns fd;


  method close_connection fd =
    self # remove_connection fd;
    Netlog.Debug.release_fd fd;
    Unix.close fd

  method close_all () =
    Hashtbl.iter
      (fun fd _ ->
	 Netlog.Debug.release_fd fd;
	 Unix.close fd)
      active_conns;
    Hashtbl.clear active_conns;
    Hashtbl.clear rev_active_conns
	  
end


class aggressive_cache () : connection_cache =
object(self)
  val mutable active_conns = Hashtbl.create 10
    (* maps file_descr to owner *)
  val mutable rev_active_conns = Hashtbl.create 10
    (* maps owner to file_descr list *)
  val mutable inactive_conns = Hashtbl.create 10
    (* maps file_descr to (inactive_data,sockaddr) *)
  val mutable rev_inactive_conns = Hashtbl.create 10
    (* maps (trans,sockaddr) to (file_descr * inactive_data) list *)

  method get_connection_state fd =
    try
      `Active(Hashtbl.find active_conns fd)
    with
	Not_found ->
	  let (idata,_) = Hashtbl.find inactive_conns fd in
	  `Inactive idata

  method set_connection_state fd peer state =
    match state with
      | `Active owner ->
	  self # forget_inactive_connection fd;
	  Hashtbl.replace active_conns fd owner;
	  let fd_list = 
	    try Hashtbl.find rev_active_conns owner with Not_found -> [] in
	  if not (List.mem fd fd_list) then
	    Hashtbl.replace rev_active_conns owner (fd :: fd_list);
      | `Inactive idata ->
          let trans = idata.conn_trans in
	  ( try
	      self # forget_active_connection fd;
	      Hashtbl.replace inactive_conns fd (idata,peer);
	      let fd_list =
		try Hashtbl.find rev_inactive_conns (trans,peer)
		with Not_found -> [] in
	      if not (List.mem_assoc fd fd_list) then
		Hashtbl.replace 
                  rev_inactive_conns (trans,peer) ((fd,idata) :: fd_list)
	    with
	      | Unix.Unix_error(Unix.ENOTCONN,_,_) ->
		  self # close_connection fd
	  )

  method find_inactive_connection peer trans =
    match Hashtbl.find rev_inactive_conns (trans,peer) with
      | [] -> raise Not_found
      | (fd,idata) :: _ -> (fd,idata)

  method find_my_connections owner =
    try
      Hashtbl.find rev_active_conns owner
    with
	Not_found -> []

  method private forget_active_connection fd =
    ( try
	let owner = Hashtbl.find active_conns fd in
	let fd_list = 
	  try Hashtbl.find rev_active_conns owner with Not_found -> [] in
	let fd_list' =
	  List.filter (fun fd' -> fd' <> fd) fd_list in
	if fd_list' <> [] then 
	  Hashtbl.replace rev_active_conns owner fd_list'
	else
	  Hashtbl.remove rev_active_conns owner
      with
	  Not_found -> ()
    );
    Hashtbl.remove active_conns fd;
   

  method private forget_inactive_connection fd =
    try
      let idata, peer = Hashtbl.find inactive_conns fd in
      (* Do not use getpeername! fd might be disconnected in the meantime! *)
      let trans = idata.conn_trans in
      let fd_list = 
	try Hashtbl.find rev_inactive_conns (trans,peer) with Not_found -> [] in
      let fd_list' =
	List.filter (fun (fd',_) -> fd' <> fd) fd_list in
      if fd_list' <> [] then 
	Hashtbl.replace rev_inactive_conns (trans,peer) fd_list'
      else
	Hashtbl.remove rev_inactive_conns (trans,peer);
      Hashtbl.remove inactive_conns fd;
    with
      | Not_found ->
	  ()


  method close_connection fd =
    self # forget_active_connection fd;
    self # forget_inactive_connection fd;
    Netlog.Debug.release_fd fd;
    Unix.close fd


  method close_all () =
    Hashtbl.iter
      (fun fd _ ->
	 Netlog.Debug.release_fd fd;
	 Unix.close fd)
      active_conns;
    Hashtbl.clear active_conns;
    Hashtbl.clear rev_active_conns;
    Hashtbl.iter
      (fun fd _ ->
	 Netlog.Debug.release_fd fd;
	 Unix.close fd)
      inactive_conns;
    Hashtbl.clear inactive_conns;
    Hashtbl.clear rev_inactive_conns
end
