namespace eval jigsaw-main {
    variable State

    set State(bgcolor)    white
    set State(fullscreen) 0
    set State(mark)       1
    set State(iconsize)   30
    set State(playmode)   false
}

proc jigsaw-main::setToplevel { w } {
    variable State

    set State(w:top) $w
}

proc jigsaw-main::getToplevel {} {
    variable State

    return $State(w:top)
}

proc jigsaw-main::setCanvas { w } {
    variable State

    set State(w:c) $w
}

proc jigsaw-main::getCanvas {} {
    variable State

    return $State(w:c)
}

################################################
#
# Function:    randomizeStackOrder
#
# Description: Makes the stackorder random on a canvas
#
#################################################
proc jigsaw-main::randomizeStackOrder {c} {
    set ids [$c find withtag !transp]
    foreach id [shuffle $ids] {
        $c raise $id
    }
}

################################################
#
# Function:    shuffle
#
# Description: Shuffles a list
#
#################################################
proc jigsaw-main::shuffle { list } {
    set n 1
    set slist {}
    foreach item $list {
        set index [expr {int(rand()*$n)}]
        set slist [linsert $slist $index $item]
        incr n
    }
    return $slist
}

#############################################################################
#
# Function Name: setGeometry
#
# Description:   
#
# Parameters:    w - window to centralise
#
#############################################################################
proc jigsaw-main::setGeometry {w} {
    set sx [winfo screenwidth $w]
    set sy [winfo screenheight $w]
    set rx [winfo reqwidth $w]
    set ry [winfo reqheight $w]
    
    set x [expr {int(($sx - $rx) / 2)}]
    set y [expr {int(($sy - $ry) / 2)}]
    
    wm geometry $w "+${x}+${y}"
}

#############################################################################
#
# Function Name: stateChangeInit
#
# Description:   Initialise variables for stateChange proc
#
# Parameters:
#
#############################################################################
proc jigsaw-main::stateChangeInit {} {
    variable sc

    # Get disabled colors
    button .scfakebut
    set ncol [.scfakebut cget -foreground]
    set dcol [.scfakebut cget -disabledforeground]
    destroy .scfakebut

    set sc(Label,attr)       "-foreground"
    set sc(Button,attr)      "-state"
    set sc(Radiobutton,attr) "-state"
    set sc(Scale,attr)       "-state"
    set sc(Spinbox,attr)     "-state"
    set sc(Labelframe,attr)  "-foreground"

    set sc(-foreground,nval) $ncol
    set sc(-foreground,dval) $dcol
    set sc(-state,nval) "normal"
    set sc(-state,dval) "disabled"

    set sc(init) 1
}

#############################################################################
#
# Function Name: stateChange
#
# Description:   Go through the window hierarchy and either:
#                1) Record state
#                2) Restore state
#                3) Disable all
#                4) Enable all
#
# Parameters:    w - widget at top of hierarchy
#
#############################################################################
proc jigsaw-main::stateChange { w mode } {
    variable sc
 
    if {![info exists sc(init)]} {
        stateChangeInit
    }
 
    set class [winfo class $w]

    if {[info exists sc($class,attr)]} {
        switch -- $mode {
            record {
                set sc(rec,$w) [$w cget $sc($class,attr)]
            }
            restore {
                $w configure $sc($class,attr) $sc(rec,$w)
            }
            normal {
                $w configure $sc($class,attr) $sc($sc($class,attr),nval)
            }
            disabled {
                $w configure $sc($class,attr) $sc($sc($class,attr),dval)
            }
        }
    }

    set children [winfo children $w]
    foreach child $children {
        stateChange $child $mode
    }
}

###################################################
# lremove
#
# Remove $item from $list 
#################################################### 
proc jigsaw-main::lremove {list item} { 
    if {[set index [lsearch $list $item]] != -1} { 
        set list [lreplace $list $index $index]  
    } 
    return $list 
} 

