#
# Steps
# 1. add assert-like tests.
# 2. grid removal code.
# 3. don't allow negative grid offsets
# 4. grid x/y $w setting.
# 5. handle minwidth and minheight



set dir [file dirname [info script]]
load [file join $dir structure.so]


#
# This inserts a widget object into the grid.
# This is used as a 2-dimensional structure.
#
proc _grid.insert.widget {grid_var w c r} {
 upvar $grid_var grid

 if {[llength $grid] <= ($r + 1)} {
  #
  # The grid row doesn't exist, so insert it.
  #
  for {set i [expr {[llength $grid] - 1}]} {$i < $r} {incr i} {
   lappend grid [list]
  }
 } 

 set row [lindex $grid $r]
 
 if {[llength $row] <= ($c + 1)} {
  #
  # The grid column doesn't exist in the row, so insert it.
  #
  for {set i [expr {[llength $row] - 1}]} {$i < $c} {incr i} {
   lappend row {}
  }
 }
 
 lset row $c $w
 lset grid $r $row
}

#
# This dumps a grid's objects in a pretty format.
#
proc _grid.dump grid {
 set i 0
 foreach row $grid {
  puts "$i -> $row"
  incr i
 }
}

#
# This dumps a grid's objects with their width, height, and slot. 
# This is typically used when debugging after a grid operation.
#
proc _grid.dump.sizes grid {
 foreach row $grid {
  foreach w $row {
   if {"" eq $w} \
    continue

   puts -nonewline "$w is [$w width] x [$w height] \[[$w -slot]\]\t"
  }
  puts ""
 }
}

proc _grid.rm.list {list slot span} {
 for {set i 0} {$i < $span} {incr i} {
  set e [lsearch -exact $list [expr {$slot + $i}]]

  if {$e < 0} \
   continue

  set list [lreplace $list $e $e]
 }
 return $list
}

#
# This checks if the $arg is in the -sticky list.
# Normal usage is like [_grid.sticky? $w width]
#
proc _grid.sticky? {w arg} {
 expr {[lsearch -exact [$w -sticky] $arg] >= 0}
}

proc _grid.get.visible {grid columns_var rows_var} {
 upvar $columns_var columns
 upvar $rows_var rows

 #
 # Find the number of visible columns.
 #
 set r 0
 foreach row $grid {
  set c 0
  foreach w $row {
   if {"" eq $w} {
    incr c
    continue
   }
   for {set i 0} {$i < [$w -columnspan]} {incr i} {
    lappend columns [expr {$c + $i}]
   }
   for {set i 0} {$i < [$w -rowspan]} {incr i} {
    lappend rows [expr {$r + $i}]
   }
   incr c
  }
  incr r
 }
}

proc _grid.layout parent {
 set m [$parent _manager]

 set grid [$m grid]

 set pwidth [$parent width]
 set pheight [$parent height]

 set visiblecolumns [list]
 set visiblerows [list]

 _grid.get.visible $grid visiblecolumns visiblerows

 set totalcolumns [llength $visiblecolumns]
 set totalrows [llength $visiblerows]

 # the number of visible columns/rows
 set numcolumns [llength [lsort -unique $visiblecolumns]]
 set numrows [llength [lsort -unique $visiblerows]]

 #puts NUMCOL:$numcolumns

 # The remaining width and height:
 set remwidth $pwidth
 set remheight $pheight
 #
 # Set the initial sizes of the widgets based on the requests.
 #
 foreach row $grid {
  foreach w $row {
   if {"" eq $w} \
    continue

   if {![_grid.sticky? $w width]} {
    $w width [$w reqwidth]
    set remwidth [expr {$remwidth - [$w width]}]
    set visiblecolumns [_grid.rm.list $visiblecolumns \
      [lindex [$w -slot] 0] [$w -columnspan]]
   }

   if {![_grid.sticky? $w height]} {
    $w height [$w reqheight]
    set remheight [expr {$remheight - [$w height]}]
    set visiblerows [_grid.rm.list $visiblerows \
      [lindex [$w -slot] 1] [$w -rowspan]]
   }
  }
 }

 set numcolumns [llength [lsort -unique $visiblecolumns]]
 set numrows [llength [lsort -unique $visiblerows]] 

 #
 # Set the sizes of the remaining sticky widgets, if there are any.
 #
 foreach row $grid {
  foreach w $row {
   if {"" eq $w} \
    continue

   if {[_grid.sticky? $w width]} {
    $w width [expr {$remwidth * [$w -columnspan] / $numcolumns}]
    #set remwidth [expr {$remwidth - [$w width]}]    
    #set numcolumns [expr {$numcolumns - [$w -columnspan]}]
   }

   if {[_grid.sticky? $w height]} {
    $w height [expr {$remheight * [$w -rowspan] / $numrows}]
   }
  }
 }

 _grid.dump.sizes $grid
}

