dialect "objectdraw"
import "Matrix" as mx

type Matrix<T> = mx.Matrix<T>
type MatrixFactory = {
  size<T>(rows: Number, cols: Number) defaultValue (default:T) -> Matrix<T>
}

def matrix: MatrixFactory = mx.matrix

// Class to create and display a magic square
class magicSquare.at(point: Point) size(magicSize: Number) 
          on (canvas: DrawingCanvas) -> GraceObject {
  // size of cell inside each slot of magic square
  def digitWidth: Number = 10
  def gap: Number = 8

  // buffer space between lines and #s in magic square
  def xOffset: Number = gap/2
  def yOffset: Number = 2*gap

  // Matrix holding entries in magic square and its size
  def magicArray: Matrix<Number> = 
    matrix.size<Number>(magicSize,magicSize) defaultValue(0)
      
  // Set the indices for the middle of the bottom row
  var currRow: Number := magicSize
  var currCol: Number := (magicSize + 1) / 2

  // Fill each cell with consecutive numbers following
  // rules for building magic square of odd size
  for (1 .. (magicSize * magicSize)) do {nextInt: Number ->
    magicArray.at(currRow,currCol) put (nextInt)

    // Find the next cell, wrapping around if necessary
    def nextRow: Number = (currRow % magicSize) + 1
    def nextCol: Number = (currCol % magicSize) + 1
    
    // If the cell is empty, remember those indices for the next assignment
    if (magicArray.at(nextRow,nextCol) == 0) then {
      currRow := nextRow
      currCol := nextCol
    } else {
      // The cell was full - Use the cell above the previous one
      currRow := if (currRow == 1) then {magicSize} else {currRow - 1}
    }
  }

  // code below draws the square on the canvas
    
  // number of digits in biggest entry
  def numDigits: Number = (magicSize*magicSize).asString.size
    
  // Size of each cell for a number
  def cellSize: Number = numDigits*digitWidth + gap
  
  // length of one side of magic square
  def totalSize: Number = cellSize * magicSize

  // Staring coordinates of square
  def cornerX: Number = point.x
  def cornerY: Number = point.y-cellSize

  // draw the vertical lines
  for (0 .. magicSize) do {col: Number ->
    line.from((col*cellSize + cornerX) @ cornerY)
          to((col*cellSize + cornerX) @ (cornerY + totalSize)) on (canvas)
  }

  // draw the horizontal lines
  for (0 .. magicSize) do {row: Number ->
    line.from(cornerX @ (row*cellSize + cornerY))
          to((cornerX + totalSize) @ (row*cellSize + cornerY)) on (canvas)
  }

  // fill in the data values on the screen from magicArray
  for (1 .. magicSize) do {row: Number ->
    for (1 .. magicSize) do {col: Number ->
      text.at((cornerX + (col-1) * cellSize + xOffset) @ 
              (cornerY + (row-1) * cellSize + yOffset))
           with(magicArray.at(row,col).asString) on (canvas)
    }
  }

}

// main program to draw magic squares
def buildMagicSquares: GraphicApplication = object {
  inherits graphicApplication.size(600,600)
  
  // instructions to user
  def instructions: Text = 
    text.at(100@100) with("Click to make a new magic square") on (canvas)
  
  // number field where user enters odd size for magic square
  def sizeEntry: NumberField = numberField.labeled("Enter odd size")
  append(sizeEntry)
  
  // Draw magic square where user clicks
  method onMousePress(pt:Point) -> Done {
    instructions.visible := false
    def size: Number = sizeEntry.number
    if ((size % 2) == 1) then { // only draw square if size is odd
      magicSquare.at(pt)size(size)on(canvas)
    }
  }

  startGraphics
}