###################################################
# jigsaw-main::setFilePath
#
# Description: Store the filename in use
#
# Parameters:  w        - parent window
#              filePath - file path
###################################################
proc jigsaw-main::setFilePath {w filePath} {
    variable State
 
    set State(filePath) $filePath
    wm title $w "Jigsaw Puzzler - [file tail $State(filePath)]"
}

###################################################
# jigsaw-main::unsetFilePath
#
# Description: Un-Store the filename in use
#
# Parameters:  w        - parent window
###################################################
proc jigsaw-main::unsetFilePath {w} {
    variable State

    catch {unset State(filePath)}
    wm title $w "Jigsaw Puzzler"
}

###################################################
# jigsaw-main::saveAs
#
# Description: Save data to file
#
# Parameters:  w - parent window
###################################################
proc jigsaw-main::saveAs { w c } {
    variable State

    set fileTypes {
        { {Puzzle files} {.puz} }
    }
    set initDir [pwd]
    set initFile "MyPuzzle.puz"
    if { [info exists State(filePath)] } {
        set initDir [file dirname $State(filePath)]
        set initFile $State(filePath)
    }

    set filePath [tk_getSaveFile \
        -filetypes $fileTypes \
        -title "Select puzzle file" \
        -parent $w \
        -confirmoverwrite true \
        -initialfile [file tail $initFile] \
        -initialdir $initDir]

    if { $filePath ne "" } {
        # Allow the dialog to be removed
        update idletasks

        if { [file extension $filePath] ne ".puz" } {
            append filePath ".puz"
        }

        # Now save
        if {[catch {jigsaw-play::save $c $filePath} message]} {
            tk_messageBox -title "Jigsaw Puzzler Save" \
                    -icon error \
                    -message $message \
                    -type ok \
                    -parent $w
        } else  {
            jigsaw-main::setFilePath $w $filePath
        }
    }
}

###################################################
# jigsaw-main::save
#
# Description: Save data to file
#
# Parameters:  w - parent window
###################################################
proc jigsaw-main::save { w c } {
    variable State

    if {![info exists State(filePath)]} {
        saveAs $w $c
    } else {
        jigsaw-play::save $c $State(filePath)
    }
}

###################################################
# jigsaw-main::close
#
# Description: Close the current jigsaw
#
# Parameters:  w - parent window
#              c - canvas path
###################################################
proc jigsaw-main::close { w c } {
    jigsaw-play::closeJigsaw $c

    unsetFilePath $w            

    updateCanvasScrollregion $c
    update idletasks
    centraliseItems $c

    updateToolbar 0
}

###################################################
# jigsaw-main::open
#
# Description: Open a puzzle data file
#
# Parameters:  w - parent window
###################################################
proc jigsaw-main::open { w c } {
    variable State

    set fileTypes {
        { {Puzzle files} {.puz} }
        { {All files}    {*.*}  }
    }
    set initDir [pwd]
    if { [info exists State(filePath)] } {
        set initDir [file dirname $State(filePath)]
    }
    set filePath [tk_getOpenFile \
        -filetypes $fileTypes \
        -initialdir $initDir \
        -parent $w \
        -title "Select puzzle file"]

    if { $filePath ne "" } {
        # Allow the dialog to be removed
        update idletasks
 
        if {[catch {jigsaw-play::open $c $filePath} message]} {
            tk_messageBox -title "Jigsaw Puzzler Open" \
                    -icon error \
                    -message $message \
                    -type ok \
                    -parent $w
        } else  {
            updateToolbar 1
            jigsaw-main::setFilePath $w $filePath
        }
    }
}

###################################################
# jigsaw-main::openDirect
#
# Description: Open a puzzle data file without interaction
#              - used if command line argument is passed
#
# Parameters:  w - parent window
#              c - canvas path
#              filePath - filePath of file to open
###################################################
proc jigsaw-main::openDirect { w c filePath} {
    if {[catch {jigsaw-play::open $c $filePath} message]} {
        tk_messageBox -title "Jigsaw Puzzler Open" \
                -icon error \
                -message $message \
                -type ok \
                -parent $w
    } else  {
        updateToolbar 1
        jigsaw-main::setFilePath $w $filePath
    }
}

