#! @SH@
# determine if help is asked for \
opts="`echo $@ | @SED@ 's/\-help//'` -- `echo $@ | @SED@ 's/^\(.*\(-help\).*\|.*\)$/\2/'`"
#if getopts "hVDrbiw" var; if then opts="-- -help"; else opts="$@"; fi
# the next line restarts using wish \
exec @TCL@ "$0" $opts &
# if this is shell, exit by now (line needed for background run) \
exit

# file: $isip/util/dev/tkdiff/tkdiff.tcl
# version: $Id: isip_tkdiff.tcl 9497 2004-05-24 21:09:34Z raghavan $

# declare some global variables
#
global line_count current_line current_index jump_pos last_bg
global p pd ph v_flag D_opt r_opt diff_opts files num_v_opts
global cvs

set p ""
set pd ""
set ph ""
set last_bg ""
set cvs "cvs"

# define some fonts to be used
#
global fonts font_list

set fonts(6) "-misc-fixed-medium-r-normal-*-*-60-*-*-c-*-iso8859-*"
set fonts(10) "*-times-medium-r-normal--*-100-*"
set fonts(12) "*-times-bold-r-normal--*-120-*"
set fonts(14) "*-times-bold-r-normal--*-140-*"

set font_list($fonts(6)) {Misc 6}
set font_list($fonts(10)) {Times 10}
set font_list($fonts(12)) {Times-Bold 12}
set font_list($fonts(14)) {Times-Bold 14}

# define the colors
#
global colors
set colors(deleted) "red"
set colors(inserted) "yellow"
set colors(main) "white"

# method: loadFile
#
# load a file into text widget
#
proc loadFile {w fp} {
    
    global line_count
    set line_count 0
    $w delete 1.0 end
    while {! [eof $fp]} {
	incr line_count
	set str [gets $fp]
	$w insert end "$str\n"
    }
}


