# $Id$

package require msgcat

namespace eval chess {
    ::msgcat::mcload [file join [file dirname [info script]] msgs]

    if {![::plugins::is_registered chess]} {
	::plugins::register chess \
			    -namespace [namespace current] \
			    -source [info script] \
			    -description [::msgcat::mc "Whether the Chess plugin is loaded."] \
			    -loadcommand [namespace code load] \
			    -unloadcommand [namespace code unload]
	return
    }

    variable square_size 48
    variable line_width 1
    array set piece_name [list \
			      wk [::msgcat::mc "K"] \
			      wq [::msgcat::mc "Q"] \
			      wr [::msgcat::mc "R"] \
			      wb [::msgcat::mc "B"] \
			      wn [::msgcat::mc "N"] \
			      wp {} \
			      bk [::msgcat::mc "K"] \
			      bq [::msgcat::mc "Q"] \
			      br [::msgcat::mc "R"] \
			      bb [::msgcat::mc "B"] \
			      bn [::msgcat::mc "N"] \
			      bp {}]
    array set prom_name [list q queen r rook b bishop n knight]
    array set prom_rev [list queen q rook r bishop b knight n]

    variable themes
    set dirs \
	[glob -nocomplain -directory [file join [file dirname [info script]] \
						pixmaps] *]
    foreach dir $dirs {
	pixmaps::load_theme_name [namespace current]::themes $dir
    }
    set values {}
    foreach theme [lsort [array names themes]] {
	lappend values $theme $theme
    }

    custom::defgroup Plugins [::msgcat::mc "Plugins options."] \
	-group Tkabber

    custom::defgroup Chess [::msgcat::mc "Chess plugin options."] \
	-group Plugins
    custom::defvar options(theme) Classic \
	[::msgcat::mc "Chess figures theme."] -group Chess \
	-type options -values $values \
	-command [namespace current]::load_stored_theme
    custom::defvar options(flip_black_view) 1 \
	[::msgcat::mc "Flip board view when playing black by default."] \
	-type boolean -group Chess
    custom::defvar options(show_last_move) 0 \
	[::msgcat::mc "Show last move by default."] \
	-type boolean -group Chess
    custom::defvar options(always_queen) 0 \
	[::msgcat::mc "Promote pawns always as queens."] \
	-type boolean -group Chess
    custom::defvar options(show_tooltips) 1 \
	[::msgcat::mc "Show tooltips with short instructions."] \
	-type boolean -group Chess \
	-command [list [namespace current]::set_tooltips]
    custom::defvar options(sound) "" \
        [::msgcat::mc "Sound to play after opponent's turn"] \
	-type file -group Chess
    custom::defvar options(allow_illegal) 0 \
	[::msgcat::mc "Allow illegal moves (useful for debugging)."] \
	-type boolean -group Chess
    custom::defvar options(accept_illegal) 0 \
	[::msgcat::mc "Accept opponent illegal moves (useful for\
		       debugging)."] \
	-type boolean -group Chess
}

proc chess::load {} {
    hook::add roster_create_groupchat_user_menu_hook \
              [namespace current]::add_groupchat_user_menu_item 48
    hook::add chat_create_user_menu_hook \
              [namespace current]::add_groupchat_user_menu_item 48
    hook::add roster_jid_popup_menu_hook \
              [namespace current]::add_groupchat_user_menu_item 48

    hook::add games_board_create_hook [namespace current]::iq_create
    hook::add games_board_turn_hook [namespace current]::iq_turn

    ::xmpp::iq::register set create games:board \
			 [namespace parent]::iq_games_board_create
    ::xmpp::iq::register set turn games:board \
			 [namespace parent]::iq_games_board_turn

    load_stored_theme
    calc_moves
}

proc chess::unload {} {
    hook::remove roster_create_groupchat_user_menu_hook \
              [namespace current]::add_groupchat_user_menu_item 48
    hook::remove chat_create_user_menu_hook \
              [namespace current]::add_groupchat_user_menu_item 48
    hook::remove roster_jid_popup_menu_hook \
              [namespace current]::add_groupchat_user_menu_item 48

    hook::remove games_board_create_hook [namespace current]::iq_create
    hook::remove games_board_turn_hook [namespace current]::iq_turn

    if {[hook::is_empty games_board_create_hook]} {
        ::xmpp::iq::unregister set create games:board
        rename [namespace parent]::iq_games_board_create ""
    }

    if {[hook::is_empty games_board_turn_hook]} {
        ::xmpp::iq::unregister set turn games:board
        rename [namespace parent]::iq_games_board_turn ""
    }

    foreach var [info vars [namespace current]::*] {
        upvar #0 $var flags
        if {[info exists flags(window)]} {
            destroy_win $flags(window)
        }
    }

    foreach var [info vars [namespace current]::*] {
        if {$var ne "[namespace current]::options"} {
            unset $var
        }
    }

    foreach img [image names] {
        if {[string first chess/ $img] == 0} {
            image delete $img
        }
    }
}

proc chess::load_stored_theme {args} {
    variable options
    variable themes

    pixmaps::load_dir $themes($options(theme))
}

proc chess::get_nick {xlib jid type} {
    if {[catch {chat::get_nick $xlib $jid $type} nick]} {
	return [chat::get_nick $jid $type]
    } else {
	return $nick
    }
}

proc chess::invite_dialog {xlib jid} {
    set w .chess_invite

    if {[winfo exists $w]} {
	destroy $w
    }

    Dialog $w -title [::msgcat::mc "Chess Invitation"] \
	-modal none -separator 1 -anchor e -default 0

    set wf [$w getframe]
    message $wf.message -aspect 50000 \
	-text [::msgcat::mc "Sending chess game invitation to %s (%s)" \
		      [get_nick $xlib $jid chat] \
		      $jid]

    pack $wf.message -pady 2m

    $w add -text [::msgcat::mc "I want play white"] \
	-command [list [namespace current]::invite $xlib $jid white]
    $w add -text [::msgcat::mc "I want play black"] \
	-command [list [namespace current]::invite $xlib $jid black]
    $w add -text [::msgcat::mc "Cancel invitation"] \
	-command [list destroy $w]

    $w draw
}

proc chess::invite {xlib jid color} {
    destroy .chess_invite

    set id chess[rand 1000000000]

    # FIX
    #set rjid [get_jid_of_user $jid]

    ::xmpp::sendIQ $xlib set \
	-query [::xmpp::xml::create create \
			-xmlns games:board \
			-attrs [list type chess \
				     id $id \
				     color $color]] \
	-to $jid \
	-command [list [namespace current]::invite_res $xlib $jid $id $color]
}

proc chess::invite_res {xlib jid id color status xml} {
    if {![string equal $status ok]} {
	after idle [list NonmodalMessageDlg .chess_invite_error -aspect 50000 -icon error \
	    -message [::msgcat::mc "%s (%s) has refused chess invitation: %s" \
				   [get_nick $xlib $jid chat] \
				   $jid [error_to_string $xml]]]
	return ""
    }

    start_play $xlib $jid $id $color
}


