################################################################################
# jigsaw-play.tcl
# 
# Package to implement playing of jigsaw
# 
# Author: David Easton
################################################################################

namespace eval jigsaw-play {
}

###################################################
# jigsaw-play::selectPiece
#
# Description: Called when a user tries to select a puzzle piece.
#              This specifically is used so that a piece is only
#              selected, if a non-transparent part of the image is
#              under the pointer, otherwise the next piece is checked.
###################################################
proc jigsaw-play::selectPiece { c x y } {
    variable Play
 
    set c

    # Make sure we allow for scrolling
    set x [$c canvasx $x]
    set y [$c canvasy $y]

    set retId ""
 
    # Get list of ids of all object below x,y
    set idList [$c find overlapping $x $y $x $y]

    # Reverse order of objects as we want
    # the objects in reverse stacking order
    # i.e. top object first
    # Only include objects that are pieces
 
    set pieceList {}
    foreach id $idList {
        if {[info exists Play(idToImg,$id)]} {
            set pieceList [linsert $pieceList 0 $id]
        }
    }

    # Now find the topmost piece with a non-transparent
    # pixel where the cursor is
    foreach id $pieceList {
        # Get topleft coords of image
        foreach {px py} [$c coords $id] {break}

        set ix [expr {int($x - $px)}]
        set iy [expr {int($y - $py)}]

        # The coordinates can occasionally be outside the image
        # - protect against this

        if {![catch {$Play(idToImg,$id) transparency get $ix $iy} trans]} {
            if {$trans == 0} {            
                # Pixel is opaque, select this piece to be moved
                set retId $id
                break
            }
        }
    }
    return $retId
}

###################################################
# jigsaw-play::canvasSeePoint
#
# Move the canvas scrollbars
# to see point x,y if withing scrollregion
###################################################
proc jigsaw-play::canvasSeePoint {c x y} {
    # This is the scroll area
    foreach {x1 y1 x2 y2} [$c cget -scrollregion] {break}

    # x and y scrollbars
    foreach {sx1 sx2} [$c xview] {break}
    foreach {sy1 sy2} [$c yview] {break}

    # This is the fractional position along the scrollbars
    # that would show the points
    set fx [expr {($x - $x1) / ($x2 - $x1)}]
    set fy [expr {($y - $y1) / ($y2 - $y1)}]

    # Do x scrollbar
    if { $sx1 == 0 && $sx2 == 1} {
        # Do nothing as there is no scrollbar
    } elseif {$fx <= 0.0} {
        $c xview moveto 0.0
    } elseif {$fx >= 1.0} {
        $c xview moveto [expr {1.0 - ($sx2 - $sx1)}]
    } elseif {$fx < $sx1} {
        $c xview moveto $fx
    } elseif {$fx > $sx2} {
        $c xview moveto [expr {$fx - ($sx2 - $sx1)}]
    }
 
    # Do y scrollbar
 
    if { $sy1 == 0 && $sy2 == 1} {
        # Do nothing as there is no scrollbar
    } elseif {$fy <= 0.0} {
        $c yview moveto 0.0
    } elseif {$fy >= 1.0} {
        $c yview moveto [expr {1.0 - ($sy2 - $sy1)}]
    } elseif {$fy < $sy1} {
        $c yview moveto $fy
    } elseif {$fy > $sy2} {
        $c yview moveto [expr {$fy - ($sy2 - $sy1)}]
    }
}

###################################################
# jigsaw-play::itemStartDrag
#
# Called when a user selects a piece
###################################################
proc jigsaw-play::itemStartDrag { c x y } {
    variable idToJointag
    variable lastX
    variable lastY

    set lastX [$c canvasx $x]
    set lastY [$c canvasy $y]
 
    if {[set cid [jigsaw-play::selectPiece $c $x $y]] ne ""} {
        # Add moveTag to all ids to which
        # the selected piece is connected

        if {[info exists idToJointag($cid)]} {
            $c addtag moveTag withtag $idToJointag($cid)
        } else {
            $c addtag moveTag withtag $cid
        }
        $c raise moveTag
    }
}

proc jigsaw-play::deleteMark { c } {
    variable info

    if { [info exists info(markRect)] } {
        $c delete $info(markRect)
        unset info(markRect)
    }
}