# method: applyDiff
#
# scan through the differences, as reported by diff, and modify the text
# buffer to display these changes. red means that the line has been deleted,
# yellow means that the line has been inserted.
#
proc applyDiff {w fp} {

    global line_count current_line current_index p jump_pos colors

    set current_line 0
    set current_index 0
    set jump_pos "1.0"

    set CHANGE 1
    set DELETE 2
    set INSERT 3
    
    set FILE1 10
    set FILE_DELIM 11
    set FILE2 12

    set state 0
    set sub_state 0
    set n0 0
    set n1 0
    set n2 0
    set n3 0
    
    # offset is the current number of inserted lines
    #
    set offset 0

    # initialized means at least one change is found
    #
    set initialized 0


    #  The normal output contains lines of these forms:
    #
    #    n17 a n3,n4n1,n2 d n3n1,n2 c n3,n4
    #
    #  where n1 and n2 represent lines file1 and n3 and n4 represent lines in
    #  file2 These lines resemble ed(1) commands to convert file1 to
    #  file2. By exchanging a for d and reading backward, file2 can be
    #  converted to file1. As in ed , identical pairs, where n1=n2 or
    #  n3=n4, are abbreviated as a single number.
    #
    #  Following each of these lines come all the lines that are affected in
    #  the first file flagged by `<', then all the lines that are affected in
    #  the second file flagged by `>'.


    while {! [eof $fp]} {
	
	set str [gets $fp]
#	puts "reading line $str, state = $state, sub_state = $sub_state"
	switch $state {
	    
	    0  
	    {
		if [regexp {[0-9]+c[0-9]+} $str] {
		    set initialized 1

		    if [regexp {^([0-9]+),([0-9]+)c([0-9]+),([0-9]+)$} $str \
			    dummy n1 n2 n3 n4] {
		    } elseif [regexp {^([0-9]+),([0-9]+)c([0-9]+)$} $str \
			    dummy n1 n2 n3] {
			set n4 $n3
		    } elseif [regexp {^([0-9]+)c([0-9]+),([0-9]+)$} $str \
			    dummy n1 n3 n4] {
			set n2 $n1
		    } elseif [regexp {^([0-9]+)c([0-9]+)$} $str dummy n1 n3] {
			set n2 $n1
			set n4 $n3
		    }

		    set state $CHANGE
		    set sub_state $FILE1
		    
		    set skip_count1 [expr $n2 - $n1 + 1]
		    set skip_count2 [expr $n4 - $n3 + 1]
		    set sc1 $skip_count1
		    set sc2 $skip_count2

#		    puts "read a CHANGE command, sc1 = $skip_count1, sc2 = $skip_count2, $str"
		} elseif [regexp {[0-9]+d[0-9]+} $str] {

		    set initialized 1

		    if [regexp {^([0-9]+),([0-9]+)d([0-9]+),([0-9]+)$} $str \
			    dummy n1 n2 n3 n4] {
		    } elseif [regexp {^([0-9]+),([0-9]+)d([0-9]+)$} $str \
			    dummy n1 n2 n3] {
			set n4 $n3
		    } elseif [regexp {^([0-9]+)d([0-9]+),([0-9]+)$} $str \
			    dummy n1 n3 n4] {
			set n2 $n1
		    } elseif [regexp {^([0-9]+)d([0-9]+)$} $str dummy n1 n3] {
			set n2 $n1
			set n4 $n3
		    }
		    set state $DELETE
		    set sub_state $FILE1
		    
		    set skip_count1 [expr $n2 - $n1 + 1]
		    set skip_count2 [expr $n4 - $n3 + 1]

		    set sc1 $skip_count1
		    set sc2 $skip_count2

		    # needed for line numbering
		    incr n3

#		    puts "read a DELETE command, sc1 = $skip_count1, sc2 = $skip_count2, $str"
		} elseif [regexp {[0-9]+a[0-9]+} $str] {

		    set initialized 1

		    if [regexp {^([0-9]+),([0-9]+)a([0-9]+),([0-9]+)$} $str \
			    dummy n1 n2 n3 n4] {
		    } elseif [regexp {^([0-9]+),([0-9]+)a([0-9]+)$} $str \
			    dummy n1 n2 n3] {
			set n4 $n3
		    } elseif [regexp {^([0-9]+)a([0-9]+),([0-9]+)$} $str \
			    dummy n1 n3 n4] {
			set n2 $n1
		    } elseif [regexp {^([0-9]+)a([0-9]+)$} $str dummy n1 n3] {
			set n2 $n1
			set n4 $n3
		    }
		    set state $INSERT
		    set sub_state $FILE2
		    
		    set skip_count1 [expr $n2 - $n1 + 1]
		    set skip_count2 [expr $n4 - $n3 + 1]

		    set sc1 $skip_count1
		    set sc2 $skip_count2

#		    puts "read an INSERT command, sc1 = $skip_count1, sc2 = $skip_count2, $str"
		} elseif [regexp {Binary files .* and .* differ} $str] {

		    # binary files, don't try to display
		    mesgBox "tkdiff message" "$str"
		    destroyWin $p
		    return
		} elseif {[regexp {^\s*$} $str] == 0} {
		    puts "ERROR: unknown command, $str"
		    exit
		}
		
		# catch up on line numbers
		#
		for {set i [expr $current_line + 1]} {$i < $n3} {incr i} {
		    
		    incr current_index
		    incr current_line
#		    puts "adding line number $current_line to pos $current_index"
		    $w insert $current_index.0 \
			    [format "%5.5d  " $current_line] line_number
		    incr line_count -1
		}
	    }

  	    1
  	    {
		
#  		puts "n1=$n1,n2=$n2,n3=$n3,n4=$n4,of=$offset,sc1=$skip_count1,sc2=$skip_count2,sc1=$sc1,sc2=$sc2 for CHANGE_MULTI"
		
  		switch $sub_state {

  		    10
  		    {
  			if {[regexp "^<" $str] == 0} {
  			    puts "ERROR: state CHANGE_MULTI:FILE1, >$str<"
  			    exit
  			}
			incr skip_count1 -1
			if {$skip_count1 == 0} {
			    set sub_state $FILE_DELIM
			}
  		    }

  		    11
  		    {
  			if {[regexp "^---$" $str] == 0} {
  			    puts "ERROR: state CHANGE_MULTI:FILE_DELIM, >$str<"
  			    exit
  			}
  			set sub_state $FILE2

			set l1 [expr $n1 + $offset]
			set l2 [expr $l1 + $sc1]

			for {set i 0} {$i < $sc1} {incr i} {
			    incr line_count -1
			    incr current_index
			    $w insert $current_index.0 "       " deleted
			}

  			$w tag add deleted $l1.0 $l2.0
  			$w tag add modified $l1.0 $l1.5
			
  		    }
		    
  		    12
  		    {
  			if {[regexp "^> (.*)$" $str dummy new_line] == 0} {
  			    puts "ERROR: state CHANGE_SINGLE:FILE_DELIM, >$str<"
  			    exit
  			}
			
  			set line [expr $n1 + $offset + $sc1 ]

  			$w insert $line.0 "$new_line\n" inserted
			
			incr current_index
			incr current_line

			if {$line != $current_index} {
			    puts "ERROR1: line = $line, current_index = $current_index"
			    exit
			}
			$w insert $current_index.0 \
			    [format "%5.5d  " $current_line] inserted

  			incr offset 1
			incr skip_count2 -1

			if {$skip_count2 == 0} {
			    set state 0
			}

  		    }
  		}
  	    }

  	    2
  	    {
		
#  		puts "n1=$n1,n2=$n2,n3=$n3,n4=$n4,of=$offset,sc1=$skip_count1,sc2=$skip_count2,sc1=$sc1,sc2=$sc2 for DELETE"
		
  		switch $sub_state {

  		    10
  		    {
  			if {[regexp "^<" $str] == 0} {
  			    puts "ERROR: state DELETE:FILE1, >$str<"
  			    exit
  			}
			incr skip_count1 -1
			if {$skip_count1 == 0} {
			    set state 0
			    
			    set l1 [expr $n1 + $offset]
			    set l2 [expr $l1 + $sc1]
			    
			    for {set i 0} {$i < $sc1} {incr i} {
				incr line_count -1
				incr current_index
				$w insert $current_index.0 "       " \
					deleted
			    }

			    $w tag add deleted $l1.0 $l2.0
			    $w tag add modified $l1.0 $l1.5
			}
			
  		    }
  		}
  	    }
	    
	    3
  	    {
		
#  		puts "n1=$n1,n2=$n2,n3=$n3,n4=$n4,of=$offset,sc1=$skip_count1,sc2=$skip_count2,sc1=$sc1,sc2=$sc2 for INSERT"
		
  		switch $sub_state {

  		    12
  		    {
  			if {[regexp "^> (.*)$" $str dummy new_line] == 0} {
  			    puts "ERROR: state CHANGE_SINGLE:FILE_DELIM, >$str<"
  			    exit
  			}
			
  			set line [expr $n1 + $offset + $sc1 ]

  			$w insert $line.0 "$new_line\n" inserted

			incr current_index
			incr current_line

			if {$line != $current_index} {
			    puts "ERROR2: line = $line, current_index = $current_index"
			    exit
			}
			$w insert $current_index.0 \
			    [format "%5.5d  " $current_line] inserted

			if {$skip_count2 == $sc2} {
			    $w tag add modified $line.0 $line.5
			}

  			incr offset 1
			incr skip_count2 -1

			if {$skip_count2 == 0} {
			    set state 0
			}

  		    }
  		}
  	    }
	    
	}
    }

    if {$initialized == 0} {
	mesgBox "tkdiff message" "Files are identical"
	destroyWin $p
	return
    }

    # catch up on final line numbers
    #
    while {$line_count > 1} {
	
	incr current_index
	incr current_line
	$w insert $current_index.0 \
		[format "%5.5d  " $current_line] line_number
	incr line_count -1
    }

    $w tag configure deleted -background $colors(deleted)
    $w tag configure inserted -background $colors(inserted)
    $w mark set insert 1.0

    focus $w

}