proc chess::invited_dialog {xlib jid iqid id color} {
    set w .chess_invited

    if {[winfo exists $w]} {
	destroy $w
    }

    Dialog $w -title [::msgcat::mc "Chess Invitation from %s" $jid] \
	-modal none -separator 1 -anchor e -default 0

    set wf [$w getframe]
    bind $wf <Destroy> [namespace code [list invited_res $w $xlib $jid $iqid $id $color 0]]

    set nick [get_nick $xlib $jid chat]
    set message1 [::msgcat::mc "Chess game invitation from %s (%s) is received." \
			 $nick $jid]
    switch -- $color {
	white {
	    set message2 [::msgcat::mc "%s wants play white." $nick]
	}
	black {
	    set message2 [::msgcat::mc "%s wants play black." $nick]
	}
	default {
	    return [list error modify bad-request]
	}
    }
    message $wf.message1 -aspect 50000 -text $message1
    message $wf.message2 -aspect 50000 -text $message2
    pack $wf.message1 -pady 1m
    pack $wf.message2 -pady 1m
    
    $w add -text [::msgcat::mc "Agree to play"] \
	   -command [namespace code [list invited_res $w $xlib $jid $iqid $id $color 1]]
    $w add -text [::msgcat::mc "Refuse to play"] \
	   -command [namespace code [list invited_res $w $xlib $jid $iqid $id $color 0]]

    $w draw
    return
}

proc chess::invited_res {w xlib jid iqid id color res} {
    catch {
	set wf [$w getframe]
	bind $wf <Destroy> {}
	destroy $w
    }

    if {$res} {
	switch -- $color {
	    white {
		start_play $xlib $jid $id black
	    }
	    black {
		start_play $xlib $jid $id white
	    }
	    default {
		::xmpp::sendIQ $xlib error \
			       -error [::xmpp::stanzaerror::error \
						modify bad-request] \
			       -to $jid \
			       -id $iqid
	    }
	}

        ::xmpp::sendIQ $xlib result \
                       -query [::xmpp::xml::create create \
					-xmlns games:board \
					-attrs [list type chess \
						     id $id]] \
                       -to $jid \
                       -id $iqid
    } else {
	::xmpp::sendIQ $xlib error \
		       -error [::xmpp::stanzaerror::error \
					modify not-acceptable] \
		       -to $jid \
		       -id $iqid
    }
    return
}

proc chess::start_play {xlib jid id color} {
    set gid [make_gid $jid $id]
    variable $gid
    variable options
    upvar 0 $gid flags

    set flags(window) [win_id chess $gid]
    set flags(xlib) $xlib
    set flags(opponent) $jid
    set flags(id) $id
    set flags(flip) 0
    set flags(our_color) $color

    trace variable [namespace current]::${gid}(position,turn) w \
	[list [namespace current]::set_label_move $gid]

    make_default_position $gid

    open $gid
}

proc chess::set_label_move {gid args} {
    variable $gid
    upvar 0 $gid flags

    switch -- $flags(position,turn) {
	white {
	    set flags(move_label) [::msgcat::mc "White"]
	    set move 1
	}
	black {
	    set flags(move_label) [::msgcat::mc "Black"]
	    set move 1
	}
	default {
	    set move 0
	}
    }
    if {$move && [is_my_move $gid]} {
	append flags(move_label) [::msgcat::mc " (You)"]
    } else {
	append flags(move_label) [::msgcat::mc " (Opponent)"]
    }
}

proc chess::make_default_position {gid} {
    variable $gid
    upvar 0 $gid flags

    for {set c 0} {$c < 8} {incr c} {
	for {set r 0} {$r < 8} {incr r} {
	    set flags(position,$c,$r) ""
	}
    }
    for {set c 0} {$c < 8} {incr c} {
	set flags(position,$c,1) wp
	set flags(position,$c,6) bp
    }
    set c 0
    foreach f {r n b q k b n r} {
	set flags(position,$c,0) w$f
	set flags(position,$c,7) b$f
	incr c
    }
    set flags(position,wk,c) 4
    set flags(position,wk,r) 0
    set flags(position,bk,c) 4
    set flags(position,bk,r) 7

    set flags(position,wk,kcastling) 1
    set flags(position,wk,qcastling) 1
    set flags(position,bk,kcastling) 1
    set flags(position,bk,qcastling) 1
    set flags(position,enpassant) {}

    set flags(position,turn) white

    catch {unset flags(position,last_move)}
    set flags(position,draw) 0
    set flags(position,halfmove) 0
    set flags(position,history) {}

    set flags(position,repetitions) [list [code_position $gid] 1]
}

proc chess::save_position {gid} {
    variable $gid
    upvar 0 $gid flags

    set flags(saved_position) [array get flags position,*]
}

proc chess::restore_position {gid} {
    variable $gid
    upvar 0 $gid flags

    array set flags $flags(saved_position)
    draw_position $gid
    unhighlight_legal_moves $gid
    update_controls $gid
    find_legal_moves $gid $flags(position,turn)
}

proc chess::code_position {gid} {
    variable $gid
    upvar 0 $gid flags

    set code ""
    for {set c 0} {$c < 8} {incr c} {
	for {set r 0} {$r < 8} {incr r} {
	    switch -- $flags(position,$c,$r) {
		wk {append code K}
		wq {append code Q}
		wr {append code R}
		wb {append code B}
		wn {append code N}
		wp {append code P}
		bk {append code k}
		bq {append code q}
		br {append code r}
		bb {append code b}
		bn {append code n}
		bp {append code p}
		default {append code .}
	    }
	}
    }

    append code $flags(position,wk,kcastling)
    append code $flags(position,wk,qcastling)
    append code $flags(position,bk,kcastling)
    append code $flags(position,bk,qcastling)

    if {[llength $flags(position,enpassant)] > 0} {
	lassign $flags(position,enpassant) c r
	set cp1 [expr {$c + 1}]
	set cm1 [expr {$c - 1}]
	if {$r == 2} {
	    if {($cp1 < 8 && $flags(position,$cp1,3) == "bp") || \
		    ($cm1 >= 0 && $flags(position,$cm1,3) == "bp")} {
		append code $c$r
	    }
	} elseif {$r == 5} {
	    if {($cp1 < 8 && $flags(position,$cp1,4) == "wp") || \
		    ($cm1 >= 0 && $flags(position,$cm1,4) == "wp")} {
		append code $c$r
	    }
	}
    }

    return $code
}

proc chess::make_gid {jid id} {
    jid_to_tag [concat $jid $id]
}

proc chess::turn_recv {gid xmlList} {
    variable options
    variable prom_rev
    variable $gid
    upvar 0 $gid flags

    set move 0
    set draw 0

    foreach xml $xmlList {
	::xmpp::xml::split $xml tag xmlns attrs cdata subels
	switch -- $tag {
	    move {
		set pos [::xmpp::xml::getAttr $attrs pos]
		set poss [split $pos ";"]
		if {[llength $poss] == 2} {
		    set pos1 [split [lindex $poss 0] ,]
		    set pos2 [split [lindex $poss 1] ,]
		    if {[llength $pos1] == 2 && [llength $pos2] == 2} {
			set cf [lindex $pos1 0]
			set rf [lindex $pos1 1]
			set ct [lindex $pos2 0]
			set rt [lindex $pos2 1]
			set prom ""
			foreach subel $subels {
			    ::xmpp::xml::split $subel stag sxmlns sattrs scdata ssubels
			    if {$stag == "promotion"} {
				if {[info exists prom_rev($scdata)]} {
				    set prom $prom_rev($scdata)
				}
			    }
			}
			set move 1
			if {$options(sound) != "" && ![::sound::is_mute]} {
			    ::sound::play $options(sound)
		        }
			    
		    }
		}
	    }
	    resign {
		end_game $gid 1 [::msgcat::mc "You win (Opponent resigned)"]
		update_controls $gid
		draw_position $gid
		highlight_last_move $gid
		return [list result [::xmpp::xml::create turn \
					    -xmlns games:board \
					    -attrs [list type chess \
							 id $flags(id)]]]
	    }
	    accept {
		if {$flags(position,draw)} {
		    end_game $gid 0.5 [::msgcat::mc "Draw (Opponent accepted)"]
		    update_controls $gid
		    draw_position $gid
		    highlight_last_move $gid
		    return [list result [::xmpp::xml::create turn \
						-xmlns games:board \
						-attrs [list type chess \
							     id $flags(id)]]]
		} else {
		    return [list error modify not-acceptable]
		}
	    }
	    draw {
		set draw 1
	    }
	}
    }

    if {$move && [do_move $gid $cf $rf $ct $rt $prom $draw]} {
	update_controls $gid $draw
	draw_position $gid
	highlight_last_move $gid

	return [list result [::xmpp::xml::create turn \
				    -xmlns games:board \
				    -attrs [list type chess \
						 id $flags(id)]]]
    } else {
	return [list error modify not-acceptable]
    }
}