proc jigsaw-play::showMark {} {
    variable info

    set info(showMarkRect) 1
}

proc jigsaw-play::hideMark {} {
    variable info

    set info(showMarkRect) 0
}

###################################################
# jigsaw-play::itemDrag
#
# Description: Called when a user moves the mouse with a piece
#              selected.  Moves the piece.
###################################################
proc jigsaw-play::itemDrag { c x y } {
    variable lastX
    variable lastY
    variable info
    variable Play
 
    set x [$c canvasx $x]
    set y [$c canvasy $y]
 
    $c move moveTag [expr {$x-$lastX}] [expr {$y-$lastY}]
    set lastX $x
    set lastY $y

    canvasSeePoint $c $x $y

    if { $info(showMarkRect) } {
        set moveIdList [$c find withtag moveTag]
        foreach cid $moveIdList {
            foreach {x1 y1 x2 y2} [$c bbox $cid] {break}
            set overlapList [$c find overlapping $x1 $y1 $x2 $y2]

            foreach oid $info(overlaps:$cid) {
                # See if we are overlapping the desired piece
                if {[lsearch $overlapList $oid] != -1} {
                    # See if it is in the right position to join
                    foreach {dx dy} $info(relPos:$cid,$oid) {break}

                    foreach {cx cy} [$c coords $cid] {break}
                    foreach {ox oy} [$c coords $oid] {break}

                    set movex [expr {$dx - ($cx - $ox)}]
                    set movey [expr {$dy - ($cy - $oy)}]
                    if {[expr {abs($movex)}] < 10 && [expr {abs($movey)}] < 10} {
                        deleteMark $c
                        set info(markRect) [$c create rectangle $x1 $y1 $x2 $y2 -outline green -width 3]
                    } else {
                        deleteMark $c
                    }
                }
            }
        }
    }
}

###################################################
# jigsaw-play::getNewJointag
#
# Description: Generate a unique tag for joining pieces
###################################################
proc jigsaw-play::getNewJointag {} {
    variable joinNo
 
    incr joinNo
    return "join-$joinNo"
}

###################################################
# jigsaw-play::showProgress
#
# Description: Show progress of puzzle finished
###################################################
proc jigsaw-play::showProgress { } {
    variable Play
    variable Widget
    variable joinNo
 
    set msg [format "%d / %d" [expr { $joinNo + 1}] $Play(numPieces)]
    $Widget(progressLabel) configure -text $msg

    set n [expr {int((100*$joinNo) / ($Play(numPieces)-1))}]
    # Map 0..100 to a red-yellow-green sequence
    set n     [expr {$n < 0? 0: $n > 100? 100: $n}]
    set red   [expr {$n > 75? 60 - ($n * 15 / 25) : 15}]
    set green [expr {$n < 50? $n * 15 / 50 : 15}]
    
    $Widget(progressLabel) configure -bg [format "#%01x%01x0" $red $green]
}

###################################################
# jigsaw-play::itemDrop
#
# Description: Called when user stop moving a puzzle piece
#              Detects if the piece needs to join another piece
###################################################
proc jigsaw-play::itemDrop { c x y } {
    set moveIdList [$c find withtag moveTag]
 
    deleteMark $c
    foreach cid $moveIdList {
        set idOrTag [jigsaw-play::joinPiece $c $cid]
        $c raise $idOrTag
    }

    $c dtag moveTag

    # Calculate percentage completion
    jigsaw-play::showProgress
 
    # If the piece has been dropped outside of the normal view,
    # we want to adjust for this.
    # If it is to the right or bottom, then just update the scroll region.
    # If it is to the left or top, move everything down and right and update
    # the scroll region.
    
    # Setup scrollregion to include all the polys
    jigsaw-main::updateCanvasScrollregion $c
}

