-----------------------------------------------------------------------
--                                                                   --
--                     Copyright (C) 2001                            --
--                          ACT-Europe                               --
--                                                                   --
-- This library 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 (at your option) any later version.  --
--                                                                   --
-- This library 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 library; if not, write to the             --
-- Free Software Foundation, Inc., 59 Temple Place - Suite 330,      --
-- Boston, MA 02111-1307, USA.                                       --
--                                                                   --
-- As a special exception, if other files instantiate generics from  --
-- this unit, or you link this unit with other files to produce an   --
-- executable, this  unit  does not  by itself cause  the resulting  --
-- executable to be covered by the GNU General Public License. This  --
-- exception does not however invalidate any other reasons why the   --
-- executable file  might be covered by the  GNU Public License.     --
-----------------------------------------------------------------------

with GNAT.OS_Lib;       use GNAT.OS_Lib;
with GNAT.Regexp;       use GNAT.Regexp;
with GNAT.Command_Line; use GNAT.Command_Line;
with GNAT.Directory_Operations; use GNAT.Directory_Operations;
with Ada.Text_IO;       use Ada.Text_IO;
with Ada.Strings.Fixed; use Ada.Strings.Fixed;

with Find_Utils;    use Find_Utils;
with Src_Contexts;  use Src_Contexts;
with Language;      use Language;
with Language.Ada;  use Language.Ada;
with Language.C;    use Language.C;
with Language.Cpp;  use Language.Cpp;
with Language.Java; use Language.Java;
with Language_Handlers.Glide; use Language_Handlers.Glide;
with Glide_Kernel;  use Glide_Kernel;
with Traces;        use Traces;
with Projects.Registry; use Projects, Projects.Registry;