proc _grid.-sticky.callback {w arg} {
 foreach i $arg {
  if {"width" ne $i && "height" ne $i} {
   return -code error "invalid -sticky list item (should be width or height): $i"
  }
 }

 return 1
}

proc _grid.integer.callback {w arg} {
 if {![string is integer -strict $arg]} {
  return -code error "invalid argument (not an integer): $arg"
 }

 return 1
}

proc _grid.-slot.callback {w arg} {
 if {2 != [llength $arg]} {
  return -code error "invalid list length (should be 2): $arg"
 }

 lassign $arg c r

 if {![string is integer -strict $c]} {
  return -code error "invalid column for slot (not an integer): $c"
 }

 if {![string is integer -strict $r]} {
  return -code error "invalid row for slot (not an integer): $r"
 }

 return 1
}


proc _grid.relayout.trace w {
 _grid.layout [$w _parent]
}

proc grid.remove w {
 set m [[$w _parent] _manager]

 lassign [$w -slot] col row

 set grid [$m grid]

 # 
 # XXX we should possibly remove this cell in the grid
 # but we would need to examine the rest of the grid dimensions.
 # 
 set rowdata [lreplace [lindex $grid $row] $col $col {}]
 lset grid $row $rowdata
 $m grid $grid
 _grid.layout [$w _parent]
}

proc grid {w args} {
 #
 # See if the parent has a manager object already.
 # Create a new grid manager object if there isn't one already.
 #
 set p [$w _parent]
 if {"" eq [$p _manager]} {
  set m [structure]
  $m grid [list] 
  $p _manager $m
 }

 #
 # Unlock and initialize the grid data for the window.
 #
 unlock.structure.creation $w
 $w \
   -sticky [list width height] \
   -columnspan 1 \
   -rowspan 1 \
   -slot [list 0 0]

 lock.structure.creation $w

 #XXX we should relayout even if the {expand} and eval fail.
 $w {expand}$args

 # 
 # Get the grid prior to insertion from the parent.
 #
 set grid [[[$w _parent] _manager] grid]

 lassign [$w -slot] c r

 _grid.insert.widget grid $w $c $r

 #
 # Set the new grid list.
 #
 [[$w _parent] _manager] grid $grid

 _grid.layout [$w _parent]

 #
 # Create the callbacks, now that the widget members have been initialized.
 #

 structure.key.callback $w -sticky [list _grid.-sticky.callback $w]

 structure.key.callback $w -columnspan [list _grid.integer.callback $w]

 structure.key.callback $w -rowspan [list _grid.integer.callback $w]

 structure.key.callback $w -slot [list _grid.-slot.callback $w]

 foreach key [list -sticky -columnspan -rowspan -slot] {
  trace.structure.key $w $key [list _grid.relayout.trace $w]
 }


 # _grid.dump $grid
 #_grid.dump.sizes $grid
}

#
# This is the basic primitive structure that is extended by grid.
#
proc window w {
 structure $w

 $w _parent . ;#XXX hardcoded
 $w _manager "" width 0 height 0 reqwidth 0 reqheight 0 \
   minwidth 0 minheight 0

 lock.structure.creation $w

 return $w
}

proc main {} {
 #
 # Create a toplevel (XXX: not like the final code)
 #
 window .
 . _parent "" width 340 height 60


 foreach w [list .w1 .w2 .w3 .w4 .w5] {
  window $w
 }

 .w1 reqwidth 20 reqheight 40
 grid .w1 -slot [list 0 0] -sticky height
 grid .w2 -slot [list 0 1] -columnspan 3
 grid .w3 -slot [list 3 0] -rowspan 2
 grid .w4 -slot [list 5 1] -columnspan 2
 puts -----
 puts Tw5:[time {grid .w5 -slot [list 3 3]}]
}
main

fileevent stdin readable {puts [eval [gets stdin]]}
vwait _forever