###################################################
# jigsaw-play::joinPiece
#
# Description: Called when user stop moving a puzzle piece
#              Detects if the piece needs to join another piece
#
# Parameters:  c   - Canvas widget path
#              cid - Id of piece to test for joining
#
# Returns:     canvas id of piece just dropped or
#              tag of pieces joined to piece just dropped
###################################################
proc jigsaw-play::joinPiece { c cid } {
    variable idToJointag
    variable info
 
    set retIdOrTag $cid
    foreach {x1 y1 x2 y2} [$c bbox $cid] {break}
 
    set overlapList [$c find overlapping $x1 $y1 $x2 $y2]
 
    foreach oid $info(overlaps:$cid) {
        # See if we are overlapping the desired piece
        if {[lsearch $overlapList $oid] != -1} {
            # See if it is in the right position to join
            foreach {dx dy} $info(relPos:$cid,$oid) {break}

            foreach {cx cy} [$c coords $cid] {break}
            foreach {ox oy} [$c coords $oid] {break}

            set movex [expr {$dx - ($cx - $ox)}]
            set movey [expr {$dy - ($cy - $oy)}]

            if {[expr {abs($movex)}] < 10 && \
                        [expr {abs($movey)}] < 10} {
                # Remove from list of pieces that still
                # need to overlap
                set info(overlaps:$cid) [jigsaw-main::lremove $info(overlaps:$cid) $oid]
                set info(overlaps:$oid) [jigsaw-main::lremove $info(overlaps:$oid) $cid]
                # Is this already joined
                # e.g. when 2 pieces, join 2 pieces
                if {[lsearch [$c find withtag moveTag] $oid] != -1} {
                    continue
                }

                # Move next to piece
                $c move moveTag $movex $movey

                # Generate a new tag for joined-up pieces
                set newJoinTag [jigsaw-play::getNewJointag]

                foreach tid [list $cid $oid] {
                    if {[info exists idToJointag($tid)]} {
                        $c addtag $newJoinTag withtag $idToJointag($tid)
                        $c dtag $idToJointag($tid)
                    } else {
                        $c addtag $newJoinTag withtag $tid
                    }
                }

                foreach tid [$c find withtag $newJoinTag] {
                    set idToJointag($tid) $newJoinTag
                }

                # Needed if joining multiple pieces
                $c addtag moveTag withtag $newJoinTag
                set retIdOrTag $newJoinTag
            }
        }
    }
    return $retIdOrTag
}

###################################################
# jigsaw-play::setupPieceBindings
#
# Description: Defines bindings to act on a puzzle piece
###################################################
proc jigsaw-play::setupPieceBindings {c} {
    variable Play
 
    $c bind piece <Button-1>        [list jigsaw-play::itemStartDrag $c %x %y]
    $c bind piece <B1-Motion>       [list jigsaw-play::itemDrag $c %x %y]
    $c bind piece <ButtonRelease-1> [list jigsaw-play::itemDrop $c %x %y]
}


###################################################
# jigsaw-play::spreadPieces
#
# Description: Spread the pieces around the canvas
###################################################
proc jigsaw-play::spreadPieces {c} {
    variable Play

    set cw [winfo width  $c]
    set ch [winfo height $c]
    set iw [image width  $Play(trans)]
    set ih [image height $Play(trans)]
 
    foreach id [$c find withtag piece] {
        foreach {x1 y1 x2 y2} [$c bbox $id] {break}

        set w [expr {$x2-$x1}]
        set h [expr {$y2-$y1}]

        set x [expr {int(($cw-$w) * rand())}]
        set y [expr {int(($ch-$h) * rand())}]
        # Do not place pieces in the area of the background image,
        # if canvas size is large enough.
        if { $cw > $iw && $ch > $ih } {
            set count 0
            while { $x < $iw && $y < $ih && $count < 10 } {
                set x [expr {int(($cw-$w) * rand())}]
                set y [expr {int(($ch-$h) * rand())}]
                incr count
            }
        }
        $c coords $id $x $y
    }
 
    # Setup scrollregion to include all the pieces
    jigsaw-main::updateCanvasScrollregion $c
}

###################################################
# jigsaw-play::storeRelPos
#
# Description: Stores the relative position between
#              adjoining pieces that is needed for
#              pieces to join
###################################################
proc jigsaw-play::storeRelPos {id1 id2} {
    variable Play
    variable info

    lappend info(overlaps:$id1) $id2
 
    foreach {x1 y1} $Play(idToPos,$id1) {break}
    foreach {x2 y2} $Play(idToPos,$id2) {break}
 
    set diffx [expr {$x1 - $x2}]
    set diffy [expr {$y1 - $y2}]
 
    set info(relPos:$id1,$id2) [list $diffx $diffy]
}

