namespace eval AdjustTabs {
    variable counter 0
}

proc ::AdjustTabs::checkTabEvent {win} {
    set index [$win index insert]
    let {line col} [split $index .]
    set tabTag [getTabTagAt $win insert]
    set tabOccurs [winHasTabsAtLine $win $line]
    if {!$tabOccurs && ($tabTag eq "")} {
	# no tabs, nothing to do
	return
    } elseif {$tabTag eq ""} {
	# new tab set
	set tabTags [setTabTagAt $win $index]
    } elseif {!$tabOccurs} {
	# existing tab deleted
	set tabTags [removeTabTagAt $win $index]
    } else {
	# existing tab area changed
	set tabTags $tabTag
    }
    foreach tag $tabTags {
	set code [namespace code "adjustWinTagTabs $win $tag"]
	after cancel $code
	after idle $code
    }
}

#
# ::AdjustTabs::removeTabTagAt
# remove tags tab and tab[0-9]+ at $index
# if inside, split area tabi to tabi + tabj
# return name(s) of affected tags
#
proc ::AdjustTabs::removeTabTagAt {win index} {
    set thisTabTag [getTabTagAt $win $index]
    if {$thisTabTag eq ""} {
	return
    }
    set fromIndex [$win index "$index linestart"]
    set toIndex [$win index $fromIndex+1lines]
    $win tag remove tab $fromIndex $toIndex
    if {[$win compare $fromIndex > 1.0]} {
	set aboveTabTag [getTabTagAt $win $fromIndex-1lines]
    } else {
	set aboveTabTag ""
    }
    set belowTabTag [getTabTagAt $win $toIndex]
    if {$aboveTabTag eq "" && $belowTabTag eq ""} {
	# no neighbour tab area
	$win tag remove $thisTabTag $fromIndex $toIndex
	return
    } elseif {$belowTabTag eq ""} {
	# reduce tag area above
	$win tag remove $aboveTabTag $fromIndex $toIndex
	return $aboveTabTag
    } elseif {$aboveTabTag eq ""} {
	# reduce tag area below
	$win tag remove $belowTabTag $fromIndex $toIndex
	return $belowTabTag
    } else {
	# split tag area
	let {fromAround toAround} [$win tag prevrange $aboveTabTag $index]
	$win tag remove $aboveTabTag $fromIndex $toAround
	set newTabTag [genTagName $win]
	lappend result $newTabTag
	$win tag add $newTabTag $toIndex $toAround
	# return this value
	list $aboveTabTag $newTabTag
    }
}

#
# ::AdjustTabs::setTabTagAt
# set tag tab[0-9]+ at $index
# return tag name
#
proc ::AdjustTabs::setTabTagAt {win index} {
    if {[getTabTagAt $win $index] ne ""} {
	return
    }
    set fromIndex [$win index "$index linestart"]
    set toIndex [$win index $fromIndex+1lines]
    $win tag add tab $fromIndex $toIndex
    if {[$win compare $fromIndex > 1.0]} {
	set aboveTabTag [getTabTagAt $win $fromIndex-1lines]
    } else {
	set aboveTabTag ""
    }
    set belowTabTag [getTabTagAt $win $toIndex]
    if {$aboveTabTag eq "" && $belowTabTag eq ""} {
	# no neighbour tab area
	set thisTabTag [genTagName $win]
	$win tag add $thisTabTag $fromIndex $toIndex
	return $thisTabTag
    } elseif {$belowTabTag eq ""} {
	# extend above tab area
	$win tag add $aboveTabTag $fromIndex $toIndex
	return $aboveTabTag
    } elseif {$aboveTabTag eq ""} {
	# extend below tab area
	$win tag add $belowTabTag $fromIndex $toIndex
	return $belowTabTag
    } else {
	# join tab area below and above
	let {belowFrom belowTo} [$win tag ranges $belowTabTag]
	$win tag remove $belowTabTag 1.0 end
	$win tag add $aboveTabTag $fromIndex $belowTo
	return $aboveTabTag
    }
}