###############################################################################

proc chess::calc_moves {} {
    variable moves

    for {set c 0} {$c < 8} {incr c} {
	for {set r 0} {$r < 8} {incr r} {
	    for {set moves(d1,$c,$r) {}; set x [expr {$c+1}]; set y [expr {$r+1}]} \
		{($x < 8) && ($y < 8)} {incr x; incr y} {
		lappend moves(d1,$c,$r) $x $y
	    }
	    for {set moves(d2,$c,$r) {}; set x [expr {$c-1}]; set y [expr {$r+1}]} \
		{($x >= 0) && ($y < 8)} {incr x -1; incr y} {
		lappend moves(d2,$c,$r) $x $y
	    }
	    for {set moves(d3,$c,$r) {}; set x [expr {$c-1}]; set y [expr {$r-1}]} \
		{($x >= 0) && ($y >= 0)} {incr x -1; incr y -1} {
		lappend moves(d3,$c,$r) $x $y
	    }
	    for {set moves(d4,$c,$r) {}; set x [expr {$c+1}]; set y [expr {$r-1}]} \
		{($x < 8) && ($y >= 0)} {incr x; incr y -1} {
		lappend moves(d4,$c,$r) $x $y
	    }
	    for {set moves(h1,$c,$r) {}; set x [expr {$c+1}]} {$x < 8} {incr x} {
		lappend moves(h1,$c,$r) $x $r
	    }
	    for {set moves(h2,$c,$r) {}; set x [expr {$c-1}]} {$x >= 0} {incr x -1} {
		lappend moves(h2,$c,$r) $x $r
	    }
	    for {set moves(v1,$c,$r) {}; set y [expr {$r+1}]} {$y < 8} {incr y} {
		lappend moves(v1,$c,$r) $c $y
	    }
	    for {set moves(v2,$c,$r) {}; set y [expr {$r-1}]} {$y >= 0} {incr y -1} {
		lappend moves(v2,$c,$r) $c $y
	    }
	    set moves(n,$c,$r) {}
	    foreach {dx dy} {1 2 -1 2 -2 1 -2 -1 -1 -2 1 -2 2 -1 2 1} {
	    	set x [expr {$c + $dx}]
	    	set y [expr {$r + $dy}]
		if {($x >= 0) && ($x < 8) && ($y >= 0) && ($y < 8)} {
		    lappend moves(n,$c,$r) $x $y
		}
	    }
	    set moves(wpt,$c,$r) {}
	    if {$r <= 6} {
		if {$c <= 6} {
		    lappend moves(wpt,$c,$r) [expr {$c + 1}] [expr {$r + 1}]
		}
		if {$c >= 1} {
		    lappend moves(wpt,$c,$r) [expr {$c - 1}] [expr {$r + 1}]
		}
	    }
	    set moves(bpt,$c,$r) {}
	    if {$r >= 1} {
		if {$c <= 6} {
		    lappend moves(bpt,$c,$r) [expr {$c + 1}] [expr {$r - 1}]
		}
		if {$c >= 1} {
		    lappend moves(bpt,$c,$r) [expr {$c - 1}] [expr {$r - 1}]
		}
	    }
	    set moves(k,$c,$r) {}
	    foreach {dx dy} {0 1 -1 1 -1 0 -1 -1 0 -1 1 -1 1 0 1 1} {
		set x [expr {$c + $dx}]
		set y [expr {$r + $dy}]
		if {($x >= 0) && ($x < 8) && ($y >= 0) && ($y < 8)} {
		    lappend moves(k,$c,$r) $x $y
		}
	    }
	}
    }
}

proc chess::center {c r} {
    variable square_size
    variable line_width

    set r [expr {7 - $r}]
    list [expr {$line_width + ($square_size * 0.5) + \
		    (($square_size + $line_width) * $c)}] \
	[expr {$line_width + ($square_size * 0.5) + \
		   (($square_size + $line_width) * $r)}]
}

proc chess::close {gid} {
    variable $gid
    upvar 0 $gid flags

    array unset flags
}

proc chess::exists {gid} {
    variable $gid
    info exists $gid
}

proc chess::open {gid} {
    variable options
    variable square_size
    variable line_width
    variable piece_name
    variable $gid
    upvar 0 $gid flags

    set jid $flags(opponent)

    set w $flags(window)
    if {[winfo exists $w]} {
	raise_win $w
	return
    }

    set title [::msgcat::mc "Chess with %s" [get_nick $flags(xlib) $jid chat]]
    add_win $w -title $title \
	       -tabtitle $title \
	       -class Chess \
	       -raise 1

    set board [canvas $w.board \
		   -width [expr {($square_size + $line_width) * 8}] \
		   -height [expr {($square_size + $line_width) * 8}]]
    pack $board -side left -anchor w -padx 10

    set flags(board) $board

    set flags(show_last_move) $options(show_last_move)
    set relief [expr {$flags(show_last_move) ? "sunken" : "raised"}]
    set slm [Button $w.show_last_move -text [::msgcat::mc "Show last move"] \
		-relief $relief \
		-command [list [namespace current]::toggle_show_last_move $gid]]
    pack $slm -side top -anchor w -fill x
    set flags(show_last_move_button) $slm

    set flags(flip) 0
    set slm [Button $w.flip -text [::msgcat::mc "Flip view"] \
		-relief raised \
		-command [list [namespace current]::toggle_flip_view $gid]]
    pack $slm -side top -anchor w -fill x
    set flags(flip_button) $slm

    frame $w.move 
    pack $w.move -side top -anchor w
    label $w.move.title -text [::msgcat::mc "Move: "]
    pack $w.move.title -side left
    label $w.move.on_move -anchor w \
	-textvariable [namespace current]::${gid}(move_label)
    pack $w.move.on_move -side left -anchor w

    set bbox [ButtonBox $w.bbox -orient vertical -spacing 0]
    $bbox add -text [::msgcat::mc "Propose a draw"] \
	-command [list [namespace current]::toggle_draw $gid]
    $bbox add -text [::msgcat::mc "Accept the draw proposal"] \
	-state disabled \
	-command [list [namespace current]::accept_draw $gid]
    $bbox add -text [::msgcat::mc "Resign the game"] \
	-command [list [namespace current]::send_resign $gid]
    grid columnconfigure $bbox 0 -weight 1
    pack $bbox -side bottom -anchor w -fill x
    set flags(bbox) $bbox
    set_tooltips

    #label $w.history -text [::msgcat::mc "History"]
    #pack $w.history -side top -anchor w
    set hsw [ScrolledWindow $w.hsw]
    pack $hsw -side top -fill x -expand yes
    set ht [text $w.text -wrap word -height 60 -state disabled]
    set font [$ht cget -font]
    set tabstop1 [font measure $font "99.."]
    set tabstop2 [font measure $font "99..Qa8-a8+= "]
    $ht configure -tabs "$tabstop1 $tabstop2"
    $ht tag configure attention -foreground [option get $ht errorForeground Text]
    $hsw setwidget $ht
    set flags(hw) $ht

    set dsq_color #77a26d
    set lsq_color #c8c365

    for {set c 0} {$c < 8} {incr c} {
	for {set r 0} {$r < 8} {incr r} {
	    set x1 [expr {$line_width + (($square_size + $line_width) * $c)}]
	    set x2 [expr {($square_size + $line_width) * ($c + 1)}]
	    set y1 [expr {$line_width + (($square_size + $line_width) * $r)}]
	    set y2 [expr {($square_size + $line_width) * ($r + 1)}]
	    set color [expr {($c+$r) % 2 ? $dsq_color : $lsq_color}]
	    set img [expr {($c+$r) % 2 ? "bf" : "wf"}]

	    $board create image $x1 $y1 -image chess/$img -anchor nw \
		-tags [list background [list cr $c [expr {7-$r}]]]
	    $board create rectangle $x1 $y1 $x2 $y2 \
		-outline {} \
		-tags [list square [list cr $c [expr {7-$r}]]]
	}
    }

    $board bind figure <1> \
	[list [namespace current]::start_drag_figure $gid %x %y]
    $board bind figure <B1-Motion> \
	[list [namespace current]::drag_figure $gid %x %y]
    $board bind figure <ButtonRelease-1> \
	[list [namespace current]::drag_end $gid %x %y]

    bind $w <Destroy> [list [namespace current]::close $gid]

    if {[is_black $flags(our_color)] && $options(flip_black_view)} {
	toggle_flip_view $gid
    }

    draw_position $gid
    update_controls $gid
    find_legal_moves $gid $flags(position,turn)
}