###################################################
# jigsaw-play::calcOverlaps
#
# Description: Calculate the position of each piece
#              relative to the adjoining pieces.
#              This is then used to determine if pieces
#              should connect together.
#
# Parameters: 
###################################################
proc jigsaw-play::calcOverlaps {} {
    variable Play
    variable info

    set numx $Play(num,x)
    set numy $Play(num,y)

    for {set nx 0} {$nx < $numx} {incr nx} {
        for {set ny 0} {$ny < $numy} {incr ny} {
            set img $Play($nx,$ny:image)
            set ourId $Play(imgToId,$img)

            set info(overlaps:$ourId) [list]

            # Add piece to the left
            if { $nx != 0 } {
                jigsaw-play::storeRelPos $ourId $Play([expr {$nx-1}],$ny:id)
            }
            # Add piece to the right
            if { $nx != [expr {$numx-1}] } {
                jigsaw-play::storeRelPos $ourId $Play([expr {$nx+1}],$ny:id)
            }
            # Add piece to the top
            if { $ny != 0 } {
                jigsaw-play::storeRelPos $ourId $Play($nx,[expr {$ny-1}]:id)
            }
            # Add piece to the bottom
            if { $ny != [expr {$numy-1}] } {
                jigsaw-play::storeRelPos $ourId $Play($nx,[expr {$ny+1}]:id)
            }
        }
    }
}

###################################################
# jigsaw-play::getXYtoId
#
# Description: Goes through all the puzzle pieces
#              linking their X,Y to the canvas Id
#
# Parameters:  c - Path of canvas widget
###################################################
proc jigsaw-play::getXYtoId {c} {
    variable Play

    for {set nx 0} {$nx < $Play(num,x)} {incr nx} {
        for {set ny 0} {$ny < $Play(num,y)} {incr ny} {
            set img $Play($nx,$ny:image)
            set id  $Play(imgToId,$img)
            set Play($nx,$ny:id) $id
            set Play($id:nx,ny) "$nx,$ny"
            set Play(idToPos,$id) $Play($nx,$ny:pos)
        }
    }
}

###################################################
# jigsaw-play::getIdToImgLinks
#
# Description: Goes through all the canvas Ids and
#              records the image used by each id
#              (of type image)
#
# Parameters:  c - Path of canvas widget
###################################################
proc jigsaw-play::getIdToImgLinks {c} {
    variable Play

    foreach id [$c find withtag !transp] {
        if {[$c type $id] eq "image"} {
            set img [$c itemcget $id -image]
            set Play(idToImg,$id) $img
            set Play(imgToId,$img) $id
        }
    }
}

###################################################
# jigsaw-play::countPieces
#
# Description: Counts the number of pieces
#              Results stored in Play(numPieces)
#
# Parameters: 
###################################################
proc jigsaw-play::countPieces {} {
    variable Play
    
    set Play(numPieces) [llength [array names Play "*:image"]]
    set Play(num,x)     [llength [array names Play "*,0:image"]]
    set Play(num,y)     [llength [array names Play "0,*:image"]]
}

###################################################
# jigsaw-play::resetJigsaw
#
# Description: Resets variables such that the
#              jigsaw can be restarted
#
# Parameters:  c - Path of canvas widget
###################################################
proc jigsaw-play::resetJigsaw {c} {
    variable idToJointag
    variable joinNo

    set joinNo 0

    foreach entry [array names idToJointag] {
        $c dtag $idToJointag($entry)
        unset idToJointag($entry)
    }
}

###################################################
# jigsaw-play::closeJigsaw
#
# Description: Deletes everything connected with current
#              jigsaw:
#                1) Delete everything from canvas
#                2) Delete images from memory
#                3) Unset variables
#
# Parameters: c - Path of canvas widget
###################################################
proc jigsaw-play::closeJigsaw {c} {
    variable Play
    variable Widget

    jigsaw-play::resetJigsaw $c

    $c delete all

    foreach entry [array names Play "idToImg,*"] {
        catch {image delete $Play($entry)}
    }
    catch {image delete $Play(image)}
    catch {image delete $Play(trans)}
    
    array unset Play
}

