#
# Copyright (c) 2022-2024 Andrea Biscuola <a@abiscuola.com>
# Copyright (c) 2023 Omar Polo <op@omarpolo.com>
#
# Permission to use, copy, modify, and distribute this software for any
# purpose with or without fee is hereby granted, provided that the above
# copyright notice and this permission notice appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
#

namespace eval irc {
	namespace export cap dial receive send register nick join part quit \
	    privmsg notice ping pong

	namespace ensemble create
}

proc ::irc::dial {cmd callback} {
	if {![catch {set fd [open "|$cmd" r+]} errstr]} {
		fconfigure $fd -blocking 0

		fileevent $fd readable "$callback $fd"

		return $fd

	} else {
		log console "::irc::dial: $errstr"

		return -code error "$errstr"
	}
}

proc ::irc::receive {chan} {
	if {[catch {set line [gets $chan]} error]} {
		log console $error
	}

	if {"$line" eq ""} {
		return -1
	}

	if {[string length $line] >= $::msgsize} {
		log console "received message longer than $::msgsize: $line"

		return -1
	}

	# Remove all non-printable characters
	regsub -all {[[:cntrl:]]+} $line {} line

	set resp [dict create]
	set date [clock seconds]
	set llist [split $line " "]

	#
	# Store IRCv3 tags if found in the incoming message
	#
	# Tags tells us who the correspondent is, what he does, where he
	# comes from and what they like.....
	#
	if {[string index [lindex $llist 0] 0] eq "@"} {
		set tags [split [string trimleft [lindex $llist 0] "@"] ";"]

		foreach tag $tags {
			set kv [split [string trimleft $tag "+"] "="]

			if {[llength $kv] == 1} {
				dict set resp tags [lindex $kv 0] ""
			} else {
				dict set resp tags [lindex $kv 0] [lindex $kv 1]
			}
		}

		set llist [lrange $llist 1 end]
	}

	#
	# Separate and process the prefix, command and argument
	# sections of the message.
	#
	if {[string index [lindex $llist 0] 0] eq ":"} {
		set from [string trimleft [lindex $llist 0] ":"]

		set flist [split $from "@"]

		if {[llength $flist] > 1} {
			dict set resp server [lindex $flist end]
			set from [lindex $flist 0]
		}

		set flist [split $from "!"]

		if {[llength $flist] > 1} {
			dict set resp user [lindex $flist end]
			set from [lindex $flist 0]
		}

		dict set resp nick [lindex $flist 0]

		set llist [lreplace $llist 0 0]
	}

	if {[llength $llist] == 0} {
		return -1
	}

	dict set resp command [string tolower [lindex $llist 0]]
	dict set resp args [lreplace $llist 0 0]
	dict set resp date $date

	return $resp
}

proc ::irc::send {chan tags cmd args} {
	set taglist {}

	#
	# IRCv3 tags are passed in a dictionary. Create a list where
	# tags are added, concatenated with their values through an
	# equal sign (=), as specified in the new standard.
	#
	dict for {k v} $tags {
		if {$v eq ""} {
			lappend taglist $k
		} else {
			lappend taglist [format "%s=%s" $k $v]
		}
	}

	#
	# Join tags, command and arguments and send them.
	#
	set argstr ""
	if {[llength $taglist] > 0} {
		set argstr [format "@%s %s %s\r" [::join $taglist ";"] $cmd \
		    [::join $args " "]]
	} else {
		set argstr [format "%s %s\r" $cmd [::join $args " "]]
	}

	if {[string length $argstr] >= $::msgsize} {
		log console "trying to send message longer than $::msgsize: $argstr"

		return -2
	}

	if {[catch {puts $chan "$argstr"} errstr]} {
		log console "::irc::send: $errstr"

		return -1
	}

	if {[catch {flush $chan} errstr]} {
		log console "::irc::send: $errstr"

		return -1
	}
}

proc ::irc::cap {chan tags capability} {
	switch -- $capability {
		ls {send $chan $tags CAP LS}
		end {send $chan $tags CAP END}
		default {send $chan $tags CAP REQ :$capability}
	}
}

proc ::irc::register {chan tags user nick pass mode name} {
	if {$pass ne ""} {send $chan $tags PASS ":$pass"}
	nick $chan $tags $nick
	send $chan $tags USER $user $mode "*" ":$name"
}

proc ::irc::nick {chan tags nick} {
	send $chan $tags NICK $nick
}

proc ::irc::join {chan tags channels {keys ""}} {
	send $chan $tags JOIN "$channels" "$keys"
}

proc ::irc::part {chan tags channels args} {
	if {[llength $args] > 0} {
		set argstr [::join $args " "]

		send $chan $tags PART $channels ":$argstr"
	} else {
		send $chan $tags PART $channels
	}
}

proc ::irc::quit {chan tags {text ""}} {
	if {$text ne ""} {
		send $chan $tags QUIT ":$text"
	} else {
		send $chan $tags QUIT
	}
}

proc ::irc::privmsg {chan tags target args} {
	set argstr [::join $args " "]

	send $chan $tags PRIVMSG $target ":$argstr"
}

proc ::irc::notice {chan tags target args} {
	set argstr [::join $args " "]

	send $chan $tags NOTICE $target ":$argstr"
}

proc ::irc::ping {chan tags text} {
	send $chan $tags PING ":$text"
}

proc ::irc::pong {chan tags text} {
	send $chan $tags PONG $text
}
