(*****************************************************************************)
(*                                                                           *)
(* Open Source License                                                       *)
(* Copyright (c) 2018 Dynamic Ledger Solutions, Inc. <contact@tezos.com>     *)
(*                                                                           *)
(* Permission is hereby granted, free of charge, to any person obtaining a   *)
(* copy of this software and associated documentation files (the "Software"),*)
(* to deal in the Software without restriction, including without limitation *)
(* the rights to use, copy, modify, merge, publish, distribute, sublicense,  *)
(* and/or sell copies of the Software, and to permit persons to whom the     *)
(* Software is furnished to do so, subject to the following conditions:      *)
(*                                                                           *)
(* The above copyright notice and this permission notice shall be included   *)
(* in all copies or substantial portions of the Software.                    *)
(*                                                                           *)
(* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR*)
(* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,  *)
(* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL   *)
(* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER*)
(* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING   *)
(* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER       *)
(* DEALINGS IN THE SOFTWARE.                                                 *)
(*                                                                           *)
(*****************************************************************************)

open Store_sigs

module Make_value (V : ENCODED_VALUE) = struct
  type t = V.t
  let of_bytes b =
    match Data_encoding.Binary.of_bytes V.encoding b with
    | None -> generic_error "Cannot parse data" (* TODO personalize *)
    | Some v -> ok v
  let to_bytes v =
    try Data_encoding.Binary.to_bytes_exn V.encoding v
    with Data_encoding.Binary.Write_error error ->
      Store_logging.log_error
        "Exception while serializing value %a"
        Data_encoding.Binary.pp_write_error error ;
      MBytes.create 0
end

module Raw_value = struct
  type t = MBytes.t
  let of_bytes b = ok b
  let to_bytes b = b
end

module Make_single_store (S : STORE) (N : NAME) (V : VALUE) = struct
  type t = S.t
  type value = V.t
  let known t = S.known t N.name
  let read t = S.read t N.name >>=? fun b -> Lwt.return (V.of_bytes b)
  let read_opt t =
    read t >|= function
    | Error _ -> None
    | Ok v -> Some v
  let read_exn t =
    read t >>= function
    | Error _ -> Lwt.fail Not_found
    | Ok v -> Lwt.return v
  let store t v = S.store t N.name (V.to_bytes v)
  let remove t = S.remove t N.name
end

let map_key f = function
  |`Key k -> `Key (f k)
  | `Dir k -> `Dir (f k)

module Make_substore (S : STORE) (N : NAME)
  : STORE with type t = S.t = struct
  type t = S.t
  type key = string list
  type value = MBytes.t
  let name_length = List.length N.name
  let to_key k = N.name @ k
  let of_key k = List.remove name_length k
  let known t k = S.known t (to_key k)
  let known_dir t k = S.known_dir t (to_key k)
  let read t k = S.read t (to_key k)
  let read_opt t k = S.read_opt t (to_key k)
  let read_exn t k = S.read_exn t (to_key k)
  let store t k v = S.store t (to_key k) v
  let remove t k = S.remove t (to_key k)
  let fold t k ~init ~f =
    S.fold t (to_key k) ~init
      ~f:(fun k acc -> f (map_key of_key k) acc)
  let keys t k = S.keys t (to_key k) >|= fun keys -> List.map of_key keys
  let fold_keys t k ~init ~f =
    S.fold_keys t (to_key k) ~init ~f:(fun k acc -> f (of_key k) acc)
  let remove_dir t k = S.remove_dir t (to_key k)
end