###################################################
# jigsaw-play::storeWidgetPaths
#
# Description: Store path of widgets needed for Jigsaw playing
#
# Parameters:  wPathList - List of widget name and paths
###################################################
proc jigsaw-play::storeWidgetPaths {wPathList} {
    variable Widget
 
    array set Widget $wPathList
}

proc jigsaw-play::createBackgroundImage { filePath transparency } {
    variable Play

    if { [info exists Play(trans)] } {
        catch { image delete $Play(trans) }
    }
    set Play(trans) [image create photo -file $filePath -format "PNG -alpha $transparency"]
    set Play(filePath) $filePath
    set Play(transparency) $transparency
}

proc jigsaw-play::showBackgroundImage { { c "" } } {
    variable Play

    if { $c eq "" } {
        set c [jigsaw-main::getCanvas]
    }
    $c create image 0 0 -anchor nw -image $Play(trans) -tags [list transp]
}

proc jigsaw-play::setBackgroundColor { color } {
    variable Play

    set Play(backgroundColor) $color
}

###################################################
# jigsaw-play::setBackground
#
# Description: Start to solve the jigsaw
#
# Parameters:  c  - Path of canvas widget
###################################################
proc jigsaw-play::setBackground {c} {
    variable Play

    if { ! [info exists Play(backgroundColor)] || "$Play(backgroundColor)" eq ""} {
        set Play(backgroundColor) white
    }

    $c configure -bg $Play(backgroundColor)
    bind $c <Configure> ""       
}

###################################################
# jigsaw-play::restartJigsaw
#
# Description: Restart the jigsaw
#
# Parameters:  c  - Path of canvas widget
###################################################
proc jigsaw-play::restartJigsaw {c} {
    # Remove join information
    jigsaw-play::resetJigsaw $c
    jigsaw-play::showProgress
    
    # Spread pieces
    jigsaw-play::calcOverlaps
    jigsaw-play::spreadPieces $c
    jigsaw-main::randomizeStackOrder $c
    $c raise piece transp
}

proc jigsaw-play::readBackgroundImage { c { transparency "" } } {
    variable Play

    set clip 0
    if { $transparency ne "" } {
        set Play(transparency) [expr { $Play(transparency) + 0.01 * $transparency}]
        if { $Play(transparency) >= 1.0 } {
            set Play(transparency) 1.0
            set clip 1
        }
        if { $Play(transparency) <= 0.0 } {
            set Play(transparency) 0.0
            set clip -1
        }
    }
    createBackgroundImage $Play(filePath) $Play(transparency)
    $c delete transp
    showBackgroundImage $c
    $c raise piece transp
    return $clip
}

###################################################
# jigsaw-play::startJigsaw
#
# Description: Start to solve the jigsaw
#
# Parameters:  c  - Path of canvas widget
#              sb - Path of Status bar widget
###################################################
proc jigsaw-play::startJigsaw {dataName c} {
    variable Play
    variable Widget

    # Clean up before we start
    jigsaw-play::closeJigsaw $c
    
    upvar $dataName data
    array set Play [array get data]
    
    createBackgroundImage $Play(filePath) $Play(transparency)
    showBackgroundImage $c

    jigsaw-play::countPieces
    jigsaw-play::drawPieces $c
    jigsaw-play::setBackground $c
    jigsaw-play::spreadPieces $c
    jigsaw-main::randomizeStackOrder $c
    jigsaw-play::resetJigsaw $c
    jigsaw-play::getIdToImgLinks $c
    jigsaw-play::getXYtoId $c
    jigsaw-play::calcOverlaps
    jigsaw-play::setupPieceBindings $c

    $c raise piece transp
}

###################################################
# jigsaw-play::save
#
# Description: Save data to file
#
# Parameters:  c - canvas widget path
###################################################
proc jigsaw-play::save {c filePath} {
    jigsaw-play::getPieceState $c

    set top [winfo toplevel $c]

    if {[catch {jigsaw-play::saveCfg $top $filePath} message]} {
        return -code error $message
    }
}
    
