(***********************************************************************)
(*                                                                     *)
(*                 MLTk, Tcl/Tk interface of OCaml                     *)
(*                                                                     *)
(*    Francois Rouaix, Francois Pessaux, Jun Furuse and Pierre Weis    *)
(*               projet Cristal, INRIA Rocquencourt                    *)
(*            Jacques Garrigue, Kyoto University RIMS                  *)
(*                                                                     *)
(*  Copyright 2002 Institut National de Recherche en Informatique et   *)
(*  en Automatique and Kyoto University.  All rights reserved.         *)
(*  This file is distributed under the terms of the GNU Library        *)
(*  General Public License, with the special exception on linking      *)
(*  described in file LICENSE found in the OCaml source tree.          *)
(*                                                                     *)
(***********************************************************************)

(* $Id$ *)

open Protocol

external internal_tracevar : string -> cbid -> unit
        = "camltk_trace_var"
external internal_untracevar : string -> cbid -> unit
        = "camltk_untrace_var"
external set : string -> string -> unit = "camltk_setvar"
external get : string -> string = "camltk_getvar"


type textVariable = string

(* List of handles *)
let handles = Hashtbl.create 401

let add_handle var cbid =
  try
    let r = Hashtbl.find handles var in
    r := cbid :: !r
  with
    Not_found ->
      Hashtbl.add handles var (ref [cbid])

let exceptq x =
  let rec ex acc = function
     [] -> acc
   | y::l when y == x -> ex acc l
   | y::l -> ex (y::acc) l
  in
  ex []

let rem_handle var cbid =
  try
    let r = Hashtbl.find handles var in
    match exceptq cbid !r with
      [] -> Hashtbl.remove handles var
    | remaining -> r := remaining
  with
    Not_found -> ()

(* Used when we "free" the variable (otherwise, old handlers would apply to
 * new usage of the variable)
 *)
let rem_all_handles var =
  try
    let r = Hashtbl.find handles var in
    List.iter (internal_untracevar var) !r;
    Hashtbl.remove handles var
  with
    Not_found -> ()


(* Variable trace *)
let handle vname ~callback:f =
  let id = new_function_id() in
  let wrapped _ =
    clear_callback id;
    rem_handle vname id;
    f() in
  Hashtbl.add callback_naming_table id wrapped;
  add_handle vname id;
  if !Protocol.debug then begin
    prerr_cbid id; prerr_string " for variable "; prerr_endline vname
  end;
  internal_tracevar vname id

(* Avoid space leak (all variables are global in Tcl) *)
module StringSet =
  Set.Make(struct type t = string let compare = compare end)
let freelist = ref (StringSet.empty)
let memo = Hashtbl.create 101

(* Added a variable v referenced by widget w *)
let add w v =
  let w = Widget.forget_type w in
  let r =
    try Hashtbl.find memo w
    with
      Not_found ->
        let r = ref StringSet.empty in
          Hashtbl.add memo w r;
          r in
   r := StringSet.add v !r

(* to be used with care ! *)
let free v =
  rem_all_handles v;
  freelist := StringSet.add v !freelist

(* Free variables associated with a widget *)
let freew w =
  try
    let r = Hashtbl.find memo w in
    StringSet.iter free !r;
    Hashtbl.remove memo w
  with
    Not_found -> ()

let _ = add_destroy_hook freew

(* Allocate a new variable *)
let counter = ref 0
let getv () =
  let v =
    if StringSet.is_empty !freelist then begin
      incr counter;
      "camlv("^ string_of_int !counter ^")"
      end
    else
      let v = StringSet.choose !freelist in
        freelist := StringSet.remove v !freelist;
        v in
    set v "";
    v

let create ?on: w () =
  let v = getv() in
  begin
  match w with
     Some w -> add w v
   | None -> ()
  end;
  v

(* to be used with care ! *)
let free v =
  freelist := StringSet.add v !freelist

let cCAMLtoTKtextVariable s = TkToken s

let name s = s
let coerce s = s