proc chess::toggle_flip_view {gid} {
    variable $gid
    upvar 0 $gid flags

    set flags(flip) [expr {!$flags(flip)}]

    set board $flags(board)

    for {set c 0} {$c < 8} {incr c} {
	for {set r 0} {$r < 8} {incr r} {
	    $board addtag [list temp [expr {7-$c}] [expr {7-$r}]] \
		   withtag [list cr $c $r]
	    $board dtag [list cr $c $r]
	}
    }

    for {set c 0} {$c < 8} {incr c} {
	for {set r 0} {$r < 8} {incr r} {
	    $board addtag [list cr $c $r] withtag [list temp $c $r]
	    $board dtag [list temp $c $r]
	}
    }
    
    set relief [expr {$flags(flip) ? "sunken" : "raised"}]
    $flags(flip_button) configure -relief $relief

    draw_position $gid
    highlight_last_move $gid
}

proc chess::set_tooltips {args} {
    variable options

    if {$options(show_tooltips)} {
	set tooltip0 [::msgcat::mc "Press button and make move if you want propose draw"]
	set tooltip1 [::msgcat::mc "Press button if you want accept the draw proposal"]
	set tooltip2 [::msgcat::mc "Press button if you want resign"]
    } else {
	set tooltip0 ""
	set tooltip1 ""
	set tooltip2 ""
    }

    foreach var [info vars [namespace current]::*] {
	upvar 0 $var flags
	if {[info exists flags(bbox)]} {
	    catch {
		$flags(bbox) itemconfigure 0 -helptext $tooltip0
		$flags(bbox) itemconfigure 1 -helptext $tooltip1
		$flags(bbox) itemconfigure 2 -helptext $tooltip2
	    }
	}
    }
}

proc chess::toggle_show_last_move {gid} {
    variable $gid
    upvar 0 $gid flags

    set flags(show_last_move) [expr {!$flags(show_last_move)}]

    set relief [expr {$flags(show_last_move) ? "sunken" : "raised"}]
    $flags(show_last_move_button) configure -relief $relief

    highlight_last_move $gid
}

proc chess::toggle_draw {gid} {
    variable $gid
    upvar 0 $gid flags

    set flags(position,draw) [expr {!$flags(position,draw)}]

    if {$flags(position,draw)} {
	$flags(bbox) itemconfigure 0 -relief sunken
    } else {
	$flags(bbox) itemconfigure 0 -relief raised
    }
}

proc chess::update_controls {gid {draw_proposed 0}} {
    variable $gid
    upvar 0 $gid flags

    $flags(bbox) itemconfigure 0 -relief raised

    if {[is_my_move $gid]} {
	$flags(board) config -cursor ""
	set flags(position,draw) 0
	if {$draw_proposed} {
	    $flags(bbox) itemconfigure 0 -state disabled
	    $flags(bbox) itemconfigure 1 -state normal
	    $flags(bbox) itemconfigure 2 -state disabled
	} else {
	    $flags(bbox) itemconfigure 0 -state normal
	    $flags(bbox) itemconfigure 1 -state disabled
	    $flags(bbox) itemconfigure 2 -state normal
	}
    } elseif {![is_white $flags(position,turn)] && \
	      ![is_black $flags(position,turn)]} {
	$flags(board) config -cursor ""
	$flags(bbox) itemconfigure 0 -state disabled
	$flags(bbox) itemconfigure 1 -state disabled
	$flags(bbox) itemconfigure 2 -state disabled
    } else {
	$flags(board) config -cursor watch
	$flags(bbox) itemconfigure 0 -state disabled
	$flags(bbox) itemconfigure 1 -state disabled
	$flags(bbox) itemconfigure 2 -state disabled
    }
}

proc chess::end_game {gid my_score message} {
    variable $gid
    upvar 0 $gid flags

    set opponent_score [expr {1 - $my_score}]

    if {[is_white $flags(our_color)]} {
	set score "$my_score : $opponent_score"
    } else {
	set score "$opponent_score : $my_score"
    }

    set flags(position,turn) none
    set flags(move_label) $message

    set hw $flags(hw)
    $hw configure -state normal
    catch {$hw delete attention.first attention.last}
    $hw delete {end -1 char} end
    $hw insert end "\n\t\t$score\n"
    $hw see end
    $hw configure -state disabled
}

proc chess::draw_position {gid} {
    variable $gid
    upvar 0 $gid flags

    $flags(board) delete figure

    for {set c 0} {$c < 8} {incr c} {
	for {set r 0} {$r < 8} {incr r} {
	    if {$flags(position,$c,$r) != ""} {
		if {$flags(flip)} {
		    set c1 [expr {7 - $c}]
		    set r1 [expr {7 - $r}]
		} else {
		    set c1 $c
		    set r1 $r
		}
		$flags(board) create image [center $c1 $r1] \
		    -image chess/$flags(position,$c,$r) \
		    -tags [list figure $flags(position,$c,$r) [list cr $c $r]]
	    }
	}
    }
}

proc chess::start_drag_figure {gid x y} {
    variable $gid
    upvar 0 $gid flags

    set board $flags(board)

    lassign [lindex [lmatch -regexp [$board gettags current] ^cr] 0] cr \
	flags(currentc) flags(currentr)

    set flags(last_x) [$board canvasx $x]
    set flags(last_y) [$board canvasy $y]
    $board raise current
    $board config -cursor hand2

    highlight_legal_moves $gid $flags(currentc) $flags(currentr)
}