###################################################
# jigsaw-play::open
#
# Description: Save data to file
#
# Parameters:  c - canvas widget path
#              filePath
###################################################
proc jigsaw-play::open {c filePath} {
    variable Play

    jigsaw-play::closeJigsaw $c

    set top [winfo toplevel $c]

    if {[catch {jigsaw-play::readCfg $top $filePath} message]} {
       return -code error $message
    }

    showBackgroundImage $c

    jigsaw-play::drawPieces $c
    jigsaw-play::setBackground $c
    jigsaw-play::getIdToImgLinks $c
    jigsaw-play::setupPieceBindings $c
    jigsaw-play::getXYtoId $c
    jigsaw-play::calcOverlaps

    # Jigsaw may already be in progress
    jigsaw-play::checkForJoinedPieces $c
    jigsaw-play::showProgress

    # Setup scrollregion to include all the pieces
    jigsaw-main::updateCanvasScrollregion $c

    $c raise piece transp
}

###################################################
# jigsaw-play::getPieceState
#
# Description: Used to get state of pieces from the canvas
#              ready for saving into a file.  Gets:
#              1) Current position of each piece
#              2) Stacking order of each piece
#
# Parameters:  c - canvas window
###################################################
proc jigsaw-play::getPieceState {c} {
    variable Play
 
    set Play(stackOrder) [list]

    foreach id [$c find withtag !transp] {
        if {[$c type $id] eq "image"} {
            set nxy $Play($id:nx,ny)
            lappend Play(stackOrder) $nxy
            set Play($nxy:cpos) [$c coords $id]
        }
    }   
}

###################################################
# jigsaw-play::drawPieces
#
# Description: Used to create pieces on the canvas that have
#              been loaded from file or created by the wizard
#
# Note:        cpos is only available when loaded from file
#              (it is the current position when puzzle was saved)
#
# Parameters:  c - canvas window
###################################################
proc jigsaw-play::drawPieces {c} {
    variable Play

    foreach nxy $Play(stackOrder) {
        if {[info exists Play($nxy:cpos)]} {
            foreach {x y} $Play($nxy:cpos) {break}
        } else {
            foreach {x y} $Play($nxy:pos) {break}
        }
        $c create image $x $y -anchor nw -image $Play($nxy:image) -tags [list piece]
    }
}

###################################################
# jigsaw-play::checkForJoinedPieces
#
# Description: Used to join pieces that should be joined when
#              a saved puzzle is re-loaded
#
# Parameters:  c - canvas window
###################################################
proc jigsaw-play::checkForJoinedPieces {c} {
    variable idToJointag

    foreach cid [$c find withtag piece] {
        if {[info exists idToJointag($cid)]} {
            $c addtag moveTag withtag $idToJointag($cid)
        } else {
            $c addtag moveTag withtag $cid
        } 
        $c addtag moveTag withtag $cid
        jigsaw-play::joinPiece $c $cid
        $c dtag moveTag
    }
}

###################################################
# jigsaw-play::getTransInfo
#
# Description: Get piece shape data into a form
#              which allows the piece to be quickly
#              recreated from the master image
#
# Parameters:  image
###################################################
proc jigsaw-play::getTransInfo {image} {
    set clist [list]

    set w [image width $image]
    set h [image height $image]

    for {set y 0} {$y < $h} {incr y} {
        set p 1
        for {set x 0} {$x < $w} {incr x} {
            set n [$image transparency get $x $y]
            if { $n != $p } {
                if { $n == 0 } {
                    lappend clist $x $y
                } else {
                    lappend clist [expr {$x+1}] [expr {$y+1}]
                }
                set p $n
            }
        }
        # Might not yet have found a transparent pixel on the
        # right of the image
        if { $p == 0 } {
            lappend clist [expr {$x+1}] [expr {$y+1}]
        }
    }
    return $clist
}