# method: nextChange
#
proc nextChange {w} {
    global jump_pos
    set next_range [$w tag nextrange modified $jump_pos]
    
    jumpAndShowChange $w $next_range
}

# method: prevChange
#
proc prevChange {w} {
    global jump_pos

    # go back one from the jump_pos
    #
    set jump_line [expr [lindex [split $jump_pos "."] 0] -1]
    set jp "$jump_line.0"

    # find the previous modified tag
    #
    set prev_range [$w tag prevrange modified $jp]
    
    jumpAndShowChange $w $prev_range
}

# method: jumpAndShowChange
#
# jump to the next change in the buffer
#
proc jumpAndShowChange {w new_range} {
    
    global jump_pos last_bg colors
    
    if {[regexp {^\s*$} "$new_range"] == 0} {
	
	if {! [string match "$last_bg" ""]} {
	    $w tag configure jump_line -foreground black -background $last_bg \
		    -overstrike 0
	    $w tag remove jump_line 1.0 end
	}

	# determine what color the background should be
	#
	set tags [$w tag names [lindex $new_range 0]]
	if {[lsearch -exact $tags "deleted"] != -1} {
	    set last_bg [$w tag cget deleted -background]
	} elseif {[lsearch -exact $tags "inserted"] != -1} {
	    set last_bg [$w tag cget inserted -background]
	} else {
	    set last_bg $colors(main)
	}

	# add the tag -- the eval is needed since the entire range
	# is in one variable
	#
	eval "$w tag add jump_line $new_range"

	# change the highlight on the current jump point
	#
	$w tag configure jump_line -background black -foreground $last_bg \
		-overstrike 1

	# scroll to the jump point
	#
	$w see [lindex $new_range 0]
	$w yview scroll -2 pages
	$w see [lindex $new_range 0]
	$w yview scroll +2 pages
	$w see [lindex $new_range 0]

	# save the jump point
	#
	set jump_pos [lindex $new_range 1]
    } else {
	bell
    }
}