###################################################
# jigsaw-main::setBackgroundColor
#
# Description: Set the canvas background color
#
# Parameters:  c - canvas path
###################################################
proc jigsaw-main::setBackgroundColor {c newCol} {
    variable State

    set State(bgcolor) $newCol
    jigsaw-play::setBackgroundColor $newCol
    $c configure -bg $newCol
}

###################################################
# jigsaw-main::selectBackgroundColor
#
# Description: Select the canvas background color
#
# Parameters:  w - parent window
#              c - canvas path
###################################################
proc jigsaw-main::selectBackgroundColor {w c} {
    variable State

    set newCol [tk_chooseColor -title "Select background color" \
            -initialcolor $State(bgcolor) -parent $w]

    if { "$newCol" ne "" } {
        jigsaw-main::setBackgroundColor $c $newCol
    }
}

proc jigsaw-main::updateToolbar { isOpen } {
    variable State
    variable Toolbar
 
    if { $isOpen == 1 } {
        $Toolbar(back)   configure -state normal
        $Toolbar(save)   configure -state normal
        $Toolbar(play)   configure -state normal
        $Toolbar(mark)   configure -state normal
        $Toolbar(dark)   configure -state normal
        $Toolbar(lite)   configure -state normal
        if { ! $State(playmode) } {
            $Toolbar(saveas) configure -state normal
        }
    } else {
        $Toolbar(back)   configure -state disabled
        $Toolbar(save)   configure -state disabled
        $Toolbar(play)   configure -state disabled
        $Toolbar(mark)   configure -state disabled
        $Toolbar(dark)   configure -state disabled
        $Toolbar(lite)   configure -state disabled
        if { ! $State(playmode) } {
            $Toolbar(saveas) configure -state disabled
        }
    }
}

###################################################
# jigsaw-main::create
#
# Description: Create a new jigsaw
#
# Parameters:  w - parent window
#              c - canvas path
###################################################
proc jigsaw-main::create {w c} {
    variable State

    jigsaw-wizard::createWizard $State(w:top)
}

###################################################
# updateCanvasScrollregion 
#
# Updates the scroll region of the canvas to
# include everything on the canvas
###################################################
proc jigsaw-main::updateCanvasScrollregion { c } {

    # Setup scrollregion to include everything on the canvas
    $c configure -scrollregion [$c bbox !transp]
}

###################################################
# buildCanvas 
#
# Build scrollable canvas within widget w
###################################################
proc jigsaw-main::buildCanvas { w } {
    variable State

    set hscroll $w.hscroll
    set vscroll $w.vscroll
    set canvas $w.c

    scrollbar $hscroll -orient horiz -command "$canvas xview"
    scrollbar $vscroll -command "$canvas yview"

    set State(w:c) [canvas $canvas \
            -bg $State(bgcolor) \
            -width 800 -height 600 \
            -highlightthickness 0 \
            -xscrollcommand "$hscroll set" \
            -yscrollcommand "$vscroll set"]
    grid $canvas  -row 0 -column 0 -sticky nswe -padx 0 -pady 0
    grid $vscroll -row 0 -column 1 -sticky ns
    grid $hscroll -row 1 -column 0 -sticky ew
    grid rowconfig $w 0 -weight 1
    grid columnconfig $w 0 -weight 1

    ::autoscroll::autoscroll $hscroll
    ::autoscroll::autoscroll $vscroll
 
    return $State(w:c)
}

###################################################
# closeDown
#
# Close down gracefully
###################################################
proc jigsaw-main::closeDown { w } {
    destroy $w
    exit
}

###################################################
# aboutBox
#
# Display an about box
###################################################
proc jigsaw-main::aboutBox { w } {
    set message "Jigsaw Puzzler 1.0\n"
    append message "(c) David Easton 2007\n"
    append message "www.easton.me.uk\n\n"
    append message "Jigsaw Puzzler 2.0\n"
    append message "(c) Paul Obermeier 2025\n"
    append message "https://www.tcl3d.org/bawt"

    tk_messageBox -icon info \
            -title "About jigsaw puzzler" \
            -type ok \
            -parent $w \
            -message $message
}