###################################################
# jigsaw-play::saveCfg
#
# Description: Save data to file
#
# Parameters:  filePath - File to which to save
###################################################
proc jigsaw-play::saveCfg {top filePath} {
    variable Play
    set retCode ok

    # Create config file
    set cfg [ini::open $filePath "w"]

    set w [winfo width  $top]
    set h [winfo height $top]

    ini::set $cfg "General" numPiecesX $Play(num,x)
    ini::set $cfg "General" numPiecesY $Play(num,y)
    ini::set $cfg "General" windowWidth  $w
    ini::set $cfg "General" windowHeight $h
    ini::set $cfg "General" backgroundColor $Play(backgroundColor)
    ini::set $cfg "General" stackOrder $Play(stackOrder)

    set imgFile [format "%s.png" [file tail [file root $filePath]]]
    set imgPath [file join [file dirname $filePath] $imgFile]

    $Play(image) write $imgPath -format PNG

    ini::set $cfg "General" imageFile $imgFile
    ini::set $cfg "General" transparency [format "%.1f" $Play(transparency)]

    # Now do each piece
    for {set x 0} {$x < $Play(num,x)} {incr x} {
        for {set y 0} {$y < $Play(num,y)} {incr y} {
            if {![info exists Play($x,$y:shapeData)]} {
                set Play($x,$y:shapeData) [getTransInfo $Play($x,$y:image)]
            }

            ini::set $cfg "piece-$x,$y" solPos $Play($x,$y:pos)
            ini::set $cfg "piece-$x,$y" curPos $Play($x,$y:cpos)
            ini::set $cfg "piece-$x,$y" width  [image width $Play($x,$y:image)]
            ini::set $cfg "piece-$x,$y" height [image height $Play($x,$y:image)]
            ini::set $cfg "piece-$x,$y" data   $Play($x,$y:shapeData)
        }
    }

    ini::commit $cfg
    ini::close $cfg
    
    return -code $retCode
}

###################################################
# jigsaw-play::createPieceImage
#
# Description: Create a piece of the jigsaw
#
# Parameters:  src   - source image
#              x     - offset from left of src image
#              y     - offset from top of src image
#              w     - width of image to create
#              h     - height of image to create
#              data  - data used to create image
###################################################
proc jigsaw-play::createPieceImage {src x y w h data} {
    set new [image create photo -width $w -height $h]
    $new blank

    foreach {x1 y1 x2 y2} $data {
        set nx $x1
        set ny $y1

        incr x1 $x
        incr x2 $x
        incr y1 $y
        incr y2 $y

        $new copy $src -from $x1 $y1 $x2 $y2 -to $nx $ny
    }
    return $new
}

###################################################
# jigsaw-play::readCfg
#
# Description: Read config file
#
# Parameters:  filePath - File to read
###################################################
proc jigsaw-play::readCfg {top filePath} {
    variable Play

    # Read config file
    if { [catch {ini::open $filePath} cfg] } {
        error "Cannot read configuration file $filePath"
    }

    set Play(num,x) [ini::value $cfg "General" numPiecesX]
    set Play(num,y) [ini::value $cfg "General" numPiecesY]
    set Play(numPieces) [expr {$Play(num,x) * $Play(num,y)}]
    set Play(backgroundColor) [ini::value $cfg "General" backgroundColor]
    set Play(stackOrder) [ini::value $cfg "General" stackOrder]

    set w [ini::value $cfg "General" windowWidth]
    set h [ini::value $cfg "General" windowHeight]
    wm geometry $top [format "%dx%d" $w $h]

    # Read jigsaw image
    set imageFile [ini::value $cfg "General" imageFile]
    set Play(transparency) [ini::value $cfg "General" transparency]

    set imagePath [file join [file dirname $filePath] $imageFile]
    set Play(image) [image create photo -file $imagePath]
    createBackgroundImage $imagePath $Play(transparency)

    # Now do each piece
    for {set x 0} {$x < $Play(num,x)} {incr x} {
        for {set y 0} {$y < $Play(num,y)} {incr y} {
            # Read positions
            set Play($x,$y:pos)  [ini::value $cfg "piece-$x,$y" solPos]
            set Play($x,$y:cpos) [ini::value $cfg "piece-$x,$y" curPos]
            
            # Now create the piece image
            foreach {xoff yoff} $Play($x,$y:pos) {break}
            set w [ini::value $cfg "piece-$x,$y" width]
            set h [ini::value $cfg "piece-$x,$y" height]
            set Play($x,$y:shapeData) [ini::value $cfg "piece-$x,$y" data]
            
            set Play($x,$y:image) [createPieceImage $Play(image) $xoff $yoff $w $h $Play($x,$y:shapeData)]
        }
    }
    ini::close $cfg
}
