#
# Copyright (c) 2024 Andrea Biscuola <a@abiscuola.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.

#
# irctk.tcl: IRCTk tcl sdk
#
# For detailed documentation, see irctk-exts-sdk(n).
#

namespace eval irctk {
	namespace export filter init ircsend loop

	set nextid 0
	set protocol 1.0

	set handshakeid -1

	variable extname
	variable extversion
	variable caps

	variable filters

	variable log
}

proc newid {} {
	variable nextid

	incr nextid
}

#
# Initialize the extension. As part of this, we already bind a filevent
# to stdin, that will be handled by the "fetch" command.
#
# "capabilities" represent a list of IRCv3 capabilities the extension
# is able to manage and that IRCTk will ask the server to enable,
# if possible.
#
# the "callback", if set, will be used for any message that the
# extension should not receive, or for all the messages if no filters
# are in place.
#
proc ::irctk::init {name version capabilities {callback {}}} {
	variable filters
	variable extname
	variable extversion
	variable caps

	set filter [dict create]
	dict set filters default callback $callback

	set extname $name
	set extversion $version
	set caps $capabilities

	fileevent stdin readable "::irctk::fetch"
}

proc ::irctk::handshake {name version} {
	variable protocol
	variable handshakeid
	variable caps

	set handshakeid [newid]

	puts [encoding convertto utf-8 [format "%s\thandshake\t%s\t%s\t%s\t%s\r" \
	    $handshakeid $protocol $name $version [concat $caps]]]

	flush stdout
}

proc ::irctk::ack {id {text ""}} {
	puts [encoding convertto utf-8 [format "%s\tack\t%s\r" $id $text]]

	flush stdout
}

proc ::irctk::nack {id {text ""}} {
	puts [encoding convertto utf-8 [format "%s\tnack\t%s\r" $id $text]]

	flush stdout
}

proc ::irctk::ircsend {net chan cmd line} {
	puts [encoding convertto utf-8 [format \
	    "\tirc\t\t\t\t\t\t\t%s\t%s\t\t%s\t%s\r" \
	    "$net" "$chan" "$cmd" "$line"]]

	flush stdout
}

#
# Set a filter with a related callback.
#
# Setting a callback to {}, is equivalent to having the messages of
# the specified type to be ignored.
#
# Filter messages are sent only when the handshake is done.
#
# This let the extension to be written in a much more simple
# fashion.
#
proc ::irctk::filter {type {callback {}}} {
	variable filters
	variable handshakeid

	set id [newid]

	if {$handshakeid != -1} {
		dict set filters $type id $id

		puts [encoding convertto utf-8 [format "%s\tfilter\t%s\r" $id $type]]

		flush stdout
	}

	dict set filters $type callback "$callback"
}

proc ::irctk::loop {} {
	vwait ::irctk::forever
}

proc ::irctk::fetch {} {
	variable protocol
	variable extname
	variable extversion
	variable filters
	variable handshakeid

	#
	# Kill everything if stdin is closed
	#
	if {[gets stdin line] <= 0} {
		if {[eof stdin]} {set ::irctk::forever 1}

		return
	}

	#
	# Remember that the messages are exchanged as UTF-8
	#
	set msg [split [encoding convertfrom utf-8 $line] "\t"]

	switch -exact -- [lindex $msg 1] {
		handshake {
			set id [lindex $msg 0]

			#
			# Run the handshake. The protocol is retro-compatible,
			# so we check if the version in our extension is too new
			# for the version of IRCTk we are running.
			#
			# If the IRCTk protocol version is newer, we do not want
			# to run.
			#

			if {[lindex $msg 2] < $protocol} {
				nack $id "$::name: Incompatible protocol version"
				flush stdout

				set ::irctk::forever 1

				return
			}

			ack $id ok
			handshake $extname $extversion
		} nack {
			set id [lindex $msg 0]

			if {$handshakeid == $id} {
				set ::irctk::forever 1

				return
			}

			dict for {type values} $filters {
				if {[dict get $values id] == $id} {
					error "[lindex $msg 2]"
				}
			}
		} ack {
			set id [lindex $msg 0]

			#
			# If the ack is for the handshake, send out the
			# filters the extension requested.
			#
			if {$id == $handshakeid} {
				dict for {type values} $filters {
					if {$type ne "default"} {
						filter $type [dict get $values callback]
					}
				}

				return
			}

			#
			# In this case, the flter was accepted.
			#
			dict for {type values} $filters {
				if {[dict exists $values id]} {
					if {[dict get $values id] == $id} {
						dict set filters $type id 0
					}
				}
			}
		} default {
			#
			# Process all the other messages.
			#

			set rmsg [dict create]
			set type [lindex $msg 1]

			#
			# Prepare the message. See irctk-extensions(7)
			# for the fields.
			#
			# We name the fields as written in the specification.
			#

			dict set rmsg id [lindex $msg 0]
			dict set rmsg type [lindex $msg 1]

			switch -exact -- $type {
				plumb {
					dict set rmsg cid [lindex $msg 2]
					dict set rmsg network [lindex $msg 3]
					dict set rmsg channel [lindex $msg 4]
					dict set rmsg data [lindex $msg 5]
				} irc {
					dict set rmsg timestamp [lindex $msg 2]
					dict set rmsg cid [lindex $msg 3]
					dict set rmsg nick [lindex $msg 4]
					dict set rmsg level [lindex $msg 5]
					dict set rmsg focus [lindex $msg 6]
					dict set rmsg status [lindex $msg 7]
					dict set rmsg network [lindex $msg 8]
					dict set rmsg channel [lindex $msg 9]
					dict set rmsg tags [lindex $msg 10]
					dict set rmsg command [lindex $msg 11]
					dict set rmsg args [lrange $msg 12 end]
				}
			}

			#
			# If there is a callback for the specified type or command,
			# We want to use it.
			#
			# Otherwise, use the default handler, if it was set with
			# ::irctk::init.
			#
			set call [dict get $filters default callback]

			if {"$type" eq "irc"} {
				set cmd [dict get $rmsg command]

				if {[dict exists $filters $cmd callback]} {
					set call [dict get $filters $cmd callback]
				} else {
					set call [dict get $filters $type callback]
				}
			} else {
				set call [dict get $filters $type callback]
			}

			if {$call ne {}} {
				$call $rmsg
			}
		}
	}
}