# method: displayText
#
# build the display screen used for text
#
proc displayText {} {

    global p fonts view_title

    if [winfo exists $p] {
	
	wm deiconify $p
	raise $p
	
    } else {
	
	# change the name of the toplevel widget
	#
	wm withdraw .
	set p ".parent"
	toplevel $p
	wm title $p "tkdiff"
	
	# create the text widget
	#
	text $p.t -yscrollcommand "$p.scrolly set" -wrap none \
		-xscrollcommand "$p.scrollx set"
	scrollbar $p.scrolly -command "$p.t yview"
	scrollbar $p.scrollx -command "$p.t xview" -orient horizontal
	pack $p.scrollx -side bottom -fill x
	pack $p.scrolly -side right -fill y
	frame $p.mbar_f 
	pack $p.mbar_f -side top -fill x
	pack $p.t -expand true -fill both
	
	# create the menu
	#
	set mw $p.mbar_f
	menubutton $mw.file -font $fonts(14) -text "File" -menu $mw.file.menu 
	menubutton $mw.help -font $fonts(14) -text "Help" -menu $mw.help.menu
	
	pack $mw.file -side left
	pack $mw.help -side right
	
	# fill in the file menu
	#
	menu $mw.file.menu -font $fonts(12)
	
	$mw.file.menu add command -label "Rescan" -command rescan
	$mw.file.menu add separator
	$mw.file.menu add command -label "Close" -command closeOrExit
	$mw.file.menu add command -label "Quit" -command cleanExit
	
	menu $mw.help.menu -font $fonts(12)
	
	$mw.help.menu add command -label "General" -command helpGeneral
	$mw.help.menu add command -label "Keys" -command helpKeys
	
	bind $p.t <KeyPress> {    
	    
	    switch %K {
		
		"space" { nextChange $p.t }
		"Home"  { $p.t mark set insert 1.0 }
		"End"  { $p.t mark set insert end }
		"Up"  { $p.t yview scroll -1 unit}
		"Down"  { $p.t yview scroll 1 unit}
		"q" { closeOrExit }
		"Q" { closeOrExit }
		"r" { rescan }
		"R" { rescan }

		"n" { nextChange $p.t }
		"N" { nextChange $p.t }
		"p" { prevChange $p.t }
		"P" { prevChange $p.t }
	    }
	}

	wm geometry $p "509x683+518+0"
	wm protocol $p WM_DELETE_WINDOW closeOrExit
    }

    # set the title
    #
    wm title $p $view_title
}

# method: displayHelp
#
# build the display screen used for text
#
proc displayHelp {} {

    global ph fonts

    if [string match $ph ".phelp"] {
	
	wm deiconify $ph
	raise $ph

    } else {
	
	# change the name of the toplevel widget
	#
	wm withdraw .
	set ph ".ph"
	toplevel $ph
	wm title $ph "tkdiff help"
	
	# create the text widget
	#
	text $ph.t -yscrollcommand "$ph.scroll set" -wrap none
	scrollbar $ph.scroll -command "$ph.t yview"
	pack $ph.scroll -side right -fill y
	frame $ph.mbar_f 
	pack $ph.mbar_f -side top -fill x
	pack $ph.t -expand true -fill both
    }
}
    
# method: displayDir
# 
# build the display screen used for directories
#    
proc displayDir {} {

    global pd fonts

    if [string match $pd ".pdir"] {
	
	raise $pd

    } else {
	
	# change the name of the toplevel widget
	#
	wm withdraw .
	set pd ".pdir"
	toplevel $pd
	wm title $pd "tkdiff Directory Scan"
	
	# create the listbox for the added files
	#
	frame $pd.a
	label $pd.labela -text "Added Files:"  -anchor w
	listbox $pd.a.lb -yscrollcommand "$pd.a.scroll set" -width 50
	scrollbar $pd.a.scroll -command "$pd.a.lb yview"
	pack $pd.a.scroll -side right -fill y -expand true
	pack $pd.a.lb -side left -fill both -expand true
	
	# create the listbox for the deleted files
	#
	frame $pd.d
	label $pd.labeld -text "Deleted Files:"  -anchor w
	listbox $pd.d.lb -yscrollcommand "$pd.d.scroll set" -width 50
	scrollbar $pd.d.scroll -command "$pd.d.lb yview"
	pack $pd.d.scroll -side right -fill y -expand true
	pack $pd.d.lb -side left -fill both -expand true

	# create the listbox for the modified files
	#
	frame $pd.m
	label $pd.labelm -text "Modified Files:" -anchor w
	listbox $pd.m.lb -yscrollcommand "$pd.m.scroll set" -width 50
	scrollbar $pd.m.scroll -command "$pd.m.lb yview"
	pack $pd.m.scroll -side right -fill y -expand true
	pack $pd.m.lb -side left -fill both -expand true

	set mw $pd.mbar_f
	frame $mw

	pack $mw $pd.labelm $pd.m $pd.labela $pd.a $pd.labeld $pd.d \
		-side top -fill x -expand true

	# create the menu
	#
	menubutton $mw.file -font $fonts(14) -text "File" -menu $mw.file.menu 
	menubutton $mw.help -font $fonts(14) -text "Help" -menu $mw.help.menu
	
	pack $mw.file -side left
	pack $mw.help -side right
	
	# fill in the file menu
	#
	menu $mw.file.menu -font $fonts(12)
	
	$mw.file.menu add command -label "Rescan" -command main
	$mw.file.menu add separator
	$mw.file.menu add command -label "Quit" -command cleanExit
	
	menu $mw.help.menu -font $fonts(12)
	
	$mw.help.menu add command -label "General" -command helpGeneral
	$mw.help.menu add command -label "About" -command helpAbout
	$mw.help.menu add command -label "Keys" -command helpKeys
	
	wm protocol $pd WM_DELETE_WINDOW cleanExit

	# setup the bindings for the listboxes
	#
	bind $pd.m.lb <Double-Button-1> {
	    set fname [%W get [%W curselection]]
	    eval $dir_commands($fname)
	}

	# setup the bindings for the listboxes
	#
	bind $pd.a.lb <Double-Button-1> {
	    set fname [%W get [%W curselection]]
	    eval $dir_commands($fname)
	}

	# setup the bindings for the listboxes
	#
	bind $pd.d.lb <Double-Button-1> {
	    set fname [%W get [%W curselection]]
	    eval $dir_commands($fname)
	}
    }
}