module Make_indexed_substore (S : STORE) (I : INDEX) = struct

  type t = S.t
  type key = I.t

  module Store = struct
    type t = S.t * I.t
    type key = string list
    type value = MBytes.t
    let to_key i k =
      assert (List.length (I.to_path i []) = I.path_length) ;
      I.to_path i k
    let of_key k = List.remove I.path_length k
    let known (t,i) k = S.known t (to_key i k)
    let known_dir (t,i) k = S.known_dir t (to_key i k)
    let read (t,i) k = S.read t (to_key i k)
    let read_opt (t,i) k = S.read_opt t (to_key i k)
    let read_exn (t,i) k = S.read_exn t (to_key i k)
    let store (t,i) k v = S.store t (to_key i k) v
    let remove (t,i) k = S.remove t (to_key i k)
    let fold (t,i) k ~init ~f =
      S.fold t (to_key i k) ~init
        ~f:(fun k acc -> f (map_key of_key k) acc)
    let keys (t,i) k = S.keys t (to_key i k) >|= fun keys -> List.map of_key keys
    let fold_keys (t,i) k ~init ~f =
      S.fold_keys t (to_key i k) ~init ~f:(fun k acc -> f (of_key k) acc)
    let remove_dir (t,i) k = S.remove_dir t (to_key i k)
  end

  let remove_all t i = Store.remove_dir (t, i) []

  let fold_indexes t ~init ~f =
    let rec dig i path acc =
      if i <= 0 then
        match I.of_path path with
        | None -> assert false
        | Some path -> f path acc
      else
        S.fold t path ~init:acc ~f:begin fun k acc ->
          match k with
          | `Dir k -> dig (i-1) k acc
          | `Key _ -> Lwt.return acc
        end in
    dig I.path_length [] init

  let indexes t =
    fold_indexes t ~init:[] ~f:(fun i acc -> Lwt.return (i :: acc))

  let list t k = S.fold t k ~init:[] ~f:(fun k acc -> Lwt.return (k :: acc))
  let resolve_index t prefix =
    let rec loop i prefix = function
      | [] when i = I.path_length -> begin
          match I.of_path prefix with
          | None -> assert false
          | Some path -> Lwt.return [path]
        end
      | [] ->
          list t prefix >>= fun prefixes ->
          Lwt_list.map_p (function
              | `Key prefix | `Dir prefix -> loop (i+1) prefix []) prefixes
          >|= List.flatten
      | [d] when i = I.path_length - 1 ->
          if (i >= I.path_length) then invalid_arg "IO.resolve" ;
          list t prefix >>= fun prefixes ->
          Lwt_list.map_p (function
              | `Key prefix | `Dir prefix ->
                  match String.remove_prefix ~prefix:d (List.hd (List.rev prefix)) with
                  | None -> Lwt.return_nil
                  | Some _ -> loop (i+1) prefix [])
            prefixes
          >|= List.flatten
      | "" :: ds ->
          list t prefix >>= fun prefixes ->
          Lwt_list.map_p (function
              | `Key prefix | `Dir prefix -> loop (i+1) prefix ds) prefixes
          >|= List.flatten
      | d :: ds ->
          if (i >= I.path_length) then invalid_arg "IO.resolve" ;
          S.known_dir t (prefix @ [d]) >>= function
          | true -> loop (i+1) (prefix @ [d]) ds
          | false -> Lwt.return_nil in
    loop 0 [] prefix

  module Make_set (N : NAME) = struct
    type t = S.t
    type elt = I.t
    let inited = MBytes.of_string "inited"
    let known s i = Store.known (s, i) N.name
    let store s i = Store.store (s, i) N.name inited
    let remove s i = Store.remove (s, i) N.name
    let remove_all s =
      fold_indexes s ~init:() ~f:(fun i () -> remove s i)
    let fold s ~init ~f =
      fold_indexes s ~init
        ~f:(fun i acc ->
            known s i >>= function
            | true -> f i acc
            | false -> Lwt.return acc)
    let elements s =
      fold s ~init:[] ~f:(fun p acc -> Lwt.return (p :: acc))
    let iter s ~f =
      fold s ~init:() ~f:(fun p () -> f p)
  end

  module Make_buffered_set (N : NAME) (Set : Set.S with type elt = I.t) = struct
    include Make_set (N)
    module Set = Set
    let read_all s =
      fold s ~init:Set.empty ~f:(fun i set -> Lwt.return (Set.add i set))
    let store_all s new_set =
      read_all s >>= fun old_set ->
      Lwt_list.iter_p (remove s)
        Set.(elements (diff old_set new_set)) >>= fun () ->
      Lwt_list.iter_p (store s) Set.(elements (diff new_set old_set))
  end

  module Make_map (N : NAME) (V : VALUE) = struct
    type t = S.t
    type key = I.t
    type value = V.t
    let known s i = Store.known (s,i) N.name
    let read s i =
      Store.read (s,i) N.name >>=? fun b -> Lwt.return (V.of_bytes b)
    let read_opt s i =
      read s i >>= function
      | Error _ -> Lwt.return_none
      | Ok v -> Lwt.return_some v
    let read_exn s i =
      read s i >>= function
      | Error _ -> Lwt.fail Not_found
      | Ok v -> Lwt.return v
    let store s i v = Store.store (s,i) N.name (V.to_bytes v)
    let remove s i = Store.remove (s,i) N.name
    let remove_all s = fold_indexes s ~init:() ~f:(fun i () -> remove s i)
    let fold s ~init ~f =
      fold_indexes s ~init
        ~f:(fun i acc ->
            read_opt s i >>= function
            | None -> Lwt.return acc
            | Some v -> f i v acc)
    let bindings s =
      fold s ~init:[] ~f:(fun p v acc -> Lwt.return ((p,v) :: acc))
    let iter s ~f =
      fold s ~init:() ~f:(fun p v () -> f p v)
    let fold_keys s ~init ~f =
      fold_indexes s ~init
        ~f:(fun i acc ->
            known s i >>= function
            | false -> Lwt.return acc
            | true -> f i acc)
    let keys s =
      fold_keys s ~init:[] ~f:(fun p acc -> Lwt.return (p :: acc))
    let iter_keys s ~f =
      fold_keys s ~init:() ~f:(fun p () -> f p)
  end

  module Make_buffered_map
      (N : NAME) (V : VALUE)
      (Map : Map.S with type key = I.t) = struct
    include Make_map (N) (V)
    module Map = Map
    let read_all s =
      fold s ~init:Map.empty ~f:(fun i v set -> Lwt.return (Map.add i v set))
    let store_all s map =
      remove_all s >>= fun () ->
      Map.fold
        (fun k v acc -> let res = store s k v in acc >>= fun () -> res)
        map Lwt.return_unit
  end

