//
// Copyright (c) 2008, Brian Frank and Andy Frank
// Licensed under the Academic Free License version 3.0
//
// History:
//   16 Jun 08  Brian Frank  Creation
//

using gfx

**
** GridPane is a container which lays out its children in a grid
** from left to right with a new row started based on 'numCols'.
**
** TODO: this API going to change, most likely there will be a
**   switch to per col/per row configuration
**
@Js
@Serializable { collection = true }
class GridPane : Pane
{

  **
  ** Number of columns before wrapping to a new row.
  ** Default is 1.
  **
  Int numCols := 1

  **
  ** Horizontal gap is number of pixels between left and
  ** right edges of adjacent cells.  Default is 4.
  **
  Int hgap := 4

  **
  ** Vertical gap is number of pixels between bottom and
  ** top edges of adjacent cells.  Default is 4.
  **
  Int vgap := 4

  **
  ** Horizontal alignment of the individual cells.  Defaults to left.
  **
  Halign halignCells := Halign.left

  **
  ** Vertical alignment of the individual cells.  Defaults to center.
  **
  Valign valignCells := Valign.center

  **
  ** Horizontal alignment of the entire pane - this defines where
  ** the extra horizontal space beyond the preferred width goes.
  ** This field only makes sense when `expandCol` is null.
  ** Defaults to left.
  **
  Halign halignPane := Halign.left

  **
  ** Vertial alignment of the entire pane - this defines where
  ** the extra vertial space beyond the preferred height goes.
  ** This field only makes sense when `expandRow` is null.
  ** Defaults to top.
  **
  Valign valignPane := Valign.top

  **
  ** If non-null, then this is a zero based row number to assign
  ** any extra height available beyond the preferred height.  A
  ** negative number indexes from the last row.  Default is null.
  **
  Int? expandRow := null

  **
  ** If non-null, then this is a zero based column number to assign
  ** any extra width available beyond the preferred width.  A
  ** negative number indexes from the last column.  Default is null.
  **
  Int? expandCol := null

  **
  ** If true, then all columns are given a uniform width which
  ** is computed from the widest column.  If false then columns
  ** might be laid out with variable widths based on the width
  ** of the cells.  Default is false.
  **
  Bool uniformCols := false

  **
  ** If true, then all rows are given a uniform height which
  ** is computed from the highest row.  If false then rows
  ** might be laid out with variable heights based on the highest
  ** of the cells.  Default is false.
  **
  Bool uniformRows:= false

  override Size prefSize(Hints hints := Hints.defVal)
  {
    return GridPaneSizes(this, children).prefPane
  }

  override Void onLayout()
  {
    // compute max width of each column, and max height of each row
    kids := children
    sizes := GridPaneSizes(this, kids)
    psize := this.size
    actualw := psize.w; actualh := psize.h
    prefw := sizes.prefPane.w; prefh := sizes.prefPane.h

    // compute expand row/col
    expandRow := this.expandRow
    expandCol := this.expandCol
    if (expandRow != null && expandRow < 0) expandRow  = sizes.numRows+expandRow
    if (expandCol != null && expandCol < 0) expandCol  = numCols+expandCol
    expandRowh := 0.max(actualh-prefh)
    expandColw := 0.max(actualw-prefw)

    // compute left hand corner of grid pane
    startx := 0; starty := 0
    if (expandCol == null)
      switch (halignPane)
      {
        case Halign.center: startx = expandColw/2
        case Halign.right:  startx = expandColw
      }
    if (expandRow == null)
      switch (valignPane)
      {
        case Valign.center: starty = expandRowh/2
        case Valign.bottom: starty = expandRowh
      }

    // layout children
    col := 0; row := 0
    x := startx; y := starty
    kids.each |Widget kid, Int i|
    {
      pref := sizes.prefs[i]
      kx := x
      ky := y
      kw := pref.w
      kh := pref.h
      rowh := sizes.rowh[row]
      colw := sizes.colw[col]

      if (row == expandRow) rowh += expandRowh
      if (col == expandCol) colw += expandColw

      switch (halignCells)
      {
        case Halign.center: kx = x + (colw-kw)/2
        case Halign.right:  kx = x + (colw-kw)
        case Halign.fill:   kw = colw
      }

      switch (valignCells)
      {
        case Valign.center: ky = y + (rowh-kh)/2
        case Valign.bottom: ky = y + (rowh-kh)
        case Valign.fill:   kh = rowh
      }

      kid.pos = Point(kx,ky)
      kid.size = Size(kw,kh)

      if (++col >= numCols) { x = startx; y += rowh + vgap; col = 0; row++ }
      else { x += colw + hgap }
    }
  }
}

@Js
internal class GridPaneSizes
{
  new make(GridPane grid, Widget[] kids)
  {
    colw.fill(0, grid.numCols)

    // short-circuit if no kids
    if (kids.isEmpty)
    {
      prefPane = Size.defVal
      return
    }

    // compute colw and rowh lists
    col := 0; row := 0
    kids.each |Widget kid|
    {
      pref := kid.visible ? kid.prefSize : Size.defVal
      prefs.add(pref)

      colw[col] = colw[col].max(pref.w)

      if (row >= rowh.size) rowh.add(pref.h)
      else rowh[row] = rowh[row].max(pref.h)

      if (++col >= grid.numCols) { col = 0; row++ }
    }

    // if uniform rows/cols
    if (grid.uniformCols) { max := colw.max; colw.size.times |Int i| { colw[i] = max } }
    if (grid.uniformRows) { max := rowh.max; rowh.size.times |Int i| { rowh[i] = max } }

    // compute prefw
    prefw := (grid.numCols - 1) * grid.hgap
    grid.numCols.times |Int c| { prefw += colw[c] }

    // compute prefh
    prefh := (numRows - 1) * grid.vgap
    numRows.times |Int r| { prefh += rowh[r] }

    // prefPane
    prefPane = Size(prefw, prefh)
  }

  Int numRows() { return rowh.size }

  Int[] colw := Int[,]     // widths of each column
  Int[] rowh := Int[,]     // heights of each row
  Size[] prefs := Size[,]  // calcualted prefSize of each widget
  Size prefPane            // pref size of whole grid
}