procedure Run is

   procedure Print_Help;
   --  Print help about command line switches.

   procedure Wrapper_Search
     (Handler       : access Glide_Language_Handler_Record'Class;
      Look_For      : String;
      Files_Pattern : GNAT.Regexp.Regexp;
      Directory     : String;
      Recurse       : Boolean;
      Options       : Search_Options;
      Scope         : Search_Scope);
   --  Execute a search.
   --  The callback mustn't be null.
   --  Any exception is handled by printing its information on Standard_Error.

   procedure Report_Error (Line : String);
   --  Called to report error while parsing the project

   CL_Print_Matches     : Boolean  := True;
   CL_Print_Files       : Boolean  := True;
   CL_Max_Matches       : Natural  := Natural'Last;

   ----------------
   -- Print_Help --
   ----------------

   procedure Print_Help is
   begin
      Put_Line ("Usage : $0 Look_For {Files_List | Files_Pattern"
        & " [Selection_Options]}");
      Put_Line ("           [Callback_Options] [General_Options]");
      New_Line;
      Put_Line ("  -P project               name of the project file to load");
      New_Line;
      Put_Line (" Look_For:");
      Put_Line ("  -look_for       string   word or pattern to search");
      New_Line;
      Put_Line (" Files_Pattern:");
      Put_Line ("  -files_pattern  string   pattern to select files.");
      New_Line;
      Put_Line (" Selection_Options:");
      Put_Line ("  -directory      string   directory where to select files");
      Put_Line ("                           """" means current dir [""""]");
      Put_Line ("  -recurse        boolean  recurse in subdirectories [0]");
      New_Line;
      Put_Line (" Callback_Options:");
      Put_Line ("  -print_matches  boolean  whether to print matches     [1]");
      Put_Line ("  -print_files    boolean  whether to print files       [1]");
      Put_Line ("  -max_matches    number   non-aborting matches [0]");
      New_Line;
      Put_Line (" General_Options:");
      Put_Line ("  -case           boolean  match case           [0]");
      Put_Line ("  -whole          boolean  whole word           [0]");
      Put_Line ("  -regexp         boolean  look_for is a regexp [0]");
      Put_Line ("  -scope          string   searching scope [whole]");
      New_Line;
      Put_Line ("Boolean is '1' for true, other for false (eg '0').");
      Put_Line ("Default values are given enclosed by [].");
      Put_Line ("Switches may be given in any order.");
      Put_Line ("Files_List has priority over Files_Pattern.");
      Put_Line ("Matches are printed so: 'file:line:text'.");
      Put_Line ("Files are printed so: 'Done file'.");
      Put_Line ("Max_... is the limit of consecutive successful ...s.");
      Put_Line ("A 0 maximum means unlimited.");
      New_Line;
      Put_Line ("   Scope     Description");
      Put_Line ("------------ --------------");
      Put_Line ("whole        never use context");
      Put_Line ("comm_only    scan only comments");
      Put_Line ("str_only     scan only strings");
      Put_Line ("comm_str     scan strings & comments");
      Put_Line ("all_but_comm scan everything but comments");
   end Print_Help;

   --------------------
   -- Wrapper_Search --
   --------------------

   procedure Wrapper_Search
     (Handler       : access Glide_Language_Handler_Record'Class;
      Look_For      : String;
      Files_Pattern : GNAT.Regexp.Regexp;
      Directory     : String;
      Recurse       : Boolean;
      Options       : Search_Options;
      Scope         : Search_Scope)
   is
      Context : Files_Context_Access;
      Remaining_Matches : Natural := CL_Max_Matches;

      function General_Callback (Match : Match_Result) return Boolean;
      --  General callback used in the testsuite

      function General_Callback (Match : Match_Result) return Boolean is
         Location : constant String := Base_Name (Current_File (Context))
           & ':' & Trim (Positive'Image (Match.Line), Ada.Strings.Left)
           & ':' & Trim (Positive'Image (Match.Column), Ada.Strings.Left);
      begin
         if CL_Print_Matches then
            Put_Line (Location & ':' & Match.Text);
            --  ??? Would be nice to print the submatches as well (parenthesis
            --  pairs)
         end if;

         Remaining_Matches := Remaining_Matches - 1;
         return Remaining_Matches /= 0;
      end General_Callback;

   begin
      Context := Files_Factory
        (All_Occurrences => True,
         Scope           => Scope);
      Set_Context
        (Context,
         Look_For      => Look_For,
         Options       => Options);

      --  ??? What if we have a specific list of files
      Set_File_List
        (Context,
         Files_Pattern => Files_Pattern,
         Directory     => Directory,
         Recurse       => Recurse);

      while Search
        (Context,
         Handler         => Handler,
         Kernel          => null,
         Callback        => General_Callback'Unrestricted_Access,
         All_Occurrences => True)
      loop
         null;
      end loop;

      Free (Search_Context_Access (Context));
   end Wrapper_Search;

   ------------------
   -- Report_Error --
   ------------------

   procedure Report_Error (Line : String) is
   begin
      Put_Line ("Error when parsing project file: " & Line);
   end Report_Error;


   --  Start of processing for Run

   CL_Look_For      : String_Access  := null;
   CL_Files_Pattern : String_Access  := new String'("*");
   CL_Directory     : String_Access  := new String'("");
   CL_Recurse       : Boolean        := False;
   CL_Options       : Search_Options := (False, False, False);
   CL_Scope         : String_Access  := new String'("");
   Project_Name     : String_Access := null;
   Scope            : Search_Scope       := Whole;
   Handler          : Glide_Language_Handler;
   New_Project_Loaded : Boolean;
   Registry         : aliased Project_Registry;
   Project          : Project_Type;
   True_Value       : constant String := "1";
   Switches         : constant String :=
     "case: directory: files_pattern: help look_for:"
     & " max_matches: print_files: print_matches: "
     & " recurse: regexp: scope: whole: P:";

begin
   Parse_Config_File (".gnatdebug");
   Projects.Registry.Initialize;

   loop
      case Getopt (Switches) is
         when ASCII.NUL =>
            exit;

         when 'c' =>
            CL_Options.Case_Sensitive := Parameter = True_Value;

         when 'd' =>
            Free (CL_Directory);
            CL_Directory := new String'(Parameter);

         when 'f' =>
            Free (CL_Files_Pattern);
            CL_Files_Pattern := new String'(Parameter);

         when 'h' =>
            Print_Help;
            return;

         when 'l' =>
            Free (CL_Look_For);
            CL_Look_For := new String'(Parameter);

         when 'm' =>
            CL_Max_Matches := Natural'Value (Parameter);

         when 'P' =>
            Free (Project_Name);
            Project_Name := new String'(Parameter);

         when 'p' =>
            if Full_Switch = "print_matches" then
               CL_Print_Matches := Parameter = True_Value;
            elsif Full_Switch = "print_files" then
               CL_Print_Files := Parameter = True_Value;
            end if;

         when 'r' =>
            if Full_Switch = "recurse" then
               CL_Recurse := Parameter = True_Value;

            else
               CL_Options.Regexp := Parameter = True_Value;
            end if;

         when 's' =>
            Free (CL_Scope);
            CL_Scope := new String'(Parameter);

         when 'w' =>
            CL_Options.Whole_Word := Parameter = True_Value;

         when others =>
            raise Program_Error;
      end case;
   end loop;

   if CL_Look_For = null then
      Put_Line (Standard_Error, "Need argument 'look_for' !");
      Put_Line (Standard_Error, "See -help.");
      return;
   end if;

   if CL_Scope.all = "" then
      null;
   elsif CL_Scope.all = "whole" then
      Scope := Whole;
   elsif CL_Scope.all = "comm_only" then
      Scope := Comments_Only;
   elsif CL_Scope.all = "str_only" then
      Scope := Strings_Only;
   elsif CL_Scope.all = "comm_str" then
      Scope := Comments_And_Strings;
   elsif CL_Scope.all = "all_but_comm" then
      Scope := All_But_Comments;
   else
      Put_Line (Standard_Error, "Bad argument for 'scope' !");
      return;
   end if;

   Gtk_New (Handler);

   Set_Registry (Handler, Registry'Unchecked_Access);

   Register_Language (Handler, "Ada", Ada_Lang);
   Add_Language_Info
     (Handler, "Ada",
      LI => null,
      Default_Spec_Suffix => ".ads",
      Default_Body_Suffix => ".adb");

   Register_Language (Handler, "C", C_Lang);
   Add_Language_Info
     (Handler, "C",
      LI => null,
      Default_Spec_Suffix => ".c",
      Default_Body_Suffix => ".h");

   Register_Language (Handler, "C++", Cpp_Lang);
   Add_Language_Info
     (Handler, "C++",
      LI => null,
      Default_Spec_Suffix => ".cpp",
      Default_Body_Suffix => ".hh");

   Register_Language (Handler, "Java", Java_Lang);
   Add_Language_Info
     (Handler, "Java",
      LI => null,
      Default_Spec_Suffix => ".java",
      Default_Body_Suffix => ".java");

   if Project_Name = null then
      Load_Default_Project (Registry, Get_Current_Dir);
   else
      Load (Registry, Project_Name.all, Report_Error'Unrestricted_Access,
            New_Project_Loaded);
   end if;
   Project := Get_Root_Project (Registry);
   Recompute_View (Registry, Report_Error'Unrestricted_Access);

   Wrapper_Search
     (Handler,
      CL_Look_For.all,
      Compile (CL_Files_Pattern.all, Glob => True),
      CL_Directory.all,
      CL_Recurse,
      CL_Options,
      Scope);

   Free (CL_Look_For);
   Free (CL_Files_Pattern);
   Free (CL_Directory);
   Free (CL_Scope);
end Run;
