open Common_options;;
open Type_options;;

type action = 
  Check of string * (string option)
  | Add of string * (string option) 
  | Del of string 
  | List
  | TestUsersSection
  | DumpUsersSection
  | StripUsersSection

exception No_downloads_ini
exception Invalid_format

let encrypt x = Md4.Md4.to_string (Md4.Md4.string x)
;;

let empty_password = ""
;;

let _ = 
  let action = ref List
  in
  let password = ref None
  in
  let filename = ref None
  in
  let quiet = ref false
  in
  let _ = Arg.parse [
    ("--add", Arg.String (fun x -> action := Add (x,None)),
       "Add the specified user, prompt or use password set with -p.");
    ("--del", Arg.String (fun x -> action := Del (x) ),
      "Delete the specified user.");
    ("--check", Arg.String (fun x -> action := Check (x,None)),
      "Check if specified user has an empty password, a password specified with"
      ^" -p or exist.");
    ("--list", Arg.Unit (fun () -> action := List),
      "List all users.");
    ("--test-users-section", Arg.Unit (fun () -> action := TestUsersSection),
      "Check the presence of a users section.");
    ("--dump-users-section", Arg.Unit (fun () -> action := DumpUsersSection),
      "Print the users section.");
    ("--strip-users-section", Arg.Unit (fun () -> action := StripUsersSection),
      "Print the specified file without the users section.");
    ("-f", Arg.String ( fun x -> filename := Some x ),
      "Which downloads.ini to use.");
    ("-p", Arg.String ( fun x -> password := Some x ),
      "Set the password.");
    ("-q", Arg.Set quiet,
      "Run quietly.");
    ]
    ( fun _ -> () )
    "Usage mldonkey_users [options] where options are :"
  in
  let real_action = 
    match (!action,!password) with
      (List, _) -> List
    | (Del x, _) -> Del x
    | (Add(x, _), y) -> Add ( x, y )
    | (Check(x, _), y) -> Check ( x, y )
    | (TestUsersSection, _) -> TestUsersSection
    | (DumpUsersSection, _) -> DumpUsersSection
    | (StripUsersSection, _) -> StripUsersSection
  in
  let file =   
    match !filename with
      Some(x) ->
      x
    | None ->
      raise No_downloads_ini
  in
  let load_users file = 
    let add_one_user lst users_option_entry =
      match users_option_entry with
        ValList([ValString(user);ValString(password)])
      | ValList([ValString(user);  ValChar(password)])
      | ValList([ValString(user); ValIdent(password)])
      | ValList([  ValChar(user);ValString(password)])
      | ValList([  ValChar(user);  ValChar(password)])
      | ValList([  ValChar(user); ValIdent(password)])
      | ValList([ ValIdent(user);ValString(password)])
      | ValList([ ValIdent(user);  ValChar(password)])
      | ValList([ ValIdent(user); ValIdent(password)]) ->
          (user,password)::lst
      | _ ->
        raise Invalid_format
    in
    let users_option = 
      try
        find_option "users2" ( load_option file )
      with Not_found ->
        ValList []
    in
    match users_option with
    ValList lst ->
      List.fold_left add_one_user [] lst
    | _ ->
      raise Invalid_format
  in
  let save_users file new_users = 
    let save_one_user other_users (user,password) =
      ValList([ ValIdent(user);ValString(password)]) :: other_users
    in
    let all_users = List.fold_left save_one_user [] new_users
    in
    let new_option = 
      Id ("users2", ValList(all_users))
    in
    save_option file (replace_option new_option (load_option file))
  in
  let debug x = 
    if !quiet then
      ()
    else
      (
        print_string x;
        print_newline ()
      )
  in
  let fatal x =
    debug x;
    exit 1
  in
  match real_action with
    List ->
      List.iter (fun (x,y) -> print_string x; print_newline ()) 
      (load_users file)
  | Check(user,Some(password)) ->
    begin
      try 
        let real_password = List.assoc user (load_users file)
        in
        if real_password = ( encrypt password ) then
          debug "Found matching user"
        else
          fatal "User exists but bad password"
      with Not_found ->
        fatal "User not found"
    end
  | Check(user,None) ->
    begin
      try
        let real_password = List.assoc user (load_users file)
        in
        if real_password = ( encrypt empty_password ) then
            fatal "This user has an empty password"
        else
            debug "This user has a good password"
      with Not_found ->
        fatal "User not found"
    end    
  | Add(user,Some(password)) ->
      save_users file (
        (user,encrypt password)
        ::(List.remove_assoc user (load_users file))
        )
  | Add(user,None) ->
    begin
      let password =
        print_string "New password :";
        read_line ()
      in
      let confirm_password =
        print_string "New password ( confirm ) :";
        read_line ()
      in
      if password = confirm_password then
        (
          save_users file (
            (user,encrypt password)
            ::(List.remove_assoc user (load_users file))
            );
          debug "New user/password saved"
        )
      else
        fatal "Password and confirmation do not match"
    end
  | Del(user) ->
    begin
      try
        save_users file (List.remove_assoc user (load_users file));
      with Not_found ->
        fatal "User not found"
    end
  | TestUsersSection ->
      begin
        try
          let _ = find_option "users2" (load_option file)
          in
          ()
        with Not_found ->
          fatal "Cannot find users section"
      end
  | DumpUsersSection ->
      begin
        try
          let users_section = find_option "users2" (load_option file)
          in
          output_option stdout (Options(Id("users2",users_section), Eof))
        with Not_found ->
          fatal "Cannot find users section"
      end
  | StripUsersSection ->
      begin
        let stripped_options = remove_option "users2" (load_option file)
        in
        output_option stdout stripped_options
      end