# method: mesgBox
#
# display a quick message
#
proc mesgBox { label mesg } {
    
    global fonts

    # default message
    #
    if {$mesg == "" } {
	set mesg "I'm sorry, no help is available for this area. Please\
		consult the user's guide for more information"
    }
    
    set w ".help"

    # don't recreate the help window, try to reuse existing one
    #
    if { [winfo exists $w] == 1 } {
	wm deiconify $w
	raise $w

	# reconfigure the message
	#
	$w.msg configure -text $mesg
	wm title $w $label

    } else {

	# create a new window
	#
	wm withdraw .
	toplevel $w
	wm title $w $label
	button $w.dis -text dismiss -command "destroyWin $w" -pady 0 
	frame $w.msg_f -width 400
	pack $w.dis -side bottom -fill x
	pack $w.msg_f -side top -padx 20 -pady 20

	# set the message
	#
	message $w.msg -width 100c 

	pack $w.msg -in $w.msg_f -side top
	$w.msg configure -text $mesg
    }
}

# method: notCvsDir
#
# determine if the specified directory is under CVS or not
#
proc notCvsDir {my_dir} {
    set cvs_dir "$my_dir/CVS"
    if {[file isdirectory $cvs_dir] == 0} {
	return 1
    } else {
	return 0
    }
}

# method: notRcsFile
#
# determine if the given file is under RCS control
#
proc notRcsFile {my_file} {
    
    set rcs_file "$my_file,v"
    if {[file exists $rcs_file] == 0} {
	return 1
    } else {
	return 0
    }
}

# method: main
#
# parse the command line and run the appropriate methods
#
proc main {} {
    
    global argv debug cvs
    global p v_flag v_opts diff_opts files 

    # if the text window exists, destroy it
    #
    catch {destroy $p}

    set v_flag 0
    set v_opts {}
    set diff_opts ""
    set state 0
    set files {}
    set help_flag 0
    set parse_error 0
    set debug 0

    # test that diff is GNU diff
    #
    set fp [open "| diff --version" "r"]
    set diff_ver [gets $fp]
    close $fp
#    if {! [string match $diff_ver  "diff - GNU diffutils version 2.8.1"] } {
#	puts "you must have GNU diff v2.8.1 to use tkdiff"
#	exit
#    }

    foreach arg [split $argv] {
	
	switch $state {
	    
	    0 {
		switch -- "$arg" {
		    
		    "-V" { set v_flag 1}
		    "-b" { set diff_opts "$diff_opts -b" }
		    "-i" { set diff_opts "$diff_opts -i" }
		    "-w" { set diff_opts "$diff_opts -w" }
		    "-D" { set state 1 }
		    "-r" { set state 2 }
		    "-n" { set cvs "$cvs -n" }
		    "-debug" { set debug 1 }
		    "-help" { set help_flag 1 }
		    default { 
			if {[file exists $arg]} {
			    lappend files $arg 
			} else {
			    mesgBox "tkdiff Error" "Error: file $arg not found"
			    set parse_error 1
			}
		    }
			    
	    }   }
	    1 { 
		set arg [string map {_ \ } $arg]
		lappend v_opts "-D \"$arg\""
		set state 0
	    }
	    2 { lappend v_opts "-r $arg"; set state 0 }
	}
    }

    if {$parse_error == 1} {
	return
    }

    if {$help_flag == 1} {
	helpGeneral
	return
    }

    if {$state == 1} {
	puts "Error: you must specify a date after -D"
	exit
    }
    if {$state == 2} {
	puts "Error: you must specify a revision after -r"
	exit
    }
    if {[llength $v_opts] > 0} {
	set v_flag 1
    }

    if {[llength $v_opts] > 2} {
	puts "Error: you can specify at most two revisions to compare"
	exit
    }
    
    # if we are in cvs mode
    #
    switch [llength $files] {

	0 {
	    
	    if {($v_flag == 0) && [notCvsDir "."]} {
		puts "Error: you must specify at least one file or run in cvs mode"
		exit
	    }

	    scanDirCvs "."
	}

	1 {

	    set file1 [lindex $files 0]
	    
	    switch [file type $file1] {
		"file" {
		    if {![notCvsDir [file dirname $file1]] } {
			scanFileCvs $file1
		    } elseif {![notRcsFile $file1]} {
			scanFileRcs $file1
		    } elseif {$v_flag == 0} {
			puts "Error: you must specify two files or run in cvs/rcs mode"
			exit
		    }
		}
		"directory" {
		    if {($v_flag == 0) && [notCvsDir $file1]} {
			puts "Error: you must specify two dirs or run in cvs mode"
			exit
		    }
		    scanDirCvs $file1
		}
		default {puts "Error: bad file type for $file1"; exit }
	    }
	}

	2 {
	    
	    set file1 [lindex $files 0]
	    set file2 [lindex $files 1]
	    
	    if {$v_flag == 0} {

		set type1 [file type $file1]
		set type2 [file type $file2]

		if {[string match $type1 $type2] == 0} {
		    puts "Error: two arguments of different file types"
		    exit
		}
		switch $type1 {
		    "file" { scanFile $file1 $file2 }
		    "directory" { scanDir $file1 $file2 }
		    default { "Error: bad file type"; exit }
		}
	    } else {
		puts "Error: only specify one argument in CVS mode"
		exit
	    }
	}
	
	default { helpGeneral }
	    
    }
}