#
# ::AdjustTabs::initTabTags
# remove all tags tab[0-9]+
# set tag tab on lines containing \t
# set new tags tab[0-9]+
# return list of set tags tab[0-9]+
#
proc ::AdjustTabs::initTabTags {win} {
    $win tag remove tab 1.0 end
    foreach tag [$win tag names] {
	if {[regexp {^tab[0-9]+$} $tag]} {
	    $win tag remove $tag 1.0 end
	}
    }
    set from 1.0
    set to 2.0
    while {[$win compare $from < end]} {
	if {[$win search \t $from $to] ne ""} {
	    $win tag add tab $from $to
	}
	set from [$win index $from+1lines]
	set to [$win index $to+1lines]
    }
    set result {}
    foreach {from to} [$win tag ranges tab] {
	set tabTag [genTagName $win]
	$win tag add $tabTag $from $to
	lappend result $tabTag
 	after idle\
 	    [namespace code\
 		 [list atjustWinTagTabsBackground $win $tabTag]]
    }
    raise [winfo toplevel .]
    set result
}

#
# ::AdjustTabs::genTagName
# return tag name tab[0-9]+ which is new to $win
#
proc ::AdjustTabs::genTagName {win} {
    variable counter
    while {[lsearch [$win tag names] tab$counter] >= 0} {
	incr counter
    }
    return tab$counter
}

#
# ::AdjustTabs::getTabTagAt
# return first tag tab[0-9]+ occuring at $index
#
proc ::AdjustTabs::getTabTagAt {win index} {
    foreach tag [$win tag names $index] {
	if {[regexp {^tab[0-9]+$} $tag]} {
	    return $tag
	}
    }
}

#
# ::AdjustTabs::winHasTabsAtLine
# return true if \t occurs in $line
#
proc ::AdjustTabs::winHasTabsAtLine {win line} {
    expr {[$win search \t $line.0 $line.end] ne ""}
}

#
# ::AdjustTabs::adjustWinTabAt
# adjust tab tag which occurs in $window at $index
#
proc ::AdjustTabs::adjustWinTabAt {window index} {
    set tabTag [getTabTagAt $window $index]
    adjustWinTagTabs $window $tabTag
}

#
# ::AdjustTabs::atjustWinTagTabsBackground
# 
#
proc ::AdjustTabs::atjustWinTagTabsBackground {win tag} {
    if {![winfo exists $win] || [$win tag ranges $tag] eq ""} {
	return
    }
    while {true} {
	let {from to} [$win tag ranges $tag]
	let {fromLine fromCol} [split $from .]
	let {toLine toCol} [split $to .]
	set someLinesVisible 0
	set allLinesVisible 1
	foreach line [range $fromLine $toLine] {
	    if {[$win dlineinfo $line.0] eq ""} {
		set allLinesVisible 0
	    } else {
		set someLinesVisible 1
	    }
	}
	if {$allLinesVisible} {
	    adjustWinTagTabs $win $tag
	    break
	} elseif {$someLinesVisible} {
	    adjustWinTagTabs $win $tag
	    sleep 200
	} else {
	    sleep 500
	}
    }
    list tabstops of $tag in $win are adjusted
}

#
# ::AdjustTabs::adjustWinTagTabs
# adjust tabs in range of tag $tabTab
# such that no cols overlap each other
#
proc ::AdjustTabs::adjustWinTagTabs {tabWindow tabTag {distance 12}} {
    set ranges [$tabWindow tag ranges $tabTag]
    if {$ranges eq {}} {
	return
    }
    set lineNrs {}
    foreach {from to} [$tabWindow tag ranges $tabTag] {
	set startIndex [$tabWindow index "$from linestart"]
	let {startLine startCol} [split $startIndex .]
	while {[$tabWindow compare $startLine.0 < $to]} {
	    lappend lineNrs $startLine
	    incr startLine
	}
    }
    if {[llength $lineNrs] <= 1} {
	$tabWindow tag configure $tabTag -tabs {}
	return
    }
    set cols ""
    foreach nr $lineNrs {
	set tabs [numOfTabsAtLine $tabWindow $nr]
	if {$cols eq ""} {
	    set cols $tabs
	} elseif {($tabs ne "" && $tabs > $cols)} {
	    set cols $tabs
	}
    }
    if {$cols eq ""} {
	return
    }
    set colWidths {}
    for {set i 0} {$i < $cols} {incr i} {
	set currentWidth [winTagColWidth $tabWindow $tabTag $i]
	if {$currentWidth eq ""} {
	    break
	}
	incr currentWidth $distance
	lappend colWidths $currentWidth
    }
    set tabstops {}
    set tabstop 0
    foreach colWidth $colWidths {
	lappend tabstops [incr tabstop $colWidth]
    }
    $tabWindow tag configure $tabTag -tabs $tabstops
}