proc chess::drag_figure {gid x y} {
    variable $gid
    upvar 0 $gid flags

    set board $flags(board)

    set x [$board canvasx $x]
    set y [$board canvasy $y]
    $board move current \
	[expr {$x - $flags(last_x)}] [expr {$y - $flags(last_y)}]
    set flags(last_x) $x
    set flags(last_y) $y

    $board itemconfigure dst_sq&&square -outline ""
    $board dtag dst_sq
    $board itemconfigure legal&&square -outline blue
    $board addtag dst_sq overlapping $x $y $x $y
    lassign [lindex [lmatch -regexp [$board gettags dst_sq&&background] ^cr] 0] \
	cr c r
    $board addtag dst_sq withtag [list cr $c $r]&&square
    $board itemconfigure dst_sq&&square -outline red
    $board itemconfigure dst_sq&&legal&&square -outline white
}

proc chess::drag_end {gid x y} {
    variable options
    variable $gid
    upvar 0 $gid flags

    set board $flags(board)

    set x [$board canvasx $x]
    set y [$board canvasy $y]
    $board itemconfigure dst_sq&&square -outline ""
    $board dtag dst_sq
    $board addtag dst_sq overlapping $x $y $x $y

    lassign [lindex [lmatch \
			 -regexp [$board gettags dst_sq&&background] \
			 ^cr] 0] cr c r
    $board dtag dst_sq

    if {$options(allow_illegal) || [is_my_move $gid]} {
	do_move $gid $flags(currentc) $flags(currentr) \
		$c $r "" $flags(position,draw)
    }
    
    update_controls $gid
    draw_position $gid

    unhighlight_legal_moves $gid

    highlight_last_move $gid
}

proc chess::highlight_last_move {gid} {
    variable $gid
    upvar 0 $gid flags

    $flags(board) itemconfigure square -outline ""
    $flags(board) itemconfigure square -outline ""
    
    if {[catch {lassign $flags(position,last_move) cf rf ct rt}]} {
	return
    }

    if {$flags(show_last_move)} {
	set color white
    } else {
	set color {}
    }

    $flags(board) itemconfigure [list cr $cf $rf]&&square -outline $color
    $flags(board) itemconfigure [list cr $ct $rt]&&square -outline $color
}

proc chess::do_move {gid cf rf ct rt prom draw} {
    variable options
    variable $gid
    upvar 0 $gid flags

    if {$cf == $ct && $rf == $rt || $ct == "" || $rt == ""} {
	return 0
    }

    set opts "-"
    set prm ""
    set suffix ""
    set checkmate 0
    set stalemate 0
    set my_move [is_my_move $gid]

    if {![is_move_legal $gid $cf $rf $ct $rt]} {
	if {$my_move && !$options(allow_illegal)} {
	    return 0
	}
	if {!$my_move && !$options(accept_illegal)} {
	    return 0
	}
    }

    save_position $gid

    if {$flags(position,$ct,$rt) != ""} {
	set opts ":"
    }
    set f $flags(position,$cf,$rf)
    set flags(position,$ct,$rt) $flags(position,$cf,$rf)
    set flags(position,$cf,$rf) ""

    if {$flags(position,$ct,$rt) == "wk"} {
	set flags(position,wk,c) $ct
	set flags(position,wk,r) $rt
    } elseif {$flags(position,$ct,$rt) == "bk"} {
	set flags(position,bk,c) $ct
	set flags(position,bk,r) $rt
    }
 	
    if {[is_white $flags(position,turn)]} {
 	set p "w"
 	set opp "b"
 	set r 0
    } else {
 	set p "b"
 	set opp "w"
 	set r 7
    }

    if {$rt == (7 - $r)} {
	if {$ct == 0} {
	    set flags(position,${opp}k,qcastling) 0
	} elseif {$ct == 7} {
	    set flags(position,${opp}k,kcastling) 0
	}
    }
    if {$flags(position,$ct,$rt) == "${p}k"} {
 	set flags(position,${p}k,kcastling) 0
 	set flags(position,${p}k,qcastling) 0
 	if {($ct - $cf) == 2} {
 	    set opts {0-0}
 	    set flags(position,5,$r) "${p}r"
 	    set flags(position,7,$r) ""
 	} elseif {($ct - $cf) == -2} {
 	    set opts {0-0-0}
 	    set flags(position,3,$r) "${p}r"
 	    set flags(position,0,$r) ""
 	}
    } elseif {$flags(position,$ct,$rt) == "${p}r"} {
 	if {($cf == 0) && ($rf == $r)} {
 	    set flags(position,${p}k,qcastling) 0
 	} elseif {($cf == 7) && ($rf == $r)} {
 	    set flags(position,${p}k,kcastling) 0
 	}
    }

    set enpassant 0
    if {$flags(position,$ct,$rt) == "${p}p"} {
	if {(($p == "w") && ($rt == 7)) || \
	    (($p == "b") && ($rt == 0))} {
	    if {$my_move} {
		set promote [promote_pawn $gid $p]
		if {$promote == -1} {
		    set flags(position,$ct,$rt) "${p}q"
		} else {
		    set flags(position,$ct,$rt) $promote
		}
	    } else {
		if {$prom != ""} {
		    set flags(position,$ct,$rt) "${p}$prom"
		}
		# If we are here, then it's an error...
	    }
	    set prm $flags(position,$ct,$rt)
 	} elseif {$flags(position,enpassant) == [list $ct $rt]} {
 	    if {[is_white $flags(position,turn)]} {
 		set flags(position,$ct,4) ""
 	    } else {
 		set flags(position,$ct,3) ""
 	    }
	    set opts ":"
 	} else {
 	    if {($rt - $rf) == 2} {
 		set flags(position,enpassant) [list $ct [expr {$rt - 1}]]
		set enpassant 1
 	    } elseif {($rt - $rf) == -2} {
 		set flags(position,enpassant) [list $ct [expr {$rt + 1}]]
		set enpassant 1
 	    }
 	}
    }

    if {!$enpassant} {
	set flags(position,enpassant) {}
    }

    set flags(position,last_move) [list $cf $rf $ct $rt]

    if {[is_white $flags(position,turn)]} {
	set flags(position,turn) black
	set check [test_check $gid $flags(position,bk,c) $flags(position,bk,r) \
				   $flags(position,bk,c) $flags(position,bk,r)]
    } else {
	set flags(position,turn) white
	set check [test_check $gid $flags(position,wk,c) $flags(position,wk,r) \
				   $flags(position,wk,c) $flags(position,wk,r)]
    }

    find_legal_moves $gid $flags(position,turn)

    if {$check} {
	if {[lempty $flags(legal_moves)]} {
	    set suffix "#"
	    set draw 0
	    set checkmate 1
	} else {
	    set suffix "+"
	}
    } else {
	if {[lempty $flags(legal_moves)]} {
	    set stalemate 1
	}
    }
    if {$draw} {
	append suffix "="
    }
    lappend opts $prm $suffix
    add_move_to_history $gid $cf $rf $ct $rt $f $opts

    set repetitions [add_to_repetitions $gid]

    if {$draw && !$my_move} {
	attention_message $gid \
	    [::msgcat::mc "\n\n Opponent proposes a draw\n\n"]
    }
	
    if {$my_move} {
	send_move $gid $cf $rf $ct $rt $prm
    }

    if {$stalemate} {
	# Draw by stalemate
	end_game $gid 0.5 [::msgcat::mc "Draw (Stalemate)"]
    } elseif {$repetitions >= 3} {
	# Draw by repetition
	end_game $gid 0.5 [::msgcat::mc "Draw (Repetition)"]
    } elseif {$checkmate} {
	if {$my_move} {
	    # I win
	    end_game $gid 1 [::msgcat::mc "You win (Checkmate)"]
	} else {
	    # Opponent wins
	    end_game $gid 0 [::msgcat::mc "Opponent wins (Checkmate)"]
	}
    }

    tab_set_updated [winfo parent $flags(board)] 1 mesg_to_user
    return 1
}