# method: scanFile
#
# scan two files and display the differences graphically
#
proc scanFile {f1 f2} {

    global p diff_opts file1 file2 v_flag view_title cvs_mode

    set cvs_mode 0

    # make sure both arguments are files
    #
    if [file isdirectory $f1] {
	mesgBox "tkdiff error" "Error: cannot view directory with file viewer"
	destroyWin $p
	return
    }
    
    if  [file isdirectory $f2] {
	mesgBox "tkdiff error" "Error: cannot view directory with file viewer"
	destroyWin $p
	return
    }
    
    if {! [file readable $f1]} {
	mesgBox "tkdiff error" "Error: cannot read $f1"
	destroyWin $p
	return
    }

    if {! [file readable $f2]} {
	mesgBox "tkdiff error" "Error: cannot read $f2"
	destroyWin $p
	return
    }
    
    if {$v_flag == 0} {
	set view_title "tkdiff $f1 $f2"
    }

    displayText

    set file1 $f1
    set file2 $f2

    $p.t configure -state normal
    set fp [open $file1 "r"]
    loadFile $p.t $fp
    close $fp
    
    set fp [open "| diff $diff_opts $file1 $file2" "r"]
    applyDiff $p.t $fp
    catch {close $fp}
    
    if {[winfo exists $p] == 1} {
	$p.t configure -state disabled
    }
}



# method: viewFile
#
# just load one file into the text display
#
proc viewFile {f1} {

    global file1 p view_title
    
    if [file isdirectory $f1] {
	mesgBox "tkdiff error" "Error: cannot view directory with file viewer"
	destroyWin $p
	return
    }

    if {! [file readable $f1]} {
	mesgBox "tkdiff error" "Error: cannot read $f1"
	destroyWin $p
	return
    }
    
    set view_title [file tail $f1]

    displayText
    
    set file1 $f1
    set file2 ""

    $p.t configure -state normal
    set fp [open $file1 "r"]
    loadFile $p.t $fp
    close $fp
    $p.t configure -state disabled
}

# method: rescan
#
# repeat the file scan in case external changes were made
#
proc rescan {} {
    global file1 file2 cvs_mode jump_pos p

    if [string match $file2 ""] {
	viewFile $file1
    } else {
	set old_jump_pos $jump_pos
	
	if {$cvs_mode} {
	    scanFileCvs $file1
	} else {
	    scanFile $file1 $file2
	}

	set jump_line [expr [lindex [split $old_jump_pos "."] 0] -1]
	set jp "$jump_line.0"

	# find the previous modified tag
	#
	set next_range [$p.t tag nextrange modified $jp]
	jumpAndShowChange $p.t $next_range
    }
}

# method: closeOrExit
#
# if in file mode, just quit. If in directory mode, kill the file text
# viewer and raise the directory viewer
#
proc closeOrExit {} {
    global pd p
    
    if [string match $pd ".pdir"] {
	wm iconify $p
	raise $pd
    } else {
	cleanExit
    }
}

# method: cleanExit
#
# clean up temp files and exit
#
proc cleanExit {} {
    
    global temp_files

    catch {
	eval "exec rm -fr [join $temp_files]"
    }

    exit
}

# method: getModuleNameCvs
#
# get the cvs module name
#
proc getModuleNameCvs {dir} {

    set rep [getRepositoryNameCvs $dir]
    
    global env
    if {! [file isdirectory $dir]} {
	puts "Error: $dir not a directory"
	exit
    }
    
    # first try to just use CVSROOT
    #
    set root ""
    catch {set root $env(CVSROOT)}

    # if CVSROOT is not set, then read out of the Root file
    #
    if [string match $root ""] {

	set root_file "$dir/CVS/Root"
	if {! [file exists $root_file]} {
	    puts "Error: cannot read $root_file"
	    exit
	}
	set fp [open $root_file "r"]
	set root [gets $fp]
	close $fp
    }

    # make sure that the repository is based on the root
    #
    if {[string first $root $rep] == 0} {
	# pull the root from the repository location to obtain the module
	#
	set module [string range $rep [expr [string length $root]+1] end]
    } else {
	set module $rep
    }
    
    return $module
}