end

module Make_set (S : STORE) (I : INDEX) = struct
  type t = S.t
  type elt = I.t
  let inited = MBytes.of_string "inited"
  let known s i = S.known s (I.to_path i [])
  let store s i = S.store s (I.to_path i []) inited
  let remove s i = S.remove s (I.to_path i [])
  let remove_all s = S.remove_dir s []

  let fold s ~init ~f =
    let rec dig i path acc =
      if i <= 1 then
        S.fold s path ~init:acc ~f:begin fun k acc ->
          match k with
          | `Dir _ -> Lwt.return acc
          | `Key file ->
              match I.of_path file with
              | None -> assert false
              | Some p -> f p acc
        end
      else
        S.fold s path ~init:acc ~f:begin fun k acc ->
          match k with
          | `Dir k ->
              dig (i-1) k acc
          | `Key _ ->
              Lwt.return acc
        end in
    dig I.path_length [] init

  let elements s =
    fold s ~init:[] ~f:(fun p acc -> Lwt.return (p :: acc))
  let iter s ~f =
    fold s ~init:() ~f:(fun p () -> f p)
end

module Make_buffered_set
    (S : STORE) (I : INDEX) (Set : Set.S with type elt = I.t) = struct
  include Make_set (S) (I)
  module Set = Set
  let read_all s =
    fold s ~init:Set.empty ~f:(fun i set -> Lwt.return (Set.add i set))
  let store_all s new_set =
    read_all s >>= fun old_set ->
    Lwt_list.iter_p (remove s) Set.(elements (diff old_set new_set)) >>= fun () ->
    Lwt_list.iter_p (store s) Set.(elements (diff new_set old_set))
end

module Make_map (S : STORE) (I : INDEX) (V : VALUE) = struct
  type t = S.t
  type key = I.t
  type value = V.t
  let known s i = S.known s (I.to_path i [])
  let read s i =
    S.read s (I.to_path i []) >>=? fun b -> Lwt.return (V.of_bytes b)
  let read_opt s i =
    read s i >>= function
    | Error _ -> Lwt.return_none
    | Ok v -> Lwt.return_some v
  let read_exn s i =
    read s i >>= function
    | Error _ -> Lwt.fail Not_found
    | Ok v -> Lwt.return v
  let store s i v = S.store s (I.to_path i []) (V.to_bytes v)
  let remove s i = S.remove s (I.to_path i [])
  let remove_all s = S.remove_dir s []
  let fold s ~init ~f =
    let rec dig i path acc =
      if i <= 1 then
        S.fold s path ~init:acc ~f:begin fun k acc ->
          match k with
          | `Dir _ -> Lwt.return acc
          | `Key file ->
              S.read_opt s file >>= function
              | None -> Lwt.return acc
              | Some b ->
                  match V.of_bytes b with
                  | Error _ ->
                      (* Silently ignore unparsable data *)
                      Lwt.return acc
                  | Ok v ->
                      match I.of_path file with
                      | None -> assert false
                      | Some path -> f path v acc
        end
      else
        S.fold s path ~init:acc ~f:begin fun k acc ->
          match k with
          | `Dir k -> dig (i-1) k acc
          | `Key _ -> Lwt.return acc
        end in
    dig I.path_length [] init

  let bindings s =
    fold s ~init:[] ~f:(fun p v acc -> Lwt.return ((p,v) :: acc))
  let iter s ~f =
    fold s ~init:() ~f:(fun p v () -> f p v)
  let fold_keys s ~init ~f =
    S.fold s [] ~init
      ~f:(fun p acc ->
          match p with
          | `Dir _ -> Lwt.return acc
          | `Key p ->
              match I.of_path p with
              | None -> assert false
              | Some path -> f path acc)
  let keys s =
    fold_keys s ~init:[] ~f:(fun p acc -> Lwt.return (p :: acc))
  let iter_keys s ~f =
    fold_keys s ~init:() ~f:(fun p () -> f p)
end

module Make_buffered_map
    (S : STORE) (I : INDEX) (V : VALUE)
    (Map : Map.S with type key = I.t) = struct
  include Make_map (S) (I) (V)
  module Map = Map
  let read_all s =
    fold s ~init:Map.empty ~f:(fun i v set -> Lwt.return (Map.add i v set))
  let store_all s map =
    remove_all s >>= fun () ->
    Map.fold
      (fun k v acc -> let res = store s k v in acc >>= fun () -> res)
      map Lwt.return_unit
end

module Integer_index = struct
  type t = int
  let path_length = 1
  let to_path x l = string_of_int x :: l
  let of_path = function
    | [x] -> begin try Some (int_of_string x) with _ -> None end
    | _ -> None
end