###################################################
# jigsaw-main::setTheme
#
# Description: Sets the theme
#
# Parameters:  theme
###################################################
proc jigsaw-main::setTheme {theme} {
    variable State
 
    set State(theme) $theme
    style theme use $theme
}

proc jigsaw-main::setFullscreen { w } {
    variable State

    set State(fullscreen) 1
    toggleFullscreen $w
}

proc jigsaw-main::toggleFullscreen { w } {
    variable State

    if { $State(fullscreen) } {
        wm attributes $w -fullscreen 1
        wm deiconify $w
    } else {
        wm attributes $w -fullscreen 0
    }
}

proc jigsaw-main::toggleMark {} {
    variable State

    if { $State(mark) } {
        jigsaw-play::showMark
    } else {
        jigsaw-play::hideMark
    }
}

proc jigsaw-main::setTransparency { c inc } {
    variable Toolbar

    set clip [jigsaw-play::readBackgroundImage $c $inc]
    $Toolbar(dark) configure -state normal
    $Toolbar(lite) configure -state normal
    if { $clip > 0 } {
        $Toolbar(dark) configure -state disabled
    }
    if { $clip < 0 } {
        $Toolbar(lite) configure -state disabled
    }
}

proc jigsaw-main::setPlayMode {} {
    variable State
    variable Toolbar

    set State(playmode) true
    destroy $Toolbar(create)
    destroy $Toolbar(saveas)
}

proc jigsaw-main::buildToolbar { w c } {
    variable Toolbar
    variable State

    set fr $w.toolFr
    ttk::frame $fr
    pack $fr -side top -fill both -expand 1

    set size $State(iconsize)

    ttk::button $fr.create -image [PuppyIcons image "add" $size] -style Toolbutton \
                           -command [list jigsaw-main::create $w $c]
    pack $fr.create -side left
    tooltip::tooltip $fr.create "Create new puzzle"
    set Toolbar(create) $fr.create

    ttk::button $fr.open -image [PuppyIcons image "open" $size] -style Toolbutton \
                         -command [list jigsaw-main::open $w $c]
    pack $fr.open -side left
    tooltip::tooltip $fr.open "Open puzzle file"
    set Toolbar(open) $fr.open

    ttk::button $fr.play -image [PuppyIcons image "application-x-object" $size] -style Toolbutton \
                           -takefocus 0 -command [list jigsaw-play::restartJigsaw $c]
    pack $fr.play -side left
    tooltip::tooltip $fr.play "Restart puzzle"
    $fr.play configure -state disabled
    set Toolbar(play) $fr.play

    ttk::button $fr.save -image [PuppyIcons image "save" $size] -style Toolbutton \
                         -command [list jigsaw-main::save $w $c]
    pack $fr.save -side left
    tooltip::tooltip $fr.save "Save current state of puzzle"
    $fr.save configure -state disabled
    set Toolbar(save) $fr.save

    ttk::button $fr.saveas -image [PuppyIcons image "save-as" $size] -style Toolbutton \
                           -command [list jigsaw-main::saveAs $w $c]
    pack $fr.saveas -side left
    tooltip::tooltip $fr.saveas "Save puzzle to file"
    $fr.saveas configure -state disabled
    set Toolbar(saveas) $fr.saveas

    label $fr.prog -text "  0 /  0"
    pack $fr.prog -side left -fill y
    set Toolbar(progress) $fr.prog
    jigsaw-play::storeWidgetPaths [list progressLabel $Toolbar(progress)]

    ttk::separator $fr.sep1 -orient vertical
    pack $fr.sep1 -side left -padx 5 -fill y

    ttk::checkbutton $fr.mark -image [PuppyIcons image "placement_left_right" $size] -style Toolbutton \
                              -variable jigsaw-main::State(mark) -command [list jigsaw-main::toggleMark]
    pack $fr.mark -side left
    tooltip::tooltip $fr.mark "Toggle piece marker"
    $fr.mark configure -state disabled
    set Toolbar(mark) $fr.mark

    ttk::checkbutton $fr.full -image [PuppyIcons image "fullscreen" $size] -style Toolbutton \
                              -variable jigsaw-main::State(fullscreen) -command [list jigsaw-main::toggleFullscreen $w]
    pack $fr.full -side left
    tooltip::tooltip $fr.full "Toggle fullscreen mode"

    ttk::button $fr.back -image [PuppyIcons image "graphics" $size] -style Toolbutton \
                         -command [list jigsaw-main::selectBackgroundColor $w $c]
    pack $fr.back -side left
    tooltip::tooltip $fr.back "Select background color"
    $fr.back configure -state disabled
    set Toolbar(back) $fr.back

    ttk::button $fr.dark -image [PuppyIcons image "puppy_theme_eve_touch" $size] -style Toolbutton \
                         -command [list jigsaw-main::setTransparency $c 10]
    pack $fr.dark -side left
    tooltip::tooltip $fr.dark "Decrease transparency"
    $fr.dark configure -state disabled
    set Toolbar(dark) $fr.dark

    ttk::button $fr.lite -image [PuppyIcons image "puppy_theme_daylight_touch" $size] -style Toolbutton \
                         -command [list jigsaw-main::setTransparency $c -10]
    pack $fr.lite -side left
    tooltip::tooltip $fr.lite "Increase transparency"
    $fr.lite configure -state disabled
    set Toolbar(lite) $fr.lite

    ttk::separator $fr.sep2 -orient vertical
    pack $fr.sep2 -side left -padx 5 -fill y

    ttk::button $fr.help -image [PuppyIcons image "info" $size] -style Toolbutton \
                         -command [list jigsaw-main::aboutBox $w]
    pack $fr.help -side left
    tooltip::tooltip $fr.help "Info about Jigsaw Puzzler"


    ttk::button $fr.exit -image [PuppyIcons image "shutdown" $size] -style Toolbutton \
                         -command [list jigsaw-main::closeDown $w]
    pack $fr.exit -side right
    tooltip::tooltip $fr.exit "Quit program"

    return $fr
}

