#! /usr/bin/env tclsh
#
# genchanges - Generate changelog (doc/Changes and ChangeLog) files.
#
# Copyright (C) 2017 - 2018 Eggheads Development Team
#
# This file 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 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.
#
# As a special exception to the GNU General Public License, if you
# distribute this file as part of a program that contains a
# configuration script generated by Autoconf, you may include it under
# the same distribution terms that you use for the rest of that program.

package require Tcl 8.6
package require textutil::adjust
package require base64

# TODO: automatic -i/-e arguments. sort order of git tag --list --sort=v:refname is stable.
# TODO: performance improvements (by reading .git/ directly?)

proc get_usage {} {
	return [subst [join {
		{Syntax: $::argv0 \[options\] <command>} {} {Commands:}
		{short        - Generate short changelog (doc/ChangesX.Y)}
		{full         - Generate full changelog (ChangeLog)}
		{release      - OVERWRITE ChangeLog and doc/ChangesX.Y (don't use, not done)}
		{} {Options (general):}
		{-d           - Verbose debug logging}
		{-r <remote>  - Specify remote for tags and public branches}
		{-l           - Skip fetching from remote. ONLY use this on a fresh clone!}
		{} {Options (short):}
		{-e <version> - Specify ref to exclude with ancestors}
		{-i <version> - Specify ref to include with ancestors (use ./XXX to force local ref XXX, e.g. -i ./release/1.8.2}
		{-v <version> - Specify the upcoming version}
		{} {Examples:}
		{  Generate doc/Changes1.8 for v1.8.2rc3 (exclude v1.8.0 because of the static 1.8.0 blob):}
		{    $::argv0 -e v1.6.21 -e v1.8.0 -i v1.8.1 -i v1.8.2 -i stable/1.8 -v 1.8.2rc3 short}
		{  Generate ChangeLog for v1.8.3 final:}
		{    $::argv0 -i stable/1.8 full}
	} \n]]
}

proc commands {} {
	lmap cmd [info commands cmd:*] {
		string range $cmd 4 end
	}
}

proc fatal {msg {showusage 0}} {
	if {$msg ne ""} {
		puts stderr $msg
		if {$showusage} {
			puts stderr ""
		}
	}
	if {$showusage} {
		puts stderr [get_usage]
	}
	exit 1
}

proc pop {listVar} {
	upvar 1 $listVar list
	set e [lindex $list 0]
	set list [lrange $list 1 end]
	return $e
}

proc dict_lappend {dictVar args} {
	upvar 1 $dictVar dict
	set add [lindex $args end]
	set path [lrange $args 0 end-1]
	if {[dict exists $dict {*}$path]} {
		set old [dict get $dict {*}$path]
	} else {
		set old ""
	}
	dict set dict {*}$path [list {*}$old $add]
}

proc log {text} {
	puts stderr $text
}

proc vlog {text} {
	if {$::verbose} {
		log $text
	}
}

proc mustexec {cmd msg} {
	vlog "Attempting execute: [join $cmd]"
	if {[catch [list exec {*}$cmd] result]} {
		fatal "Execution failed. $msg: $result"
	}
	vlog "Execution successful: $result"
	set result [regsub -all -- {\n\n\n+} [string trim $result] "\n\n"]
	return $result
}

proc parsecmdline {argv} {
	global verbose local
	if {![llength $argv]} {
		fatal "" 1
	}
	foreach var {remote command version includes excludes} {
		set $var ""
	}
	set verbose 0
	set local 0
	while {[llength $argv]} {
		set arg [pop argv]
		if {[string index $arg 0] eq "-"} {
			for {set i 1} {$i < [string length $arg]} {incr i} {
				set c [string index $arg $i]
				switch -exact -- $c {
					"l" { set local 1 }
					"r" { set remote [pop argv] }
					"d" { set verbose 1 }
					"v" { set version [pop argv] }
					"i" { lappend includes [pop argv] }
					"e" { lappend excludes [pop argv] }
					default {
						fatal "Unknown option: -$c" 1
					}
				}
				vlog "OptParse: $c (left: $argv)"
			}
		} else {
			set command $arg
			break
		}
	}
	if {$command eq ""} {
		fatal "No command specified." 1
		show_usage
	}
	if {![llength $includes]} {
		fatal "No -i includes specified." 1
	}
	foreach var {remote version includes excludes} {
		cfg$var [set $var]
	}
	foreach file [textutil::adjust::listPredefined] {
		textutil::adjust::readPatterns [textutil::adjust::getPredefined $file]
	}
	fetchremote
	return $command
}

proc indent {text indent} {
	textutil::adjust::indent $text [string repeat " " $indent]
}

proc adjust {text {len 120}} {
	textutil::adjust::adjust $text -hyphenate true -justify left -length $len -strictlength true
}

interp alias {} cfgincludes {} cfgtags includes
interp alias {} cfgexcludes {} cfgtags excludes

proc cfgtags {varName patterns} {
	global remote tags
	upvar #0 $varName thesetags
	set thesetags ""
	if {![info exists tags]} {
		set taglist [regexp -all -inline -- {\S+} [mustexec {git tag --list} "Failed getting tag list"]]
		set tags ""
		foreach tag $taglist {
			set commit [string trim [mustexec [list git rev-parse $tag] "Could not rev-parse tag $tag"]]
			dict lappend tags $commit $tag
			vlog "$tag <- $commit"
		}
	}
	foreach pattern $patterns {
		set tmp [dict values $tags $pattern]
		if {![llength $tmp]} {
			# no matching tags, must be a branch
			if {[string range $pattern 0 1] eq "./"} {
				# force local branch, illegal branch name
				set path [string range $pattern 2 end]
			} else {
				set path $remote/$pattern
			}
			mustexec [list git rev-parse --verify -q $path] "Could not find revision '$path'."
			set tmp [list $path]
		}
		lappend thesetags {*}$tmp
	}
}

proc cfgversion {version} {
	global major
	if {$version ne "" && ![regexp {^(\d+\.\d+)\.\d+} $version -> major]} {
		fatal "Invalid version number: $version. Try 1.8.1 or 1.8.1rc1 or similar."
	}
	set ::version $version
}

proc cfgremote {remote} {
	set remotes [regexp -all -inline -- {\S+} [exec git remote]]
	if {![llength $remotes]} {
		fatal "No git remotes configured."
		exit 1
	}
	if {$remote eq ""} {
		if {[llength $remotes] == 1} {
			set remote [lindex $remotes 0]
		} else {
			fatal "Multiple remotes available, must specify -r. Available: [join $remotes {, }]"
		}
	}
	if {[llength $remotes] == 1 && $remote eq ""} {
		set remote [lindex $remotes 0]
	}
	if {$remote ni $remotes} {
		fatal "Unknown remote: $remote. Available: [join $remotes {, }]"
		exit 1
	}
	vlog "Remotes: '[join $remotes ',']'. Using '$remote'"
	set ::remote $remote
}

proc fetchremote {} {
	global remote local
	if {$local} {
		log "Operating locally only, skipping branches/tags-fetch."
		return
	}
	log "Updating tags and branches from remote '$remote'. Branches first..."
	mustexec [list -ignorestderr git fetch $remote] "Could not fetch from remote"
	log "Branches updated. Fetching tags..."
	mustexec [list -ignorestderr git fetch -t $remote] "Could not fetch tags from remote"
	log "Tags updated."
}

proc start {} {
	global remote
	set command [parsecmdline $::argv]
	if {$command ni [commands]} {
		fatal "Unknown command: $command. Available: [join [commands] {, }]" 1
	}
	log "Working with remote $remote..."
	puts [cmd:$command]
}

proc revlist {excludes includes} {
	set includestr $includes
	set excludestr [lmap x $excludes { return -level 0 ^$x }]
	return [list {*}$includestr {*}$excludestr]
}

proc commitlist {{full 0}} {
	global version includes excludes verbose
	set commits ""
	for {set i 0} {$i < [llength $includes]} {incr i} {
		set cmd [list git rev-list --reverse --date-order {*}[revlist [expr {($i == 0 && $full) ? "" : $excludes}] [lrange $includes $i $i]]]
		set mycommits [mustexec $cmd "Failed to get revlist"]
		foreach commit $mycommits {
			if {$commit ni $commits} {
				lappend commits $commit
			}
		}
	}
	return [lreverse $commits]
}

proc cmd:release {} {
	global major version
	if {![info exists major] || $version eq ""} {
		fatal "Need version number (-v) for short changelog."
	}
	if {![file exists ChangeLog.gz] || ![file exists doc/Changes$major]} {
		fatal "ChangeLog or doc/Changes$major don't exist, are we in the right directory? Then please create them empty if necessary."
	}
	set short [cmd:short]
	set full [cmd:full]
	log "Writing ChangeLog.gz"
	set fs [open ChangeLog.gz w]
	zlib push gzip $fs -level 9
	puts $fs [string trim $full \n]
	close $fs
	log "Exiting, TODO: remove this when shortlog is done!"
	exit 0
	log "Writing doc/Changes$major"
	set fs [open doc/Changes$major w]
	puts $fs [string trim $short \n]
	close $fs
}

proc clean {data} {
	regsub -all -- {(\r)} $data {} data
	regsub -all -- {\t} $data { } data
	regsub -all -- { +\n} $data "\n" data
	regsub -all -- {\n{4,}} $data "\n\n\n" data
	return $data
}

proc getcommitinfo:date {commit} {
	clock format [getcommitinfo:time $commit] -gmt 1 -format "%Y-%m-%d"
}

proc getcommitinfo:files {commit} {
	set data [mustexec [list git show --pretty= --numstat $commit] "Failed to get commit info for $commit"]
	set result ""
	foreach line [split $data \n] {
		if {$line eq ""} {
			continue
		}
		if {![regexp -- {^(\d+|-)\t(\d+|-)\t(.+)$} $line - add del file]} {
			error "ERROR ON '$line'"
		}
		if {$add eq "-" && $del eq "-"} {
			lappend result [format "%13s %s" (binary) $file]
		} else {
			lappend result [format "%6s %6s %s" +$add -$del $file]
		}
	}
	join $result \n
}

proc getcommitinfo:tags {commit} {
	global tags
	if {![dict exists $tags $commit]} {
		return ""
	}
	dict get $tags $commit
}

proc getcommitinfo:body {commit} {
	# roughly where we started using subject/body split messages after cvs->git
	if {[getcommitinfo:time $commit] > 1451487300} {
		return [getcommitinfofield body $commit]
	}
	return ""
}

proc getcommitinfo:subject {commit} {
	if {[getcommitinfo:time $commit] > 1451487300} {
		set msg [string trim [getcommitinfofield subject $commit]]
		if {[string index $msg 0] in {"*" "-"}} {
			set lines [lmap x [split $msg [string index $msg 0]] { set x [string totitle [string trim $x]]; expr {$x eq "" ? [continue] : "$x"} }]
			return [join $lines ". "]
		}

		return [getcommitinfofield subject $commit]
	}
	set msg [getcommitinfofield fullbody $commit]
	# yes, really (14c25840)
	set msg [string map {---------------------------------------------------------------------- ""} $msg]
	set lines [lmap l [split $msg \n] { expr {[set l [string trim $l]] eq "" ? [continue] : [string totitle "$l"]} }]
	if {![llength $lines]} {
		return "*** EMPTY COMMIT MESSAGE ***"
	}
	return [join $lines ". "]
}

proc getcommitinfo:fullbody {commit} {
	set msg [getcommitinfofield fullbody $commit]
	set msg [string trim $msg]
	if {[string index $msg 0] in {"*" "-"}} {
		set lines [lmap x [split $msg [string index $msg 0]] { set x [string trim $x]; expr {$x eq "" ? [continue] : "* $x"} }]
		return [join $lines \n]
	}
	return $msg
}

proc getcommitinfofield {field commit} {
	global commitinfocache commitinfofields
#	vlog "Getting $commit ($field)"
	if {![dict exists $commitinfocache $commit]} {
		set result [split [mustexec [list git show -s --pretty=format:[join [dict values $commitinfofields] %x00] $commit] "Failed to get commit info for $commit"] \x00]
		for {set i 0} {$i < [dict size $commitinfofields]} {incr i} {
			dict set commitinfocache $commit [lindex [dict keys $commitinfofields] $i] [string trim [lindex $result $i]]
		}
		dict set commitinfocache $commit filelist [split [mustexec [list git show --name-only --format= $commit] "Failed to get commit files for $commit"] \n]
	}
	regsub -all -- {\r\n?} [dict get $commitinfocache $commit $field] "\n"
}

set commitinfocache ""
set commitinfofields {fullbody %B time %ct authorname %aN authoremail %aE shorthash %h hash %H authordate %aI subject %s body %b}
foreach field [list {*}[dict keys $commitinfofields] filelist] {
	if {![llength [info commands getcommitinfo:$field]]} {
		interp alias {} getcommitinfo:$field {} getcommitinfofield $field
	}
}

proc getcommitinfo {commit args} {
	global commitinfocache commitinfofields

	set data ""
	foreach info $args {
		dict set data $info [getcommitinfo:$info $commit]
	}
	return $data
}

proc reportstatus {what i max} {
	if {($i & 0xF) == 0 || $i == $max - 1} {
		puts -nonewline stderr "\u001b\[1000D$what ... [format %3d [expr {$i == $max ? 100 : 100*(1+$i)/$max}]] %"
	}
	if {$i == $max - 1} {
		puts ""
	}
}

proc cmd:full {} {
	global excludes includes tags
	set commits [commitlist 1]
	set result {""}
	for {set i 0} {$i < [llength $commits]} {incr i} {
		reportstatus "Generating ChangeLog info" $i [llength $commits]
		set commit [lindex $commits $i]
		set commitinfo [getcommitinfo $commit body subject authorname authoremail shorthash authordate files]
		dict with commitinfo {
			set this ""
			lappend this "Commit $shorthash ($authordate) by $authorname <$authoremail>" ""
			if {[string index $subject 0] in {* -} && [string index $subject 1] eq " "} {
				set body [getcommitinfo:fullbody $commit]
			} elseif {$subject ne ""} {
				lappend this [adjust $subject] ""
			}
			if {$body ne ""} {
				set thisbody ""
				foreach line [split $body \n] {
					lappend thisbody [indent [adjust $line] 2]
				}
				lappend this [join $thisbody \n] ""
			}
			if {$files ne ""} {
				lappend this $files ""
			}
		}
		lappend result [join $this \n]
	}
	log ""
	return [clean [join $result \n[string repeat - 120]\n]]
}

proc dateindent {commits date} {
	set result ""
	for {set i 0} {$i < [llength $commits]} {incr i} {
		set msg [lindex $commits $i]
		if {$i == 0} {
			lappend result "$date $msg"
		} else {
			lappend result "[string repeat " " [string length $date]] $msg"
		}
	}
	return $result
}

proc versionindent {lines} {
	lmap l $lines { return -level 0 "  $l" }
}

proc finalformatshortlog {commits} {
	set versionresult ""
	foreach version [lreverse [dict keys $commits]] {
		set versioncommits [dict get $commits $version]
		set dateresult ""
		foreach date [lreverse [dict keys $versioncommits]] {
			set datecommits [dict get $versioncommits $date]
			set byresult ""
			# force unattributed commits to come first
#			if {[dict exists $datecommits ""]} {
#				set unattrib [dict get $datecommits ""]
#				dict unset datecommits ""
#				dict set datecommits "" $unattrib
#			}
#			foreach by [lreverse [dict keys $datecommits]] {}
			foreach datecommit $datecommits {
				lassign $datecommit by bycommits
				set bycommits [list $bycommits]
#				set bycommits [lreverse [dict get $datecommits $by]]
				set this [lmap c $bycommits {
					set rest [lassign [split [adjust $c 100] \n] first]
					set rest [lmap r $rest { return -level 0 "  $r" }]
					set rest [list "* $first" {*}$rest]
					if {$by ne ""} {
						lappend rest "    \[$by\]"
					}
					join $rest \n
				}]
				if {$by ne ""} {
#					lappend this "  $by"
				}
				lappend byresult $this
			}
			vlog "BYRESULT: $byresult"
			lappend dateresult "[join [dateindent [split [join [lmap x $byresult { join $x \n }] \n] \n] $date] \n]"
		}
		vlog "DATERESULT: $dateresult"
		lappend versionresult "[expr {$version ne "" ? "Eggdrop $version:" : ""}]\n\n[join [versionindent [split [join $dateresult \n] \n]] \n]"
	}
	join $versionresult \n\n
}

proc cmd:short {} {
	global version verbose tags major
	set commits [lreverse [commitlist]]
	if {$verbose} {
		vlog "--- Start of History ---"
		foreach commit $commits {
			vlog "* $commit[expr {[dict exists $tags $commit] ? " ([dict get $tags $commit])" : ""}]"
		}
		vlog "---- End of History ----"
	}
	foreach key {version date foundby patchby} {
		dict set last $key ""
	}
	set result ""
	set thisversion ""
	for {set i 0} {$i < [llength $commits]} {incr i} {
		reportstatus "Generating doc/Changes info" $i [llength $commits]
		set commit [lindex $commits $i]
		# skip merge commits unless they have a tag associated to them (e.g. v1.8.1 is a merge commit with a tag)
		if {![dict exists $tags $commit] && ![catch {exec git cat-file -t $commit^2}]} {
			continue
		}
		set commitinfo [getcommitinfo $commit fullbody subject date body filelist]
		set body [dict get $commitinfo body]
		set fullmsg [dict get $commitinfo fullbody]
		set shortmsg [dict get $commitinfo subject]
		set date [dict get $commitinfo date]
		set files [dict get $commitinfo filelist]
		set found ""
		set patch ""
		set newfiles ""
		foreach file $files {
			set fn [lindex [file split $file] end]
			if {$file in {src/patch.h src/version.h ChangeLog.gz ChangeLog} || [string match doc/Changes?.* $file] || $fn in {configure}} {
				continue
			}
			lappend newfiles $file
		}
		if {![dict exists $tags $commit] && [llength $files] && ![llength $newfiles]} {
			vlog "Skipping $commit ($files)"
			continue
		}
		# Subject can contain this information too, scan the whole body, then replace in subject
		foreach {- category names} [regexp -nocase -all -inline -- {(found|patch) by:([^\r\n/]+)} $fullmsg] {
			foreach nick [split $names {, }] {
				set nick [string trim $nick ""]
				if {$nick ne ""} {
					# dict to deduplicate
					dict set [string tolower $category] $nick 1
				}
			}
		}
		# Only at the end
		regsub -all -nocase -- {(found|patch) by:.*$} $shortmsg {} shortmsg
		set by ""
		if {[dict size $found]} {
			lappend by "Found by: [join [dict keys $found] {, }]"
		}
		if {[dict size $patch]} {
			lappend by "Patch by: [join [dict keys $patch] {, }]"
		}
		set by [join $by { / }]
#		dict_lappend thisversion $date $by $shortmsg
		dict_lappend thisversion $date [list $by $shortmsg]
		if {[dict exists $tags $commit]} {
			# got version tag, everything up to here belongs to that version
			dict set result "[dict get $tags $commit] ($date)" $thisversion
			set thisversion ""
		}
	}
	log ""
	dict set result [expr {$version eq "" ? "" : "v$version"}] $thisversion
	set log [finalformatshortlog $result]
	return [clean "Eggdrop Changes (Last Updated [clock format [clock seconds] -gmt 1 -format "%Y-%m-%d"]):\n__________________________________________\n\n$log\n\n[zlib decompress [base64::decode $::suffix]]"]
}

# This is the old Changes1.8 file until 1.8.0 which includes manual annotations and changelogs
set suffix {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}

start