proc chess::promote_pawn {gid color} {
    variable options
    variable square_size
    variable line_width
    variable $gid
    upvar 0 $gid flags

    if {$options(always_queen)} {
	return -1
    }

    set w .promote
    if {[winfo exists $w]} {
	destroy $w
    }

    Dialog .promote -title [::msgcat::mc "Pawn promotion"] \
	-separator 0 -anchor e -default 0

    set fr [frame $w.fr]
    set select [canvas $w.select \
	-width [expr {($square_size + $line_width) * 4}] \
	-height [expr {($square_size + $line_width)}]]

    pack $fr -padx 3m -pady 1m
    pack $select -padx 3m -pady 1m

    set c 0
    foreach fig {q r b n} {
	set img [expr {$c % 2 ? "bf" : "wf"}]
	set x1 [expr {$line_width + (($square_size + $line_width) * $c)}]
	set x2 [expr {($square_size + $line_width) * ($c + 1)}]
	set y1 [expr {$line_width + (($square_size + $line_width) * 0)}]
	set y2 [expr {($square_size + $line_width) * (0 + 1)}]
	$select create image $x1 $y1 -image chess/$img -anchor nw \
	    -tags [list background fg$fig]
	$select create image $x1 $y1 -image chess/$color$fig -anchor nw \
	    -tags [list figure fg$fig]
	$select create rectangle $x1 $y1 $x2 $y2 \
		-outline {} \
		-tags [list square fg$fig]
	incr c
    }

    bind $select <Any-Enter> [list [namespace current]::promotion:motion %W %x %y]
    bind $select <Any-Motion> [list [namespace current]::promotion:motion %W %x %y]
    bind $select <Any-Leave> [list [namespace current]::promotion:leave %W %x %y]
    foreach fig {q r b n} {
	$select bind fg$fig <ButtonRelease-1> \
	    [list Dialog::enddialog $w $color$fig]
    }

    $w draw
}

proc chess::promotion:motion {c x y} {

    set x [$c canvasx $x]
    set y [$c canvasy $y]

    $c itemconfigure dst_sq&&square -outline ""
    $c dtag dst_sq
    
    $c addtag dst_sq overlapping $x $y $x $y
    set tags [$c gettags dst_sq&&background]
    set tag [lindex $tags [lsearch $tags fg*]]
    if {$tag != ""} {
	$c addtag dst_sq withtag $tag&&square
    }
    
    $c itemconfigure dst_sq&&square -outline blue
}

proc chess::promotion:leave {c x y} {
    $c itemconfigure dst_sq&&square -outline ""
    $c dtag dst_sq
}

proc chess::accept_draw {gid} {
    variable $gid
    upvar 0 $gid flags

    ::xmpp::sendIQ $flags(xlib) set \
	-query [::xmpp::xml::create turn \
			-xmlns games:board \
			-attrs [list type chess \
				     id $flags(id)] \
			-subelement [::xmpp::xml::create accept]] \
	-to $flags(opponent)

	end_game $gid 0.5 [::msgcat::mc "Draw (You accepted)"]
	update_controls $gid
	draw_position $gid
	highlight_last_move $gid
}

proc chess::send_resign {gid} {
    variable $gid
    upvar 0 $gid flags

    ::xmpp::sendIQ $flags(xlib) set \
	-query [::xmpp::xml::create turn \
			-xmlns games:board \
			-attrs [list type chess \
				     id $flags(id)] \
			-subelement [::xmpp::xml::create resign]] \
	-to $flags(opponent)

	end_game $gid 0 [::msgcat::mc "Opponent wins (You resigned)"]
	update_controls $gid
	draw_position $gid
	highlight_last_move $gid
}

proc chess::send_move {gid cf rf ct rt prom} {
    variable $gid
    upvar 0 $gid flags

    set move_tags [list [make_move_tag $gid $cf $rf $ct $rt $prom]]
    if {$flags(position,draw)} {
	lappend move_tags [::xmpp::xml::create draw]
    }

    ::xmpp::sendIQ $flags(xlib) set \
	-query [::xmpp::xml::create turn \
			-xmlns games:board \
			-attrs [list type chess \
				     id $flags(id)] \
			-subelements $move_tags] \
	-to $flags(opponent) \
	-command [list [namespace current]::send_result $gid]
}

proc chess::send_result {gid status xml} {
    if {$status == "error"} {
	attention_message $gid \
	    [::msgcat::mc "\n\n Opponent rejected move:\n %s\n\n" \
			  [error_to_string $xml]]
	restore_position $gid
    }
}

proc chess::make_move_tag {gid cf rf ct rt prom} {
    variable prom_name

    if {$prom == ""} {
	::xmpp::xml::create move \
		-attrs [list pos "$cf,$rf;$ct,$rt"]
    } else {
	set f [string index $prom 1]
	::xmpp::xml::create move \
		-attrs [list pos "$cf,$rf;$ct,$rt"] \
		-subelement [::xmpp::xml::create promotion \
				    -cdata $prom_name($f)]
    }
}

proc chess::add_to_repetitions {gid} {
    variable $gid
    upvar 0 $gid flags

    array set repetitions $flags(position,repetitions)
    set code [code_position $gid]
    if {[info exists repetitions($code)]} {
	incr repetitions($code)
    } else {
	set repetitions($code) 1
    }
    set flags(position,repetitions) [array get repetitions]
    return $repetitions($code)
}

proc chess::add_move_to_history {gid cf rf ct rt f opts} {
    variable piece_name
    variable $gid
    upvar 0 $gid flags

    incr flags(position,halfmove) 1

    lappend flags(position,history) [list $cf $rf $ct $rt $f $opts]

    set hw $flags(hw)
    $hw configure -state normal
    $hw delete 0.0 end

    $hw insert end "\t[::msgcat::mc White]\t[::msgcat::mc Black]\n"
    set i 1
    foreach {w b} $flags(position,history) {
	$hw insert end "${i}.\t"
	if {$w != {}} {
	    lassign $w cf rf ct rt f opts
	    lassign $opts opt prom check
	    if {$prom == ""} {
		set m ""
	    } else {
		set m $piece_name($prom)
	    }
	    if {($opt == "0-0") || ($opt == "0-0-0")} {
		$hw insert end "$opt$check\t"
	    } else {
		incr rf
		incr rt
		set lf [format %c [expr {$cf+97}]]
		set lt [format %c [expr {$ct+97}]]
		set n $piece_name($f)
		$hw insert end "$n${lf}$rf${opt}${lt}$rt$m$check\t"
	    }
	}
	if {$b != {}} {
	    lassign $b cf rf ct rt f opts
	    lassign $opts opt prom check
	    if {$prom == ""} {
		set m ""
	    } else {
		set m $piece_name($prom)
	    }
	    if {($opt == "0-0") || ($opt == "0-0-0")} {
		$hw insert end "$opt$check\n"
	    } else {
		incr rf
		incr rt
		set lf [format %c [expr {$cf+97}]]
		set lt [format %c [expr {$ct+97}]]
		set n $piece_name($f)
		$hw insert end "$n${lf}$rf${opt}${lt}$rt$m$check\n"
	    }
	}
	incr i
    }
    $hw see end
    $hw configure -state disabled
}

