// Copyright 2000 by Robert I. Pitts <rip@cs.bu.edu>
// All rights reserved.

function CharMatrix(initialStr)
{
  // public data

  this.height = 0
  this.width = 0

  // private data

  this.M = null

  // public methods

  this.set = function (str, removeSpace)
  {
    if (arguments.length < 2)
      removeSpace = false

    var lines = str.split("\n")

    this.height = lines.length

    if (removeSpace) {
      // Remove preceding blank lines.
      while (lines[0].search(/^\s*$/) == 0)
        lines.shift()

      // Remove trailing blank lines.
      while (lines[this.height-1].search(/^\s*$/) == 0)
        this.height--
    } else {
      // Remove line caused by newline on last line.
      if (lines[this.height-1] == "")
        this.height--
    }

    this.width = 0

    this.M = new Array(this.height)

    for (var row = 0; row < this.height; row++) {
      var line = lines[row]
      if (removeSpace)
        line = lines[row].replace(/\s+$/, "")
      this.M[row] = line.split("")
      this.width = Math.max(this.width, this.M[row].length)
    }
  }

  this.toString = function ()
  {
    var str = ""
    for (var row = 0; row < this.M.length; row++) {
      if (row != 0)
        str += "\n"
      str += this.M[row].join("")
    }
    return str
  }

  this.withTags = function (tags)
  {
    var str = ""

    for (var row = 0; row < this.M.length; row++) {
      var solTag = ""    // start of line tag
      var eolTag = "\n"  // end of line tag

      for (var tagi = 0; tagi < tags.length; tagi++)
        with (tags[tagi])
          if (type == "line" && (typeof y == "undefined" || y == row)) {
            solTag = start
            eolTag = end
          }

      for (var col = 0; col < this.M[row].length; col++) {
        var startTag = ""
        var endTag = ""

        for (var tagi = 0; tagi < tags.length; tagi++)
          with (tags[tagi])
            if (type == "pos" &&
                (typeof y == "undefined" || y == row) &&
                (typeof x == "undefined" || x == col)) {
              startTag = start
              endTag = end
            }

        str += startTag + this.M[row][col] + endTag
      }

      str += eolTag
    }

    return str
  }

  this.validRows = function ()
  {
    if (this.M == null)
      return false

    var firstlen = this.M[0].length

    for (var row = 1; row < this.M.length; row++) {
      if (this.M[row].length != firstlen)
        return false
    }

    return true
  }

  this.validChars = function (charset)
  {
    if (this.M == null)
      return true

    for (var row = 0; row < this.M.length; row++) {
      for (var col = 0; col < this.M[row].length; col++) {
        if (charset.indexOf(this.M[row][col]) == -1)
          return false
      }
    }

    return true
  }

  this.hasLocationXY = function (x, y)
  {
    return y > -1 && y < this.M.length &&
           x > -1 && x < this.M[y].length
  }

  this.getCharAtXY = function (x, y)
  {
    return this.hasLocationXY(x, y) ? this.M[y][x] : ""
  }

  this.hasCharAtXY = function (x, y, charset)
  {
    if (!this.hasLocationXY(x, y))
      return false
    return charset.indexOf(this.M[y][x]) != -1
  }

  this.findFirstChar = function (charset)
  {
    if (this.M == null)
      return null

    for (var row = 0; row < this.M.length; row++) {
      for (var col = 0; col < this.M[row].length; col++) {
        if (charset.indexOf(this.M[row][col]) != -1)
          return {x: col, y: row}
      }
    }

    return null
  }

  this.countChar = function (charset)
  {
    if (this.M == null)
      return 0

    var count = 0

    for (var row = 0; row < this.M.length; row++) {
      for (var col = 0; col < this.M[row].length; col++) {
        if (charset.indexOf(this.M[row][col]) != -1)
          count++
      }
    }

    return count
  }

  this.setCharAtXY = function (x, y, val)
  {
    if (this.hasLocationXY(x, y))
      this.M[y][x] = val
  }

  this.replaceCharAtXY = function (x, y, oldset, newset)
  {
    if (!this.hasLocationXY(x, y))
      return false

    var i = oldset.indexOf(this.M[y][x])

    if (i == -1)
      return false

    this.M[y][x] = newset.substr(i, 1)

    return true
  }

  this.replaceChars = function (oldset, newset)
  {
    if (this.M == null)
      return 0

    var changed = 0

    for (var row = 0; row < this.M.length; row++) {
      for (var col = 0; col < this.M[row].length; col++) {
        if (this.replaceCharAtXY(col, row, oldset, newset))
          changed++
      }
    }

    return changed
  }

  if (arguments.length)
    this.set(initialStr)
}
