(**************************************************************************)
(*                   Cameleon                                             *)
(*                                                                        *)
(*      Copyright (C) 2002 Institut National de Recherche en Informatique et   *)
(*      en Automatique. All rights reserved.                              *)
(*                                                                        *)
(*      This program 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 2 of the License, or  *)
(*      any later version.                                                *)
(*                                                                        *)
(*      This program 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 this program; if not, write to the Free Software       *)
(*      Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA          *)
(*      02111-1307  USA                                                   *)
(*                                                                        *)
(*      Contact: Maxence.Guesdon@inria.fr                                *)
(**************************************************************************)

(** Main module. 
@author Maxence Guesdon
@since 2001
*)

module Ocvs_types = Ocamlcvs.Types
module Ocvs_commands = Ocamlcvs.Commands
module Ocvs_behav = Ocamlcvs.Behav
module Ocvs_list = Ocamlcvs.List
module Ocvs_tree = Ocamlcvs.Tree

module M = Cam_messages
module G = Cam_global

module UI = Cam_gui

open Ocvs_behav
open Ocvs_list
open Ocvs_tree

open Cam_data
open Cam_types

let _ = Cam_args.parse ()

let (!!) = Options.(!!)
let (=:=) = Options.(=:=)



class list_behaviour data =
  object
    val mutable last_clicked_column = 0

    inherit [Cam_types.file] Ocvs_behav.cvs (data :> Cam_types.file Ocvs_behav.data)

    method elements = data#elements
    method update_element = data#update_element
    method remove_element = data#remove_element
    method t_of_cvs_info = data#t_of_cvs_info
    method cvs_info_of_t = data#cvs_info_of_t
	
    method comparison_function col =
      let f_value  =
	match col with
	| 1 -> fun f -> Ocamlcvs.Types.string_of_status f.f_status
	| 2 -> fun f -> f.f_work_rev
	| 3 -> fun f -> f.f_rep_rev
	| 4 -> fun f -> f.f_date_string
	| 5 -> fun f -> f.f_type.Cam_types.ft_name
	| _ -> fun f -> f.f_name
      in
      if last_clicked_column = col then
	(
	 last_clicked_column <- -1 ;
	 fun f1 -> fun f2 -> compare (f_value f2) (f_value f1)
	)
      else
	(
	 last_clicked_column <- col ;
	 fun f1 -> fun f2 -> compare (f_value f1) (f_value f2)
	)

    method display_strings f =
      (f.f_type.Cam_types.ft_color,
       [ Filename.basename f.f_name ; 
	 Ocamlcvs.Types.string_of_status f.f_status ; 
	 f.f_work_rev ;
	 f.f_rep_rev ;
	 f.f_date_string ;
	 f.f_type.Cam_types.ft_name
       ]
      )
    method titles = [ 
      M.file ;
      M.status ;
      M.working_rev ;
      M.rep_rev ;
      M.date ;
      M.file_type
    ] 
    method autorize_file (file : Cam_types.file) = 
      print_string ("autorize file"^file.f_name); print_newline () ;
      if data#file_changed file
      then
	match GToolbox.question_box 
	    ~title: M.mQuestion
	    ~buttons: [ M.continue ;
			M.skip ;
			M.stop ]
	    (M.save_changed_file_before file.f_name)
	with
	  1 -> 
	    data#save_file file ;
	    Ocvs_behav.Continue
	| 2 -> Ocvs_behav.Skip
	| _ -> Ocvs_behav.Stop
      else
	Ocvs_behav.Continue

    method after_action (_ : Cam_types.file) = ()
    method menu_ctx (selection : Cam_types.file list) = 
      match selection with
	[] ->
	  [ M.m_add_files, Cam_misc.execute_command M.a_add_file ;
	    M.m_add_binary_files, Cam_misc.execute_command M.a_add_binary_file ;
	  ]
      | _ ->
	  [ 
	    M.m_edit, Cam_misc.execute_command M.a_edit ;
	    M.m_edit_with, Cam_misc.execute_command M.a_edit_with ;
	    M.m_add_files, Cam_misc.execute_command M.a_add_file;
	    M.m_add_binary_files, Cam_misc.execute_command M.a_add_binary_file;
	    M.m_commit_files, Cam_misc.execute_command M.a_commit_files;
	    M.m_tag_files, Cam_misc.execute_command M.a_tag_file ;
	    M.m_tags_of_file, Cam_misc.execute_command M.a_tags_of_file ;
	    M.m_remove_files, Cam_misc.execute_command M.a_remove_file ;
	    M.m_last_diff, Cam_misc.execute_command M.a_last_differences ;
	    M.m_diff_with, Cam_misc.execute_command M.a_diff_with ;
	    M.m_diff_between, Cam_misc.execute_command M.a_diff_between ;
	    M.m_resolve_conflicts, Cam_misc.execute_command M.a_resolve_conflicts; 
	    M.m_log_file, Cam_misc.execute_command M.a_log; 
	  ]

    method select (_ : Cam_types.file) = ()
    method unselect (_ : Cam_types.file) = ()

    method double_click (_ : Cam_types.file) = 
      Cam_misc.execute_command !!Cam_config.file_double_click_command ()

    method needs_cvs_status = !!Cam_config.auto_update_file_view

  end