proc chess::find_pseudo_legal_moves {gid color callback} {
    variable moves
    variable $gid
    upvar 0 $gid flags

    set c [string index $color 0]

    for {set cf 0} {$cf < 8} {incr cf} {
	for {set rf 0} {$rf < 8} {incr rf} {
	    if {[string index $flags(position,$cf,$rf) 0] != $c} {
		continue
	    }

	    switch -- $flags(position,$cf,$rf) {
		"" { continue }

		wp {
		    set rt [expr {$rf + 1}]
		    if {$rf < 7 && $flags(position,$cf,$rt) == ""} {
			if {$rf == 6} {
			    eval $callback $gid $cf $rf $cf $rt promotion
			} else {
			    eval $callback $gid $cf $rf $cf $rt
			}
		    }

		    if {$rf == 1 && $flags(position,$cf,2) == "" && \
			    $flags(position,$cf,3) == ""} {
			eval $callback $gid $cf $rf $cf 3
		    }

		    foreach {ct rt} $moves(wpt,$cf,$rf) {
			if {[is_black $flags(position,$ct,$rt)] || \
				($flags(position,enpassant) == [list $ct $rt])} {
			    if {$rf == 6} {
				eval $callback $gid $cf $rf $ct $rt promotion
			    } else {
				eval $callback $gid $cf $rf $ct $rt
			    }
			}
		    }
		}

		bp {
		    set rt [expr {$rf - 1}]
		    if {$rf > 0 && $flags(position,$cf,$rt) == ""} {
			if {$rf == 1} {
			    eval $callback $gid $cf $rf $cf $rt promotion
			} else {
			    eval $callback $gid $cf $rf $cf $rt
			}
		    }

		    if {$rf == 6 && $flags(position,$cf,5) == "" && \
			    $flags(position,$cf,4) == ""} {
			eval $callback $gid $cf $rf $cf 4
		    }

		    foreach {ct rt} $moves(bpt,$cf,$rf) {
			if {[is_white $flags(position,$ct,$rt)] || \
				$flags(position,enpassant) == [list $ct $rt]} {
			    if {$rf == 1} {
				eval $callback $gid $cf $rf $ct $rt promotion
			    } else {
				eval $callback $gid $cf $rf $ct $rt
			    }
			}
		    }
		}

		wn -
		bn {
		    foreach {ct rt} $moves(n,$cf,$rf) {
			if {[is_same_color $flags(position,$cf,$rf) \
				 $flags(position,$ct,$rt)]} \
			    continue
			eval $callback $gid $cf $rf $ct $rt
		    }
		}

		wb -
		bb {
		    foreach d {d1 d2 d3 d4} {
			foreach {ct rt} $moves($d,$cf,$rf) {
			    if {[is_same_color $flags(position,$cf,$rf) \
				 $flags(position,$ct,$rt)]} \
				break
			    eval $callback $gid $cf $rf $ct $rt
			    if {$flags(position,$ct,$rt) != ""} \
				break
			}
		    }
		}

		wr -
		br {
		    foreach d {h1 h2 v1 v2} {
			foreach {ct rt} $moves($d,$cf,$rf) {
			    if {[is_same_color $flags(position,$cf,$rf) \
				$flags(position,$ct,$rt)]} \
				break
			    eval $callback $gid $cf $rf $ct $rt
			    if {$flags(position,$ct,$rt) != ""} \
				break
			}
		    }
		}

		wq -
		bq {
		    foreach d {d1 d2 d3 d4 h1 h2 v1 v2} {
			foreach {ct rt} $moves($d,$cf,$rf) {
			    if {[is_same_color $flags(position,$cf,$rf) \
				$flags(position,$ct,$rt)]} \
				break
			    eval $callback $gid $cf $rf $ct $rt
			    if {$flags(position,$ct,$rt) != ""} \
				break
			}
		    }
		}

 		wk {
 		    foreach {ct rt} $moves(k,$cf,$rf) {
 			if {[is_same_color $flags(position,$cf,$rf) \
				 $flags(position,$ct,$rt)]} \
 			    continue
 			eval $callback $gid $cf $rf $ct $rt
 		    }
 		    if {($cf == 4) && ($rf == 0)} {
 			if {$flags(position,wk,kcastling) && \
				$flags(position,5,0) == "" && \
				$flags(position,6,0) == ""} {
 			    eval $callback $gid 4 0 6 0 kcastling
 			}
 			if {$flags(position,wk,qcastling) && \
				$flags(position,3,0) == "" && \
				$flags(position,2,0) == "" && \
				$flags(position,1,0) == ""} {
 			    eval $callback $gid 4 0 2 0 qcastling
 			}
 		    }
 		}
 		bk {
 		    foreach {ct rt} $moves(k,$cf,$rf) {
 			if {[is_same_color $flags(position,$cf,$rf) \
				 $flags(position,$ct,$rt)]} \
 			    continue
 			eval $callback $gid $cf $rf $ct $rt
 		    }
 		    if {($cf == 4) && ($rf == 7)} {
 			if {$flags(position,bk,kcastling) && \
				$flags(position,5,7) == "" && \
				$flags(position,6,7) == ""} {
 			    eval $callback $gid 4 7 6 7 kcastling
 			}
 			if {$flags(position,bk,qcastling) && \
				$flags(position,3,7) == "" && \
				$flags(position,2,7) == "" && \
				$flags(position,1,7) == ""} {
 			    eval $callback $gid 4 7 2 7 qcastling
			}
		    }
		}
	    }
	}
    }
}

proc chess::find_legal_moves {gid color} {
    variable $gid
    upvar 0 $gid flags

    set flags(legal_moves) {}
    find_pseudo_legal_moves $gid $color check_legal_callback
}

proc chess::check_legal_callback {gid cf rf ct rt {opt ""}} {
    variable $gid
    upvar 0 $gid flags

    if {![test_check $gid $cf $rf $ct $rt $opt]} {
	lappend flags(legal_moves) [list $cf $rf $ct $rt $opt]
    }
}