# http://wiki.tcl.tk/2451 plus a little
proc jigsaw-main::getCanvasViewArea {win} {
    set sregion [$win cget -scrollregion]
    if {[llength $sregion]} {
        foreach {x1 y1 x2 y2} $sregion {break}
        set totalXArea [expr {$x2 - $x1}]
        set totalYArea [expr {$y2 - $y1}]
    } else  {
        set totalXArea [winfo width $win]
        set totalYArea [winfo height $win]
    }
    set xview [$win xview]
    set yview [$win yview]
 
    # Allow for no scrollbars and canvas filling larger area
    if { "$xview" == "0 1"} {
        set totalXArea [winfo width $win]
    }
    if { "$yview" == "0 1"} {
        set totalYArea [winfo height $win]
    }

    set xstart [expr {int([lindex $xview 0] * $totalXArea)}]
    set xend [expr {int([lindex $xview 1] * $totalXArea)}]
 
    set ystart [expr {int([lindex $yview 0] * $totalYArea)}]
    set yend [expr {int([lindex $yview 1] * $totalYArea)}]

    return [list $xstart $ystart $xend $yend]
}

proc jigsaw-main::centraliseItems { c } {
    set bbox [$c bbox !transp]
    if {$bbox == ""} { return }
 
    # Limits of items in canvas
    foreach { x1 y1 x2 y2 } $bbox { break }
    # Real canvas limits
    foreach { u1 v1 u2 v2 } [getCanvasViewArea $c] {break}
 
    set dx [expr {$x2-$x1}]
    set dy [expr {$y2-$y1}]
    set du [expr {$u2-$u1}]
    set dv [expr {$v2-$v1}]

    set xmove 0
    set ymove 0

    # If the items fit in the canvas, then move
    # them to the centre
    if {$du > $dx} {
        set xmove [expr {$u1 - $x1 + ($du-$dx)/2}]
    }
    if {$dv > $dy} {
        set ymove [expr {$v1 - $y1 + ($dv-$dy)/2}]
    }
    $c move !transp $xmove $ymove
}