class tree_behaviour (roots : string list) 
    current_box_index data =
  object(self)
    inherit [Cam_types.file] Ocvs_behav.cvs data

    (** expand the given directory or not *)
    method expand_dir complete_dir =
      List.mem complete_dir !!Cam_config.expanded_dirs;

    (** set the given directory as expanded *)
    method add_expanded_dir complete_dir =
      if self#expand_dir complete_dir then
	()
      else
	Cam_config.expanded_dirs =:= complete_dir :: !!Cam_config.expanded_dirs

    (** remove the given directory as expanded *)
    method remove_expanded_dir complete_dir =
      if self#expand_dir complete_dir then
	Cam_config.expanded_dirs =:= 
	  (List.filter (fun s -> s <> complete_dir)
	     !!Cam_config.expanded_dirs
	  )
      else
	()

    method update_element = data#update_element
    method t_of_cvs_info = data#t_of_cvs_info

    method roots = roots
    method menu_ctx (selection : string option) = 
      match selection with
	None -> 
	  [ M.m_add_dir, Cam_misc.execute_command M.a_add_dir ]
      |	Some s ->
	  [ M.m_add_dir, Cam_misc.execute_command M.a_add_dir ;
	    M.m_update_dir, Cam_misc.execute_command M.a_update_dir ;
	    M.m_commit_dir, Cam_misc.execute_command M.a_commit_dir ;
	    M.m_tag_dir, Cam_misc.execute_command M.a_tag_dir ;
	    M.m_update_dir_view, Cam_misc.execute_command M.a_update_dir_view ;
	  ]

    method select (dir : string) = 
      try (List.nth !Cam_global.views (current_box_index ()))#display_dir (Some dir)
      with _ -> ()

    method unselect (dir : string) = 
      try (List.nth !Cam_global.views (current_box_index ()))#display_dir None
      with _ -> ()

  end


let (lb : Cam_types.file Ocvs_behav.list_behaviour) = 
  if !Cam_args.with_rep then
    new list_behaviour data
  else
    new Cam_norep.list_behaviour data (Cam_misc.execute_command M.a_edit)

(* the tree of modules *)

let _ = Cam_global.add_view (new Cam_modules_view.view () :> Cam_view.view) M.modules

(* the list of files *)

class files_view (l : Cam_types.file Ocvs_list.box) =
  object(self)
    method coerce = l#box#coerce
    method display_dir d_opt = 
      (
       (match d_opt with
	 None -> ()
       | Some d -> Cam_gui.display_message (Cam_messages.analyzing_dir d)
       );
       l#display_dir d_opt;
       (
	match d_opt with
	  None -> ()
	| Some _ -> Cam_gui.display_message Cam_messages.mOk
       )
      )
  end

let listbox = new Ocvs_list.box (lb) 
let _ = Cam_global.add_view (new files_view listbox) M.files

(* the directory tree *)

