(* Copyright 2012 Tony Garnock-Jones <tonygarnockjones@gmail.com>. *)

(* This file is part of Hop. *)

(* Hop is free software: you can redistribute it and/or modify it *)
(* under the terms of the GNU General Public License as published by the *)
(* Free Software Foundation, either version 3 of the License, or (at your *)
(* option) any later version. *)

(* Hop 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 *)
(* General Public License for more details. *)

(* You should have received a copy of the GNU General Public License *)
(* along with Hop.  If not, see <http://www.gnu.org/licenses/>. *)

open Lwt
open Sexp
open Printf

let message_not_understood context m =
  Log.warn "Message not understood" [Str context; Message.sexp_of_message m]

let create_thread name cleanup main initarg =
  let guarded_main initarg =
    try_lwt
      main initarg
    with e ->
      lwt () = Log.warn "Thread died with exception" [Str name; Str (Printexc.to_string e)] in
      (match cleanup with
      | Some cleaner -> cleaner e
      | None -> return ())
  in
  guarded_main initarg

let daemon_thread_died name nested_cleaner e =
  lwt () = (match nested_cleaner with
    | Some c -> c e
    | None -> return ()) in
  Server_control.shutdown_now [Sexp.Str "Daemon thread exited"; Sexp.Str name];
  return ()

let create_daemon_thread name cleanup main initarg =
  create_thread name (Some (daemon_thread_died name cleanup)) main initarg

let starts_with s1 s2 =
  try Str.first_chars s1 (String.length s2) = s2 with _ -> false

let ends_with s1 s2 =
  try Str.last_chars s1 (String.length s2) = s2 with _ -> false

let strip s =
  let len = String.length s in
  let ws i = Char.code (String.get s i) <= 32 in
  let rec left index = if index < len && ws index then left (index + 1) else index in
  let rec right index = if index >= 0 && ws index then right (index - 1) else index in
  let l = left 0 in
  let r = 1 + right (len - 1) in
  if r <= l then "" else String.sub s l (r - l)

let strsub replacement_fn s =
  let len = String.length s in
  let finish_span low high acc = String.sub s low (high - low) :: acc in
  let finish acc = String.concat "" (List.rev acc) in
  let rec outer_loop acc low =
    let rec inner_loop high =
      if high = len
      then finish (finish_span low high acc)
      else
	match replacement_fn (String.get s high) with
	| Some handler ->
	    let (replacement, new_low) = handler (s, high) in
	    outer_loop (replacement :: finish_span low high acc) new_low
	| None ->
	    inner_loop (high + 1)
    in inner_loop low
  in outer_loop [] 0

let unhex_char c =
  match c with
  | '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' -> Char.code c - Char.code '0'
  | 'a' | 'b' | 'c' | 'd' | 'e' | 'f' -> Char.code c - Char.code 'a' + 10
  | 'A' | 'B' | 'C' | 'D' | 'E' | 'F' -> Char.code c - Char.code 'A' + 10
  | _ -> -1

let unhex s =
  let len = String.length s in
  let rec loop index acc =
    if index = len
    then acc
    else loop (index + 1) (acc * 16 + unhex_char (String.get s index))
  in
  loop 0 0

let stream_to_string s =
  lwt pieces = Lwt_stream.to_list s in
  return (String.concat "" pieces)

let stream_generator f =
  let mbox = Lwt_mvar.create_empty () in
  let yield v = Lwt_mvar.put mbox (Some v) in
  ignore (lwt () = f yield in
	  Lwt_mvar.put mbox None);
  Lwt_stream.from (fun () -> Lwt_mvar.take mbox)

let file_contents filename =
  let ch = open_in filename in
  let len = in_channel_length ch in
  let buf = String.make len ' ' in
  really_input ch buf 0 len;
  close_in ch;
  buf

let rec split_at xs n =
  match n with
    | 0 -> ([], xs)
    | _ ->
      match xs with
	| [] -> raise (Failure "Util.split_at")
	| x :: xs' ->
	  let (hs, ts) = split_at xs' (n - 1) in
	  (x :: hs, ts)

let rec list_make n v =
  match n with
    | 0 -> []
    | _ -> v :: list_make (n - 1) v

let rec split_at_fill v xs n =
  match n with
    | 0 -> ([], xs)
    | _ ->
      match xs with
	| [] ->
	  (list_make n v, [])
	| x :: xs' ->
	  let (hs, ts) = split_at_fill v xs' (n - 1) in
	  (x :: hs, ts)