proc chess::test_check {gid cf rf ct rt {opt ""}} {
    variable $gid
    upvar 0 $gid flags

    set enpassantback ""
    set enpassantx ""
    set enpassanty ""
    if {[is_white $flags(position,turn)]} {
	set color black
	set f wk
	set of bk
	if {($rf == 4) && \
		($flags(position,$cf,$rf) == "wp") && \
		($flags(position,enpassant) == [list $ct $rt])} {
	    set enpassantback "bp"
	    set enpassantx $ct
	    set enpassanty [expr {$rt - 1}]
	    set flags(position,$enpassantx,$enpassanty) ""
	}
    } else {
	set color white
	set f bk
	set of wk
	if {($rf == 3) && \
		($flags(position,$cf,$rf) == "bp") && \
		($flags(position,enpassant) == [list $ct $rt])} {
	    set enpassantback "wp"
	    set enpassantx $ct
	    set enpassanty [expr {$rt + 1}]
	    set flags(position,$enpassantx,$enpassanty) ""
	}
    }
    set checks 0

    set back $flags(position,$ct,$rt)
    set flags(position,$ct,$rt) $flags(position,$cf,$rf)
    set flags(position,$cf,$rf) ""
    if {$flags(position,$ct,$rt) == "wk"} {
	set flags(position,wk,c) $ct
	set flags(position,wk,r) $rt
    } elseif {$flags(position,$ct,$rt) == "bk"} {
	set flags(position,bk,c) $ct
	set flags(position,bk,r) $rt
    }

    set kc  $flags(position,$f,c)
    set kr  $flags(position,$f,r)
    set okc $flags(position,$of,c)
    set okr $flags(position,$of,r)


    if {$opt == "kcastling"} {
 	set kcs [list 4 5 6]
    } elseif {$opt == "qcastling"} {
 	set kcs [list 4 3 2]
    } else {
 	set kcs [list $kc]
    }

    foreach c $kcs {
 	incr checks [test_figures $gid $color $c $kr]
 	incr checks [test_pawns $gid $color $c $kr]
 	if {[info exists okc]} {
 	    set dx [expr {abs($c-$okc)}]
 	    set dy [expr {abs($kr-$okr)}]
 	    if {($dx <= 1) && ($dy <= 1)} {
     		incr checks
 	    }
 	}
    }

    set flags(position,$cf,$rf) $flags(position,$ct,$rt)
    set flags(position,$ct,$rt) $back
    if {$flags(position,$cf,$rf) == "wk"} {
	set flags(position,wk,c) $cf
	set flags(position,wk,r) $rf
    } elseif {$flags(position,$cf,$rf) == "bk"} {
	set flags(position,bk,c) $cf
	set flags(position,bk,r) $rf
    }
    if {$enpassantback != ""} {
	set flags(position,$enpassantx,$enpassanty) $enpassantback 
    }

    return $checks
}

proc chess::test_pawns {gid color c r} {
    variable moves
    variable $gid
    upvar 0 $gid flags

    if {[is_white $color]} {
	foreach {x y} $moves(bpt,$c,$r) {
	    if {$flags(position,$x,$y) == "wp"} {
	    	return 1
	    }
	}
    } else {
	foreach {x y} $moves(wpt,$c,$r) {
	    if {$flags(position,$x,$y) == "bp"} {
	    	return 1
	    }
	}
    }
    return 0
}

proc chess::test_figures {gid color c r} {
    variable moves
    variable $gid
    upvar 0 $gid flags

    if {[is_white $color]} {
    	foreach i {d1 d2 d3 d4} {
	    foreach {x y} $moves($i,$c,$r) {
    		switch -- $flags(position,$x,$y) {
		    "" {continue}
		    wq -
		    wb {return 1}
		    default {break}
		}
	    }
	}
    	foreach i {h1 h2 v1 v2} {
	    foreach {x y} $moves($i,$c,$r) {
    		switch -- $flags(position,$x,$y) {
		    "" {continue}
		    wq -
		    wr {return 1}
		    default {break}
		}
	    }
	}
	foreach {x y} $moves(n,$c,$r) {
    	    switch -- $flags(position,$x,$y) {
		wn {return 1}
		default {continue}
	    }
	}
    } else {
    	foreach i {d1 d2 d3 d4} {
	    foreach {x y} $moves($i,$c,$r) {
    		switch -- $flags(position,$x,$y) {
		    "" {continue}
		    bq -
		    bb {return 1}
		    default {break}
		}
	    }
	}
    	foreach i {h1 h2 v1 v2} {
	    foreach {x y} $moves($i,$c,$r) {
    		switch -- $flags(position,$x,$y) {
		    "" {continue}
		    bq -
		    br {return 1}
		    default {break}
		}
	    }
	}
	foreach {x y} $moves(n,$c,$r) {
    	    switch -- $flags(position,$x,$y) {
		bn {return 1}
		default {continue}
	    }
	}
    }
    return 0
}

proc chess::is_move_legal {gid cf rf ct rt} {
    variable $gid
    upvar 0 $gid flags

    expr {[lmatch -regexp $flags(legal_moves) ^[list $cf $rf $ct $rt]] != {}}
}

proc chess::highlight_legal_moves {gid cf rf} {
    variable $gid
    upvar 0 $gid flags

    foreach move [lmatch -regexp $flags(legal_moves) ^[list $cf $rf]] {
	lassign $move cft rft ct rt
	$flags(board) addtag legal withtag [list cr $ct $rt]&&square

    }
    $flags(board) itemconfigure legal&&square -outline blue
}

proc chess::unhighlight_legal_moves {gid} {
    variable $gid
    upvar 0 $gid flags

    foreach sq [$flags(board) find withtag legal&&square] {
	$flags(board) itemconfigure $sq \
	    -outline [$flags(board) itemcget $sq -fill]
    }
    $flags(board) dtag legal
}

proc chess::attention_message {gid message} {
    variable $gid
    upvar 0 $gid flags

    set hw $flags(hw)
    $hw configure -state normal
    $hw delete {end -1 char} end
    $hw insert end $message attention
    $hw see end
    $hw configure -state disabled
}

proc chess::is_my_move {gid} {
    variable $gid
    upvar 0 $gid flags

    is_same_color $flags(position,turn) $flags(our_color)
}

proc chess::is_white {f} {
    string equal -length 1 $f w
}

proc chess::is_black {f} {
    string equal -length 1 $f b
}

proc chess::is_same_color {f1 f2} {
    string equal -length 1 $f1 $f2
}

proc chess::add_groupchat_user_menu_item {m xlib jid} {
    set mm $m.gamesmenu
    if {![winfo exists $mm]} {
	menu $mm -tearoff 0
	$m add cascade -label [::msgcat::mc "Games"] -menu $mm
    }
    $mm add command -label [::msgcat::mc "Chess..."] \
	-command [list [namespace current]::invite_dialog $xlib $jid]
}

proc chess::iq_create {varname xlib from iqid xml} {
    upvar 2 $varname var

    ::xmpp::xml::split $xml tag xmlns attrs cdata subels

    if {[::xmpp::xml::getAttr $attrs type] == "chess"} {
	if {[::xmpp::xml::isAttr $attrs color]} {
	    set color [::xmpp::xml::getAttr $attrs color]
	    switch -- $color {
		white -
		black { }
		default {
		    set var [list error modify bad-request]
		}
	    }
	} else {
	    set color white
	}
	set var [[namespace current]::invited_dialog \
		     $xlib $from $iqid \
		     [::xmpp::xml::getAttr $attrs id] \
		     $color]
    }
    return
}

proc chess::iq_turn {varname xlib from xml} {
    upvar 2 $varname var

    ::xmpp::xml::split $xml tag xmlns attrs cdata subels

    if {[::xmpp::xml::getAttr $attrs type] == "chess"} {
	set gid [make_gid $from [::xmpp::xml::getAttr $attrs id]]
	if {[exists $gid]} {
	    set var [[namespace current]::turn_recv $gid $subels]
	} else {
	    set var [list error cancel item-not-found]
	}
    }
    return
}


# Common games:board part
proc iq_games_board_create {xlib from xml args} {
    set res [list error cancel feature-not-implemented]
    set iqid [::xmpp::xml::getAttr $args -id]
    hook::run games_board_create_hook res $xlib $from $iqid $xml
    return $res
}

proc iq_games_board_turn {xlib from xml args} {
    set res [list error cancel feature-not-implemented]
    hook::run games_board_turn_hook res $xlib $from $xml
    return $res
}

