(define selfmenuroot "<Image>/Filters/Check Duplicate Scripts ...")     ; Change these to suit yourself         
(define selfmenuentry "Test For Duplicate Scripts...")

(define selfauthor "Kevin Payne")
(define selfcopyright "Copyright (C) 2011 Kevin Payne paynekj@hotmail.com")

; Support for this script can be found on GIMPChat: http://gimpchat.com/viewtopic.php?f=9&t=3057
;
; Version 1.0 09.11.2011 First version
; Version 1.1 10.11.2011 Add option to search all script directories
; Version 1.2 11.11.2011 Change checking options to include checking a separate directory against the installed scripts
; Version 1.3 14.11.2011 Add options to create a log file
; Version 1.4 14.11.2011 Tidying
; Version 1.5 15.11.2011 Re-write to get full menu paths into log file

(define selffileversion "15.11.2011 Version 1.5 - Re-write to get full menu paths into log file")

; ##################################################################################################
; IMPORTANT if you change the file name, change this to match
(define selffilename "\n- kp24_test_for_duplicate_scripts.scm")
; I do this to get the file name into the description shown by the procedure browser
; ##################################################################################################

; What gets displayed when the mouse is hovered over the menu entry
(define selftipstrip (string-append "Test for duplicate scripts" "\n\n\nRegisters in menu " selfmenuroot  "/" selfmenuentry "\n\nScript File name - " selffilename))

; ####################### USEAGE #############################################################################################
;
;Check What?:
; All installed scripts:
;   will check the are no duplicates within all the installed scripts.
;   i.e all the scripts in the directories defined in Edit >> Preferences >> Folders >> Scripts
;
; Single directory within itself:
;   will check that all the scripts in the directory you specify don't have any duplicates among themselves.
;
; Single directory against installed scripts:
;   will check that the scripts in the specified directory don't duplicate any of the installed scripts.
;
;
;Logging:  (It is adviable to have the Error Console open when running this script)
; None:
;   the logging file is not used and all duplication messages are sent to the error console (or dialog boxes)
;
; Duplicates Only:
;   The duplication messages are also written to the log file
;
; Everything:
;   The duplication messages are written to the log file, along with details of scripts checked, in a pipe "|" delimited string
;
;
;Example:
;
; <Image>/Filters/Alpha to Logo/3D Outline... | C:\Program Files\GIMP-2.0\share\gimp\2.0\scripts\3d-outline.scm | script-fu-3d-outline-logo-alpha | Hrvoje Horvat | 07 April, 1998
;
; Where the fields are:
; Full Menu Path | Source File Path | Procedure Name | Author String | Version String
;
; ####################################################################################################################


; return a list of the currently defined script directories
(define (kp24_installedScriptDirectories)
  (let* (
          (source_directory_list "")
        )
        (catch ()   ; just in case, but it shouldn't error 
          (set! source_directory_list (car (gimp-gimprc-query "script-fu-path")))  ; string containing the users script directories
        )
        (if (> (string-length source_directory_list) 0) ; just in case it did error
          (set! source_directory_list (strbreakup source_directory_list SEARCHPATH-SEPARATOR))  ; turn the string into a list
          (set! source_directory_list (list (string-append gimp-directory DIR-SEPARATOR "scripts"))) ; no user defined script directories so make a single item list of the default directory
        )
        source_directory_list    
  )
)