# method: getRepositoryNameCvs
#
# get the cvs repository name
#
proc getRepositoryNameCvs {dir1} {

    global env
    if {! [string match [file type $dir1] "directory"]} {
	puts "Error: $dir1 not a directory"
	exit
    }
    
    # the repository file contains the name of the module with CVSROOT
    # prepended. open the file and read the string
    #
    set rep_file "$dir1/CVS/Repository"
    if {! [file exists $rep_file]} {
    	puts "Error: cannot read $rep_file"
	exit
    }
    set fp [open $rep_file "r"]
    set rep [gets $fp]
    close $fp

    return $rep
}

# method: getWorkingDateCvs
#
# get the date from which this cvs version was based
#
proc getWorkingDateCvs {file1} {

    if [string match [file type $file1] "file"] {
	set dir_mode 0
    } elseif [string match [file type $file1] "directory"] {
	set dir_mode 1
	set dates {}
    } else {
	puts "Error: invalid file type for $file1"
	exit
    }

    set fp [open "| cvs status -l $file1" "r"]
    while {! [eof $fp]} {
	set str [gets $fp]
	
	if [regexp {Working revision:\s+[\d\.]+\s*(.*)$} $str dummy vdate] {
	    
	    if {$dir_mode == 0} {
		catch {close $fp}
		return $vdate
	    } else {
		lappend dates $vdate
	    }
	}
	
    }
    catch {close $fp}
    
    if {$dir_mode == 1} {
	
	set sdate [lsort -decreasing -command cvsDateSort $dates]
	set vdate [lindex $sdate 0]
	return $vdate
    }
	    
    puts "Error: could not determine version for $file1"
    exit
}
    
# method: cvsDateSort
#
proc cvsDateSort {date1 date2} {
    
    # easy and likely case first
    #
    if [string match $date1 $date2] {
	return 0
    }

    set month(Jan) 0
    set month(Feb) 1
    set month(Mar) 2
    set month(Apr) 3
    set month(May) 4
    set month(Jun) 5
    set month(Jul) 6
    set month(Aug) 7
    set month(Sep) 8
    set month(Oct) 9
    set month(Nov) 10
    set month(Dec) 11

    # match Tue Aug 29 18:41:12 2000
    set date_pattern {^\w\w\w\s+(\w\w\w)\s+(\d+)\s+(\d+):(\d+):(\d+)\s+(\d+)$}
    
    if {! [regexp $date_pattern $date1 dummy mon1 day1 h1 m1 s1 year1]} {
	puts "Error: bad date $date1"
	return 0
    }
    if {! [regexp $date_pattern $date2 dummy mon2 day2 h2 m2 s2 year2]} {
	puts "Error: bad date $date2"
	return 0
    }
    
    if {$year1 > $year2} {
	return 1
    } elseif {$year1 < $year2} {
	return -1
    } else {
	
	if {$month($mon1) > $month($mon2)} {
	    return 1
	} elseif {$month($mon1) < $month($mon2)} {
	    return -1
	} else {
	    
	    if {$day1 > $day2} {
		return 1
	    } elseif {$day1 < $day2} {
		return -1
	    } else {
		
		if {$h1 > $h2} {
		    return 1
		} elseif {$h1 < $h2} {
		    return -1
		} else {

		    if {$m1 > $m2} {
			return 1
		    } elseif {$m1 < $m2} {
			return -1
		    } else {
			
			if {$s1 > $s2} {
			    return 1
			} elseif {$s1 < $s2} {
			    return -1
			} else {
			    puts "Error: bad case"
			    exit
			}
		    }
		}
	    }
	}
    }
}
    

# method: scanFileCvs
#
proc scanFileCvs {f1} {

    global v_opts p diff_opts file1 file2 v_flag view_title cvs_mode cvs

    set cvs_mode 1
    set file2 ""

    # make sure both arguments are files
    #
    if [file isdirectory $f1] {
	mesgBox "tkdiff error" "Error: cannot view directory with file viewer"
	destroyWin $p
	return
    }
    
    if {! [file readable $f1]} {
	mesgBox "tkdiff error" "Error: cannot read $f1"
	destroyWin $p
	return
    }
    
    set view_title "tkdiff $f1 [join $v_opts]"

    displayText

    set file1 $f1

    # first determine the full module and filename
    #
    set full_name "[getModuleNameCvs [file dirname $f1]]/[file tail $f1]"
    
    # the basis file (the one to which changes are applied) is either
    # the current version, if no v_opts are specified, or the first
    # option on the command line.
    #
    if {[llength $v_opts] == 0} {
	set com "$cvs checkout -p $full_name"
    } else {
	set com "$cvs checkout -p [lindex $v_opts 0] $full_name"
    }

    set fp [open "| $com 2>/dev/null" "r"]
    $p.t configure -state normal
    loadFile $p.t $fp
    close $fp
    
#    puts "| $cvs diff $diff_opts [join $v_opts] $file1"

    # the diff stream will come from cvs diff, not system diff
    #
    set fp [open "| $cvs diff $diff_opts [join $v_opts] $file1" "r"]
    
    # jump through the CVS stuff until we get to a diff line, if there
    # is no output it means the file was not changed
    #
    set found_diff 0
    while {! [eof $fp]} {

	set str [gets $fp]
	
	# is this the diff line?
	#
	if [regexp "^diff " $str] {
#	    puts $str
	    set found_diff 1
	    break
	}
    }

    applyDiff $p.t $fp
    catch {close $fp}
    
    if {[winfo exists $p] == 1} {
	$p.t configure -state disabled
    }
}