let tb = 
  if !Cam_args.with_rep then
    new tree_behaviour !Cam_args.roots 
      (fun () -> UI.wnote#current_page)
      (Cam_data.data :> file Ocvs_behav.data)
  else
    new Cam_norep.tree_behaviour !Cam_args.roots
      (fun () -> UI.wnote#current_page)
      Cam_data.data

let treebox = new Ocvs_tree.box (tb :> Cam_types.file Ocvs_behav.tree_behaviour)
let _ = UI.vbox_tree#pack ~expand: true treebox#box#coerce

let _ = UI.wnote#goto_page 0

(* managment of the window listing the commands *)

let window_commands = new Cam_misc.display_list
    ~destroy_on_close: false
    M.commands
    ~titles: [M.name ; M.description]
    ~on_double_click: (fun (com,_) -> Cam_global.exec com [] ())
    (fun (com,h) -> ([Gpattern.String com ; Gpattern.String h], None))

let update_window_commands () =
  window_commands#update_data (Cam_global.available_commands_with_help ())

(* The custom tool bar *)

let update_doc_menu load_doc = 
  let f_create ele =
    let text_doc = new Cam_doc_gui.text_doc () in
    let _ = text_doc#window#show () in
    text_doc#push ele
  in
  Cam_doc.update load_doc f_create 
    Cam_doc_gui.search_exact 
    Cam_doc_gui.search_regexp 
    !Cam_menus.doc_menu;
  Gc.compact ();
  match !Cam_doc_gui.modules_window with
    None -> ()
  | Some _ -> Cam_doc_gui.create_or_update_list_window ()

let update_menus () =
  List.iter UI.menubar#remove UI.menubar#children;
  List.iter 
    (Cam_misc.create_menu UI.menubar) 
    !Cam_global.menus

let update_main_window_hotkeys () =
  Okey.remove_widget UI.window ();
  let add ((mods, k), action) = Okey.add UI.window ~mods k (Cam_misc.execute_command action) in
  List.iter add !!Cam_config.keymap_main

let update_config update_doc () =
  update_menus ();
  update_doc_menu update_doc ;
  Cam_bar.build UI.handle_box;
  update_window_commands ();
  update_main_window_hotkeys ()

(* add some commands to the commands table *)

let edit_with _ =
  let entries = List.map
      (fun s -> `I (s, fun () -> 
	                 let e = Cam_types.editor_of_string s in
	                 List.iter (fun f -> Cam_edit.edit Cam_data.data ~editor: e f) listbox#selection))
      ((List.filter (fun s -> s <> "") 
	  (List.map fst Cam_types.editor_strings)) @ 
       (List.map fst !Cam_global.custom_editors))
  in
  GToolbox.popup_menu ~entries ~button: 3 ~time: 0
     
let commands = [
  (M.a_edit, "", (fun _ -> List.iter (Cam_edit.edit Cam_data.data) listbox#selection)) ;
  (M.a_edit_with, "", edit_with) ;
  (M.a_quit, "", fun _ -> UI.window#destroy ()) ;
  (M.a_config, "", (fun _ -> Cam_config_gui.config update_config)) ;
  (M.a_new_file, "", Cam_commands.new_file) ;
  (M.a_display_doc_box, "", fun _ -> Cam_doc_gui.create_or_update_list_window ()) ;
  (M.a_list_commands, M.h_list_commands, (fun _ -> window_commands#show )) ;
  (M.a_about_box, "",  (fun _ -> GToolbox.message_box M.m_about M.software_about)) ;
  (M.a_update_files_view, "", (fun _ -> listbox#update true)) ;
  (M.a_update_dir_view, "", (fun _ -> treebox#update)) ;
] 

let cvs_commands = [
  (M.a_add_dir, "", (fun _ -> treebox#cvs_add_dir)) ;
  (M.a_update_dir, "", (fun _ -> treebox#cvs_update_dir)) ;
  (M.a_commit_dir, "", (fun _ -> treebox#cvs_commit_dir)) ;
  (M.a_tag_dir, "", (fun _ -> treebox#cvs_tag_dir)) ;

  (M.a_add_file, "", (fun _ -> treebox#cvs_add_files)) ;
  (M.a_add_binary_file, "", (fun _ -> treebox#cvs_add_binary_files)) ;
  (M.a_commit_files, "", (fun _ -> listbox#cvs_commit_selected_files)) ;
  (M.a_tag_file, "", (fun _ -> listbox#cvs_tag_selected_files)) ;
  (M.a_tags_of_file, "", (fun _ -> listbox#cvs_tags_of_file)) ;
  (M.a_remove_file, "", (fun _ -> listbox#cvs_remove_selected_files)) ;
  (M.a_last_differences, "", (fun _ -> listbox#cvs_lastdiff_file)) ;
  (M.a_diff_with, "",  (fun _ -> listbox#cvs_differences_with)) ;
  (M.a_diff_between, "", (fun _ -> listbox#cvs_differences_between)) ;
  (M.a_resolve_conflicts, "", (fun _ -> listbox#cvs_resolve_conflicts)) ;

  (M.a_log, "", (fun _ -> listbox#cvs_log_file)) ;
] 
    
let all_commands = 
  commands @  (if !Cam_args.with_rep then cvs_commands else [])

let _ = List.iter (fun (c,desc,f) -> G.add_command c desc f) all_commands

(* init *)

let _ = Cam_global.selected_dir := (fun () -> treebox#selection)
let _ = Cam_global.selected_files := (fun () -> listbox#selection)
let _ = update_config true ()
let _ = Cam_doc_gui.create_or_update_list_window ()

(* For communications with efuns *)
let _ = Sys.set_signal Sys.sigpipe Sys.Signal_ignore

(* set the timeout to check files still edited in efuns and xemacs. *)
let _ = GMain.Timeout.add !!Cam_config.check_edited_files_delay
    (fun () -> 
      Cam_efuns.check_efuns_files () ; 
      Cam_xemacs.check_xemacs_files () ;
      true)

let _ = UI.window#show ()

(* Execute commands on the command line *)
let _ = List.iter (fun c -> Cam_misc.execute_command c ()) !Cam_args.init_commands


let _ = !Cam_global.display_message Cam_messages.welcome

let _ = GMain.Main.main ()

let _ = Cam_data.data#close_files_before_exit
let _ = Cam_config.save_gui ()