; build a list of the script files in the supplied directories
(define (kp24_buildScriptFileListNew source_directory_list)  ; expect the directory list to be a list
  (let* (
           (varFileList '())
        )
        (for-each                       ; scan through the directories building a list of script files
          (lambda (directory_path)
            (set! varFileList (append varFileList (cadr (file-glob (string-append directory_path DIR-SEPARATOR "*.scm") 1) ) ) ); Get the list of script files 
          ) source_directory_list
        )
        varFileList      ; return the list
  )
)

; useful utility copied from http://www.scheme.com/tspl3/start.html#./start:s145
(define list-copy
  (lambda (ls)
    (if (null? ls)
        '()
        (cons (car ls) (list-copy (cdr ls)))
    )
  )
)

; this is a nasty bodge because there doesn't seem to be a suitable constant available
; I feel a bugzilla coming on...
; and thanks to saulgoode for the tidier version
;(define SEARCHPATH-SEPARATOR (if (string=? DIR-SEPARATOR "\\") ";" ":"))

; It could be a symbol, but return a string
(define (kp24_ensure_string thing)
  (if (symbol? thing)
    (symbol->string thing)
    thing
  )
)

; build these here just to keep the line lengths down later on
(define (duplicate_report proc filepath menu author version)
  (string-append proc " in \n" filepath "\n Menu: " menu "\n Author: " author "\n Version: " version "\n")
)
(define (duplicate_report2 proc_name_list filepath_list menu_path_list author_entry_list version_entry_list list_ref)
  (duplicate_report (list-ref proc_name_list list_ref) (list-ref filepath_list list_ref) (list-ref menu_path_list list_ref) (list-ref author_entry_list list_ref) (list-ref version_entry_list list_ref))
)
(define (kp24_normal_log_entry proc filepath menu author version list_ref)
  (string-append (list-ref menu list_ref) " | " (list-ref filepath list_ref) " | " (list-ref proc list_ref) " | " (list-ref author list_ref) " | " (list-ref version list_ref))
)

; strip the underscore from the menu entry text
(define (kp24_strip_underscore string)
  (unbreakupstr (strbreakup string "_") "")
)

; Extract the Menu path from a script-fu-menu-register and append it to the appropriate menu text
; THIS WILL GO WRONG IF THERE ARE DUPLICATE PROCEDURE NAMES - IT MIGHT APPPEND THE PATH TO THE WRONG MENU TEXT !
; Note: because we get passed pointers, we can change the values in the lists
(define (kp24_add_menu_path token proc_name_list menu_entry_list menu_path_list)
  (let* ( (proc_name "") (menu_entry "") (list_ref 0) (found_remainder '())
        )
        (begin
          (set! proc_name (kp24_ensure_string (car (cdr token))))
          ; We search the list backwards, in the hope that the last entry is the correct one!
          (set! found_remainder (member proc_name (reverse proc_name_list))) ; is this procedure already in the list?
          (if (list? found_remainder)
            (begin                                                                   ; Yes, this procedure is already installed
              (set! list_ref (- (length proc_name_list) (length found_remainder)))   ; find the list index of the installed occurence
              (set! list_ref (- (length found_remainder) 1 ))
              (set! menu_entry (string-append (kp24_ensure_string (car (cdr (cdr token)))) "/" (list-ref menu_entry_list list_ref)))
              (set-car! (list-tail menu_path_list list_ref) menu_entry)
            )
            (gimp-message (string-append proc_name " somehow wasn't found !" ))
          )
        )
  )
)

; ##################################################################################################
; This is where all the hard work gets done
; ##################################################################################################
(define (kp24_checkFilesWithinDirectories varFileList filesToCheckList same_dir logging_mode log_file_name)
  (let* ( 
           (found 0) (found_remainder '()) (list_ref 0) (register_count 0) (file_count 0) (number_of_files 0)
           (token "")
           (stream 0) (log_stream 0)
           (duplicate_count 0)
           
           (proc_name "")     (proc_name_list '())     (check_proc_name '())
           (menu_entry "")    (menu_entry_list '())    (check_menu_entry '())
                              (filepath_list '())      (check_filepath '())
                              (menu_path_list '())     (check_menu_path '())
           (author_entry "")  (author_entry_list '())  (check_author_entry '())
           (version_entry "") (version_entry_list '()) (check_version_entry '())
           
           (logging_file_path (string-append gimp-directory DIR-SEPARATOR ".." DIR-SEPARATOR log_file_name))

           (script_count 0) (number_of_scripts 0)
        )

        (if (> logging_mode 0) (set! log_stream (open-output-file logging_file_path)) )

        ; This section builds a list of scripts to check against
        (set! number_of_files (length varFileList))    ; how many files are we checking?
        (gimp-progress-set-text "Building details of installed files...")

        (for-each                      ; step through each of the script files to check against
          (lambda (filepath)
            (set! found 0)                            ; flag to indicate if we've found what what we're looking for
            (set! stream (open-input-file filepath))  ; open the script file
            (set! file_count (+ file_count 1))
            (gimp-progress-update (/ file_count number_of_files))           ; keep the user informed
            (gimp-progress-set-text (string-append "Processing " filepath))
            (while (= found 0)                        ; loop round until we've found what what we're looking for
              (begin
                (set! token (read stream))            ; read a section from the script file
                (if (list? token)                     ; if we've got something useful
                  (if (string=? (symbol->string (car token)) "script-fu-register")  ; check if this section is what we want
                    (begin
                      (set! proc_name (kp24_ensure_string (car (cdr token))))                ; extract the procedure name
                      (set! menu_entry (kp24_strip_underscore (kp24_ensure_string (car (cdr (cdr token))))))         ; extract the menu entry
                      (set! author_entry (kp24_ensure_string (car (cdr (cdr (cdr (cdr (cdr token))))))))  ; extract the author entry
                      (set! version_entry (kp24_ensure_string (car (cdr (cdr (cdr (cdr (cdr (cdr token)))))))))  ; extract the version entry                         

                      (set! proc_name_list (append proc_name_list (list proc_name))) ; New name so add to list
                      (set! filepath_list (append filepath_list (list filepath)))    ; remember which file it was in
                      (set! menu_entry_list (append menu_entry_list (list menu_entry))) ; New so add to list
                      (set! author_entry_list (append author_entry_list (list author_entry))) ; New so add to list
                      (set! version_entry_list (append version_entry_list (list version_entry))) ; New so add to list
                      (set! menu_path_list (append menu_path_list (list menu_entry)))  ; add a placeholder to the list
                    )
                    ; this next bit tries to capture the menu path from the script-fu-menu-register if it exists and
                    ; add it to the front of the menu entry.
                    ; For this to work script-fu-register MUST come before script-fu-menu-register
                    (if (string=? (symbol->string (car token)) "script-fu-menu-register")  ; check if this section is what we want
                      (kp24_add_menu_path token proc_name_list menu_entry_list menu_path_list)
                    )
                  )
                  (set! found 1) ; We ran out of file
                )
              )
            )
            (close-input-port stream)  ; close the script file
          )
          varFileList
        )
            
        (if (> logging_mode 1) (display (string-append "Found " (number->string file_count) " script files to check against\n\n") log_stream))
        (gimp-message (string-append "Found " (number->string file_count) " script files to check against"))

        (if (= same_dir TRUE)  ; If we are checking within the same directory(ies), then we've already
          (begin               ; made the list of scripts to check
             (set! check_proc_name (list-copy proc_name_list))
             (set! check_filepath  (list-copy filepath_list))
             (set! check_menu_entry (list-copy menu_entry_list))
             (set! check_menu_path (list-copy menu_path_list))
             (set! check_author_entry (list-copy author_entry_list))
             (set! check_version_entry (list-copy version_entry_list))
          )
          (begin
            (set! number_of_files (length filesToCheckList))    ; how many files are we checking?
            (set! file_count 0)
            (for-each                      ; step through each of the script files to check against
              (lambda (filepath)
                (set! found 0)                            ; flag to indicate if we've found what what we're looking for
                (set! stream (open-input-file filepath))  ; open the script file
                (set! file_count (+ file_count 1))
                (gimp-progress-update (/ file_count number_of_files))           ; keep the user informed
                (gimp-progress-set-text (string-append "Processing " filepath))
                (while (= found 0)                        ; loop round until we've found what what we're looking for
                  (begin
                    (set! token (read stream))            ; read a section from the script file
                    (if (list? token)                     ; if we've got something useful
                      (if (string=? (symbol->string (car token)) "script-fu-register")  ; check if this section is what we want
                        (begin
                          (set! proc_name (kp24_ensure_string (car (cdr token))))                ; extract the procedure name
                          (set! menu_entry (kp24_strip_underscore (kp24_ensure_string (car (cdr (cdr token))))))         ; extract the menu entry
                          (set! author_entry (kp24_ensure_string (car (cdr (cdr (cdr (cdr (cdr token))))))))  ; extract the author entry
                          (set! version_entry (kp24_ensure_string (car (cdr (cdr (cdr (cdr (cdr (cdr token)))))))))  ; extract the version entry                         

                          (set! check_proc_name (append check_proc_name (list proc_name))) ; New name so add to list
                          (set! check_filepath (append check_filepath (list filepath)))    ; remember which file it was in
                          (set! check_menu_entry (append check_menu_entry (list menu_entry))) ; New so add to list
                          (set! check_author_entry (append check_author_entry (list author_entry))) ; New so add to list
                          (set! check_version_entry (append check_version_entry (list version_entry))) ; New so add to list
                          (set! check_menu_path (append check_menu_path (list menu_entry)))  ; add an empty placeholder to the list
                        )
                        ; this next bit tries to capture the menu path from the script-fu-menu-register if it exists and
                        ; add it to the front of the menu entry.
                        ; For this to work script-fu-register MUST come before script-fu-menu-register
                        (if (string=? (symbol->string (car token)) "script-fu-menu-register")  ; check if this section is what we want
                          (begin
                          (kp24_add_menu_path token check_proc_name check_menu_entry check_menu_path)
                          )
                        )
                      )
                     (set! found 1) ; We ran out of file
                    )
                  )
                )
                (close-input-port stream)  ; close the script file
              )
              filesToCheckList
            )
          )
        )
 
        (set! number_of_scripts (length check_proc_name))         
        (gimp-message (string-append "There are " (number->string number_of_scripts) " scripts to check"))


        ; Now we're going to run through the list of scripts to check, comparing them to the list to check against
        (set! duplicate_count 0)
        (set! script_count 0)
        (set! found 0)
        (for-each
          (lambda (proc_name)                                                   ; loop through the scripts to check
            (gimp-progress-update (/ script_count number_of_scripts))           ; keep the user informed
            (gimp-progress-set-text (string-append "Processing " proc_name))
            
            ; log the current procedure if we are logging everything
            (if (> logging_mode 1) (display (string-append (kp24_normal_log_entry check_proc_name check_filepath check_menu_path check_author_entry check_version_entry script_count) "\r\n") log_stream ))
              
            (if (= same_dir TRUE) 
              (set! found_remainder (member proc_name (list-tail proc_name_list (+ script_count 1))))  ; same directory so only need to search what's left
              (set! found_remainder (member proc_name proc_name_list))                                 ; different directories so need to search the full list
            )
            (if (list? found_remainder)     ; The procedure name was found somewhere else so we have a duplicate
              (begin
                (set! duplicate_count (+ duplicate_count 1))
                (set! list_ref  (- (length proc_name_list) (length found_remainder)) )   ; find the list index of the duplicate
                (gimp-message (string-append "duplicate: " proc_name " in \n" (list-ref check_filepath script_count)  "\nand\n" (list-ref filepath_list list_ref)))
                (if (> logging_mode 0)
                  (display (string-append "\nduplicate:\n" (duplicate_report2 check_proc_name check_filepath check_menu_path check_author_entry check_version_entry script_count) (duplicate_report2 proc_name_list filepath_list menu_entry_list author_entry_list version_entry_list list_ref) "\n") log_stream)
                )
              )
            )
            (set! script_count (+ script_count 1))
          )
          check_proc_name
        )

        ; checking done, now tell the user what we found
        (if (> logging_mode 0)
          (begin
            (display (string-append "\nFinished. Found " (number->string duplicate_count) " duplicates in " (number->string number_of_scripts) " scripts") log_stream)
            (close-output-port log_stream)
            (gimp-message (string-append "Log file: " logging_file_path))
          )
        )
        (gimp-message (string-append "Finished. Found " (number->string duplicate_count) " duplicates in " (number->string number_of_scripts) " scripts"))
  )
)

; ##################################################################################################
; This is the main routine that gets called from the user interface
; ##################################################################################################
(define (script-fu-kp24_test_for_duplicate_scripts Unused which_check_option explicit_directory logging_mode log_file)
  (let* ( 
           (varFileList '()) (filesToCheckList '()) (same_dir TRUE)
        )

        (gimp-progress-init "Building list of files" -1)
        (cond 
           ((= which_check_option 0) ; Check all installed
             (begin
                (set! varFileList (kp24_buildScriptFileListNew (kp24_installedScriptDirectories)))
                (set! filesToCheckList (list-copy varFileList)) 
                (set! same_dir TRUE)
             )  
           )
           ((= which_check_option 1) ; Compare single directory with itself
             (begin
                (set! varFileList (kp24_buildScriptFileListNew (list explicit_directory)))
                (set! filesToCheckList (list-copy varFileList))               
                (set! same_dir TRUE)
             )
           )
           ((= which_check_option 2) ; Compare single directory with installed
             (begin
                (set! varFileList (kp24_buildScriptFileListNew (kp24_installedScriptDirectories)))
                (set! filesToCheckList (kp24_buildScriptFileListNew (list explicit_directory)))
                (set! same_dir FALSE)
             )
           )
           ((else) (error "Unknown option for which_check_option"))
        )

        (kp24_checkFilesWithinDirectories varFileList filesToCheckList same_dir logging_mode log_file) ; go and do all the hard work
  )
)

(script-fu-register "script-fu-kp24_test_for_duplicate_scripts"
                    selfmenuentry    ; I prefer to have all of these defined at the top of the file
                    selftipstrip     ; where they are easier to find - especially for someone else
                    selfcopyright    ; who would like to change the Menu text and location.                   
                    selfauthor       ; Ironically that makes the log output of this file less useful!
                    selffileversion
                    ""               ; Don't need to specify image type as this script doesn't do anything to images.
                    SF-TEXT _"Usage" _"'All installed scripts'\n will check the are no duplicates within all the installed scripts\n\n'Single directory within itself'\n will check all the scripts in the directory you specify don't have any duplicates among themselves.\n\n'Single directory against installed scripts'\n will check that the scripts in the specified directory don't duplicate any of the installed scripts."
                    SF-OPTION _"Check What?" '(_"All installed scripts" _"Single directory within itself" _"Single directory against installed scripts")
                    SF-DIRNAME _"Single Directory" (string-append gimp-directory DIR-SEPARATOR "scripts")
                    SF-OPTION _"Logging" '(_"None" _"Duplicates only" _"Everything")
                    SF-STRING _"Log File Name" "duplicate_scripts.txt"
                    )

(script-fu-menu-register "script-fu-kp24_test_for_duplicate_scripts" selfmenuroot)
