(**
http://www.bittorrent.org/beps/bep_0003.html
*)

(* open Prelude *)

let (>>) x f = f x
let fail fmt = Printf.ksprintf failwith fmt

open Printf
open ExtLib

module type Number = sig
  type t
  val neg : t -> t
  val of_int : int -> t
  val to_int : t -> int
  val of_string : string -> t
  val to_string : t -> string
  val zero : t
  val add : t -> t -> t
  val mul : t -> t -> t
end

module Make(N : Number) = struct

type t =
  | S of string
  | I of N.t
  | L of t list
  | D of (string * t) list

let decode_stream ?(hints=[]) chars =
  let ten = N.of_int 10 in
  let digit c = N.of_int (Char.code c - Char.code '0') in
  let rec loop acc = parser
    | [< x=parse_one; t; >] -> loop (x::acc) t
    | [< >] -> List.rev acc
  and parse_one = parser
    | [< s=parse_string >] -> S s
    | [< ''i'; n=parse_int_num; ''e' >] -> I n
    | [< ''l'; l=loop []; ''e' >] -> L l
    | [< ''d'; d=loop_d []; ''e' >] -> D d
  and loop_d acc = parser
    | [< k=parse_string; v=parse_one; t >] -> loop_d ((k,v) :: acc) t
    | [< >] -> List.rev acc
  and parse_string = parser
    | [< n = parse_pos_num; '':'; s = take (N.to_int n) >] -> s
  and parse_int_num = parser
    | [< ''-'; x = parse_pos_num >] -> N.neg x
    | [< x = parse_pos_num >] -> x
  and parse_pos_num = parser
    | [< ''0' >] -> N.zero
    | [< ''1'..'9' as c; n = parse_digits (digit c) >] -> n
  and parse_digits n = parser
    | [< ''0'..'9' as c; t >] -> parse_digits (N.add (N.mul n ten) (digit c)) t
    | [< >] -> n
  and take n chars =
    let s = String.make n '\000' in
    for i = 0 to n-1 do s.[i] <- Stream.next chars done; s
  in
  let main () =
    let r = parse_one chars in
    if not (List.mem `IgnoreTail hints) then Stream.empty chars;
    r
  in
  let show () =
    let tail = Stream.npeek 10 chars >> List.map (String.make 1) >> String.concat "" in
    sprintf "Position %u : %s" (Stream.count chars) tail
  in
  try
    main ()
  with
  | Stream.Error _ | Stream.Failure -> failwith (show ())

let rec print out =
  let pr fmt = IO.printf out fmt in
  function
  | I n -> pr "%s " (N.to_string n)
  | S s -> pr "\"%s\" " (String.slice ~last:10 s)
  | L l -> pr "( "; List.iter (print out) l; pr ") "
  | D d -> pr "{ "; List.iter (fun (k,v) -> pr "%s: " k; print out v) d; pr "} "

let to_string t =
  let module B = Buffer in
  let b = B.create 100 in
  let puts s = bprintf b "%u:%s" (String.length s) s in
  let rec put = function
    | I n -> bprintf b "i%se" (N.to_string n)
    | S s -> puts s
    | L l -> B.add_char b 'l'; List.iter put l; B.add_char b 'e'
    | D d ->
      B.add_char b 'd';
      List.iter (fun (s,x) -> puts s; put x) (List.sort ~cmp:(fun (s1,_) (s2,_) -> compare s1 s2) d);
      B.add_char b 'e'
  in
  put t; B.contents b

(** @raise exn on error *)
let decode s =
  Stream.of_string s >> decode_stream

let key s k v =
  match v with
  | D l -> k (try List.assoc s l with Not_found -> fail "no key '%s'" s)
  | _ -> fail "not a dictionary"

let int = function I n -> n | _ -> fail "int"
let str = function S s -> s | _ -> fail "str"
let list k v = match v with L l -> k l | _ -> fail "list"
let listof k v = match v with L l -> List.map k l | _ -> fail "listof"
let dict k v = match v with D l -> k l | _ -> fail "dict"

end

include Make(Int64)