#! /usr/bin/env tclsh
#
# genchanges - Generate changelog (doc/Changes and ChangeLog) files.
#
# Copyright (C) 2017 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

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}
		{} {Options (general):}
		{-d           - Verbose debug logging}
		{-r <remote>  - Specify remote for tags and public branches}
		{} {Options (short):}
		{-e <version> - Specify ref to exclude with ancestors}
		{-i <version> - Specify ref to include with ancestors}
		{-n <version> - Specify the upcoming version}
		{} {Examples:}
		{  Generate doc/Changes1.8 for v1.8.2rc3:}
		{    $::argv0 -e v1.6.21 -i v1.8.1 -i v1.8.2 -i stable/1.8 -n 1.8.2rc3 short}
		{  Generate doc/Changes1.8 for v1.8.3 final (make sure version order is correct at the end):}
		{    $::argv0 -e v1.6.21 -i "v1.8.*" -i stable/1.8 -n 1.8.3 short}
		{  Generate ChangeLog for v1.8.3 final:}
		{    $::argv0 -i stable/1.8}
	} \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 log {text} {
	puts stderr $text
}

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

proc parsecmdline {argv} {
	global verbose
	if {![llength $argv]} {
		fatal "" 1
	}
	foreach var {remote command version includes excludes} {
		set $var ""
	}
	set verbose 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 {
					"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]
	}
	return $command
}

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

proc cfgtags {varName patterns} {
	global remote
	upvar #0 $varName tags
	set tags ""
	foreach pattern $patterns {
		set tmp [regexp -all -inline -- {\S+} [exec git tag -l $pattern]]
		if {![llength $tmp]} {
			# no matching tags
			if {[catch {exec git rev-parse --verify -q $remote/$pattern}]} {
				fatal "Could not parse revision $pattern on remote $remote."
			}
			set tmp [list $remote/$pattern]
		}
		lappend tags {*}$tmp
	}
}

proc cfgversion {version} {
	if {$version ne "" && ![regexp {^\d+\.\d+\.\d+} $version]} {
		fatal "Invalid ersion number: $version. Try 1.8.1 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 start {} {
	global remote
	set command [parsecmdline $::argv]
	if {$command ni [commands]} {
		fatal "Unknown command: $command. Available: [join [commands] {, }]" 1
	}
	log "Working with remote $remote..."
	cmd:$command
}

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

proc doexec {cmd} {
	log "Executing: [join $cmd]"
	if {[catch {exec {*}$cmd} res]} {
		fatal "Error during executing: $res"
	}
	set res [regsub -all -- {\n\n\n+} [string trim $res] "\n\n"]
	log "Executed: [join $cmd]"
	return $res
}

proc cmd:full {} {
	set revlist [revlist]
	set cmd [list git log --no-merges --date-order --name-status {--pretty=format:%n------------------------------%nCommit %h (%ai) by %aN %n%n%B%n%n} {*}[revlist]]
	puts [doexec $cmd]
}

proc cmd:short {} {
	global version includes excludes
	if {$version eq ""} {
		fatal "Need version number (-v) for short changelog."
	}
	set cmd [list git rev-list --no-merges --reverse --author-date-order {*}[revlist]]
	set commits [doexec $cmd]
	set taglist [regexp -all -inline -- {\S+} [exec git tag --list]]
	set tags ""
	foreach tag $taglist {
		dict set tags [string trim [exec git rev-parse $tag]] $tag
	}
	set lastdate ""
	set lastappend ""
	foreach commit $commits {
		set fullmsg [exec git show -s --pretty=format:%B $commit]
		set shortmsg [exec git show -s --pretty=format:%s $commit]
		set date [clock format [exec git show -s --pretty=format:%ct $commit] -gmt 1 -format "%Y-%m-%d"]
		set found ""
		set patch ""
		set shortmsg [string trim $shortmsg "- "]
		if {$lastappend ne ""} {
			lappend result {*}[string map [list %%CHANGELOGDATE%% [expr {($date eq $lastdate && !$forcedate) ? "[string repeat " " 10]" : "$lastdate"}]] $lastappend]
		}
		set forcedate 0
		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 set [string tolower $category] $nick 1
				}
			}
		}
		set shortmsgs ""
		#ugly specific fixup
		if {[string index $shortmsg 0] eq "*"} {
			set split [split $shortmsg *]
			foreach e $split {
				set e [string trim $e]
				if {$e eq ""} { continue }
				lappend shortmsgs $e
			}
		} else {
			set shortmsgs [list $shortmsg]
		}
		set secondline ""
		if {[dict size $found]} {
			lappend secondline "Found by: [join [dict keys $found] {, }]"
		}
		if {[dict size $patch]} {
			lappend secondline "Patch by: [join [dict keys $patch] {, }]"
		}
		if {[llength $secondline]} {
			set lastmsg [lindex $shortmsgs end]
			append lastmsg "\n  [string repeat " " 12] [join $secondline { / }]"
			set shortmsgs [list {*}[lrange $shortmsgs 0 end-1] $lastmsg]
		}
		set lastappend ""
		set lastmsg [lindex $shortmsgs end]
		set shortmsgs [lrange $shortmsgs 0 end-1]
		foreach shortmsg $shortmsgs {
			set lines [split [textutil::adjust::adjust $shortmsg -full true -justify left -length 120 -strictlength true] \n]
			lappend lastappend "  [string repeat " " 10] * [pop lines]"
			foreach line $lines {
				lappend lastappend "  [string repeat " " 12] $line"
			}
		}
		set lines [split [textutil::adjust::adjust $shortmsg -full true -justify left -length 120 -strictlength true] \n]
		lappend lastappend "  %%CHANGELOGDATE%% * [pop lines]"
		foreach line $lines {
			lappend lastappend "  [string repeat " " 10] * $line"
		}
		if {[dict exists $tags $commit]} {
			lappend lastappend "\nEggdrop [dict get $tags $commit] (released $date):\n"
			set forcedate 1
		}
		set lastdate $date
	}
	lappend result {*}[string map [list %%CHANGELOGDATE%% $lastdate] $lastappend]
	puts "Eggdrop Changes (Last Updated [clock format [clock seconds] -gmt 1 -format "%Y-%m-%d"]):\n__________________________________________\n\nEggdrop v$version:\n"
	puts [join [lreverse $result] \n]
}

start