#
# ::AdjustTabs::winTagColWidth
# return really needed width inside tag $tabTag on col $colNr
#
proc ::AdjustTabs::winTagColWidth {tabWindow tabTag colNr} {
    set result ""
    foreach {from to} [$tabWindow tag ranges $tabTag] {
	set startIndex [$tabWindow index "$from linestart"]
	let {startLine startCol} [split $startIndex .]
	while {[$tabWindow compare $startLine.0 < $to]} {
	    set colWidth [calcLineColWidth $tabWindow $startLine $colNr]
	    if {$colWidth ne ""} {
		if {$result eq "" || $colWidth > $result} {
		    set result $colWidth
		}
	    }
	    incr startLine
	}
    }
    set result
}

#
# ::AdjustTabs::calcLineColWidth
# return really needed width in line $line at tabstop $colNr
#
proc ::AdjustTabs::calcLineColWidth {window line colNr} {
    if {$colNr == 0} {
	let {x y w h} [$window bbox $line.0]
	if {![info exists x]} {
	    return
	}
	set leftEdge $x
    } else {
	set leftEdge [calcTabRightEdge $window $line [expr {$colNr-1}]]
    }
    if {$leftEdge eq ""} {
	return
    }
    set rightEdge [calcTabLeftEdge $window $line $colNr]
    if {$rightEdge ne ""} {
	expr {$rightEdge - $leftEdge}
    }
}

#
# ::AdjustTabs::calcTabLeftEdge
# return left edge of tab space in text window
#
proc ::AdjustTabs::calcTabLeftEdge {window line tabNr} {
    set index [calcTabIndex $window $line $tabNr]
    if {$index ne ""} {
	let {x y w h} [$window bbox $index]
	if {[info exists x]} {
	    set x
	}
    } elseif {[numOfTabsAtLine $window $line] == $tabNr} {
	let {x y w h} [$window bbox $line.end]
	if {[info exists x]} {
	    set x
	}
    }
}

#
# ::AdjustTabs::calcTabRightEdge
# return right edge of tab space in text window
#
proc ::AdjustTabs::calcTabRightEdge {window line tabNr} {
    set index [calcTabIndex $window $line $tabNr]
    if {$index ne ""} {
	let {x y w h} [$window bbox $index]
	if {[info exists x]} {
	    expr {$x + $w}
	}
    }
}

#
# ::AdjustTabs::calcTabIndex
# return widget index of tab $tabNr in line $line
#
proc ::AdjustTabs::calcTabIndex {window line tabNr {start 0}} {
    set result\
	[$window search \t "$line.0+$start chars" $line.0+1lines]
    if {$tabNr == 0 || $result eq ""} {
	set result
    } else {
	let {row col} [split $result .]
	calcTabIndex $window $line [expr {$tabNr-1}] [incr col]
    }
}

#
# ::AdjustTabs::numOfTabsAtLine
#  return number of tabs contained in line
#
proc ::AdjustTabs::numOfTabsAtLine {window line} {
    set str1 [$window get $line.0 $line.end]
    set str2 [string map [list \t {}] $str1]
    set l1 [string length $str1]
    set l2 [string length $str2]
    expr {$l1 - $l2}
}

namespace eval AdjustTabs {
    namespace export\
	checkTabEvent\
	initTabTags
}
namespace import -force ::AdjustTabs::*