# method: scanFileRcs
#
proc scanFileRcs {file1} {
    global v_opts temp_files view_title v_flag

    set file_tail [file tail $file1]
    
    set f2 $file1

    set f1 "/tmp/tkdiff_[pid]_$file_tail"
    
    catch {
	eval "exec co -p $file1 > $f1"
    }
    
    lappend temp_files $f1
    
    set view_title "tkdiff $file_tail ([lindex $v_opts 0] vs local)"

    scanFile $f1 $f2
}


# method: scanDir
#
proc scanDir {dir1 dir2} {

    global pd v_flag dir_commands

    displayDir
    
    catch {unset dir_commands}

    set state 0

    $pd.d.lb delete 0 end
    $pd.a.lb delete 0 end
    $pd.m.lb delete 0 end

    set fp [open "| diff $dir1 $dir2" "r"]
    
    while {! [eof $fp]} {

	set str [gets $fp]
	
	set sstr [split $str]
	set f1 ""
	set f2 ""
	
	# is a file only in one directory?
	#
	if [regexp "^Only in " $str] {

	    set odir [lindex $sstr 2]
	    set ofile [lindex $sstr 3]
	    set odir [string range $odir 0 [expr [string length $odir] -2]]

	    if [string match [file type $odir/$ofile] "directory"] {
		continue
	    }

	    if [string match "$odir" "$dir1"] {
		$pd.d.lb insert end "$ofile"
		set dir_commands($ofile) "viewFile $dir1/$ofile"
	    } elseif [string match "$odir" "$dir2"] {
		$pd.a.lb insert end "$ofile"
		set dir_commands($ofile) "viewFile $dir2/$ofile"
	    } else {
		puts "Error: $str"
		exit
	    }
	} elseif [regexp "^diff " $str] {
	    
	    set f1 [file tail [lindex $sstr 1]]

	    if [string match [file type "$dir1/$f1"] "directory"] {
		continue
	    }

	    $pd.m.lb insert end "$f1"
	    set dir_commands($f1) "scanFile $dir1/$f1 $dir2/$f1"
	}
    }

    catch { close $fp }
}


# method: scanDirCvs
#

# this method runs cvs diff in directory mode to determine what files
# have changed. it will create a list of such files, the user can then
# select any one file to have its differences displayed. 
#
proc scanDirCvs {dir1} {
    global v_opts temp_files v_flag view_title debug pd v_flag dir_commands
    global cvs
    
    
    displayDir
    
    catch {unset dir_commands}

    set state 0

    $pd.d.lb delete 0 end
    $pd.a.lb delete 0 end
    $pd.m.lb delete 0 end

    set fp [open "| $cvs diff [join $v_opts] $dir1 2>/dev/null" "r"]
    
    while {! [eof $fp]} {

	set str [gets $fp]
	
	set sstr [split $str]
	set f1 ""
	set f2 ""
	
	# has something been removed?
	#
	if [regexp "^cvs diff: cannot find" $str] {

	    set del_file [lindex $sstr 4]

	    $pd.d.lb insert end "$del_file"
	    puts "del_file = $del_file"
	    set dir_commands($del_file) "viewFileRcs $del_file"

	} elseif [regexp "^Index: " $str] {
	    
	    set mod_file [lindex $sstr 1]

	    $pd.m.lb insert end "$mod_file"
	    set dir_commands($mod_file) "scanFileCvs $mod_file"
	}
    }

    catch { close $fp }
}    

# method: helpGeneral
#
proc helpGeneral {} {
    global env ph

    displayHelp

    set fp [open "$env(ISIP_DEVEL)/util/devel/tkdiff/help.txt"]
    loadFile $ph.t $fp
    close $fp
    
}

# method: destroyWin
#
# kill the specified window. if no windows are left, exit
#
proc destroyWin {arg} {
    destroy $arg

    if {[llength [winfo children .]] == 0} {
	cleanExit
    }
}

# call the main program to parse the command line and run the
# appropriate method
#
main

#
# end of file
