open Value
open Ident
open Auto_pat
open Encodings


type t = (Value.t * string) list

let rec print ppf = function
  | [] -> ()
  | (v, t) :: l ->
      print ppf l;
      Format.fprintf ppf 
	"Value @[%a@] does not match type @[%s@]@."
	Value.print v
	t

let print_to_string f x =
  let b = Buffer.create 1024 in
  let ppf = Format.formatter_of_buffer b in
  f ppf x;
  Format.pp_print_flush ppf ();
  Buffer.contents b

let to_string e =
  print_to_string print e

exception Failed of t

let make_result pt d (code,_,_) =
  if d.fail_code == code then raise (Failed pt);
  code

let rec run_disp_basic pt d f =  function
  | [(_,r)] -> make_result pt d r
  | (t,r)::rem -> 
      if f t then make_result pt d r 
      else run_disp_basic pt d f rem
  | _ -> assert false

let rec run_dispatcher pt d v = 
  match d.actions with
    | AIgnore r -> make_result pt d r
    | AKind k -> run_disp_kind pt d k v

and run_disp pt d v =
  run_dispatcher ((v, d.expected_type)::pt) d v

and run_disp_kind pt d actions = function
  | Pair (v1,v2) -> run_disp_prod pt d v1 v2 actions.prod
  | Xml (v1,v2,v3) | XmlNs (v1,v2,v3,_) -> run_disp_prod pt d v1 (Pair(v2,v3)) actions.xml
  | Record r -> run_disp_record pt d 0 r actions.record
  | Atom q -> make_result pt d (Atoms.get_map q actions.atoms) 
  | Char c -> make_result pt d (Chars.get_map c actions.chars) 
  | Integer i ->
      run_disp_basic pt d (fun t -> Types.Int.has_int t i) actions.basic
  | Abstraction (None,_) ->
      run_disp_basic pt d
	(fun t -> failwith "Run-time inspection of external abstraction")
        actions.basic
  | Abstraction (Some iface,_) ->
      run_disp_basic pt d (fun t -> Types.Arrow.check_iface iface t) 
        actions.basic
  | Absent ->
      run_disp_basic pt d (fun t -> Types.Record.has_absent t) actions.basic
  | v ->
      run_disp_kind pt d actions (normalize v)


and run_disp_prod pt d v1 v2 = function
  | Impossible -> assert false
  | TailCall d1 -> run_disp pt d1 v1
  | Ignore d2 -> run_disp_prod2 pt d v2 d2
  | Dispatch (d1,b1) ->
      let code1 = run_disp pt d1 v1 in
      run_disp_prod2 pt d v2 b1.(code1)

and run_disp_prod2 pt d v2 = function
  | Impossible -> assert false
  | Ignore r -> make_result pt d r
  | TailCall d2 -> run_disp pt d2 v2
  | Dispatch (d2,b2) ->
      let code2 = run_disp pt d2 v2 in
      make_result pt d b2.(code2)

and run_disp_record pt d n fields = function
  | None -> assert false
  | Some (RecLabel (l,r)) ->
      (* TODO: get rid of this exception... *)
      (try run_disp_record1 pt d l (succ n) 
	 (Imap.find fields (Upool.int l)) fields r
       with Not_found -> 
	 run_disp_record1 pt d l n Absent fields r)
  | Some (RecNolabel (some,none)) ->
      let r = if (n < Imap.cardinal fields) then some else none in
      match r with
	| Some r -> make_result pt d r
	| None -> assert false
      
and run_disp_record1 pt d l n v1 rem = function
  | Impossible -> assert false
  | TailCall d1 -> run_disp pt d1 v1
  | Ignore d2 ->  run_disp_record2 pt d n rem d2
  | Dispatch (d1,b1) ->
      let code1 = run_disp pt d1 v1 in
      run_disp_record2 pt d n rem b1.(code1)

and run_disp_record2 pt d n rem = function
  | Impossible -> assert false
  | Ignore r -> make_result pt d r
  | TailCall d2 -> run_disp_record_loop pt n rem d2
  | Dispatch (d2,b2) ->
      let code2 = run_disp_record_loop pt n rem d2 in
      make_result pt d b2.(code2)

and run_disp_record_loop pt n rem d =
  match d.actions with
    | AIgnore r -> make_result pt d r
    | AKind k -> run_disp_record pt d n rem k.record

let is_xml = function ((Xml _ | XmlNs _),_) -> true | _ -> false

let rec simplify = function
  | (Absent, _) :: l -> simplify l
  | x :: l -> (try [ x; List.find is_xml l ] with Not_found -> [ x ])
  | [] -> assert false

let check d v =
  if (d.fail_code < 0) then ()
  else
    let (code,_) = Run_dispatch.run_dispatcher d v in
    if code == d.fail_code then (ignore (run_disp [] d v); assert false)

let explain d v =
  try  check d v; None
  with Failed p -> Some p

let do_check d v =
  try check d v; v
  with Failed p ->
    let p = simplify p in
    let s = print_to_string print p in
    raise (CDuceExn (string_latin1 s))

let check_failure d v =
  try check d v; v
  with Failed p ->
    let p = simplify p in
    let s = print_to_string print p in
    failwith s

