function Binfile() {
  this.bytes = []
}

Binfile.ascii_table = []

Binfile.ascii_chars =
  " !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOP" +
  "QRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~"

for (var i = 0; i < 32; i++) {
  Binfile.ascii_table[i] = "."
}
for (var i = 0; i < Binfile.ascii_chars.length; i++) {
  Binfile.ascii_table[i + 32] = Binfile.ascii_chars.charAt(i)
}
for (var i = 127; i < 256; i++) {
  Binfile.ascii_table[i] = "."
}

Binfile.prototype.toString = function(how) {
  if (how == "hex_dump") {
    return this.hex_dump()
  } else if (how == "base64_encoded") {
    return this.base64_encoded()
  } else if (how == "raw_dump") {
    var ret = new String()

    for (var i = 0; i < this.bytes.length; i++) {
      ret += this.bytes[i].toString() + " "
    }

    ret += "\n"

    return ret
  } else {
    var ret = new String()

    for (var i = 0; i < this.bytes.length; i++) {
      ret += Binfile.ascii_table[this.bytes[i]]
    }

    return ret
  }
}

Binfile.prototype.writeByte = function(b) {
  if (typeof(b) != "number" || b < 0 || b > 255 ||
      b != b.toFixed())
  {
    return alert('invalid argument to Binfile.writeByte')
  }

  this.bytes.push(b)
}

Binfile.prototype.writeShort = function(s) {
  if (typeof(s) != "number" || s < 0 || s > 65535 ||
      s != s.toFixed())
  {
    return alert('invalid argument to Binfile.writeShort')
  }

  this.writeByte(s & 0x00ff)
  this.writeByte((s & 0xff00) >> 8)
}

Binfile.prototype.writeLong = function(l) {
  if (typeof(l) != "number" || l < 0 || l > 0xffffffff ||
      l != l.toFixed())
  {
    return alert('invalid argument to Binfile.writeLong')
  }

  this.writeByte(l & 0x000000ff)
  this.writeByte((l & 0x0000ff00) >> 8)
  this.writeByte((l & 0x00ff0000) >> 16)
  this.writeByte((l & 0xff000000) >> 24)
}

Binfile.prototype.writeString = function(s) {
  for (var i = 0; i < s.length; i++) {
    this.writeByte(Binfile.ascii_chars.indexOf(s.charAt(i)) + 32)
  }
}

function hex_num(x, pad, ch) {
  if (!pad) pad = 2
  if (!ch) ch = "0"

  var str = x.toString(16)

  while (str.length < pad)
    str = ch + str

  return str
}

Binfile.prototype.hex_dump = function() {
  var ret = hex_num(0, 4, " ") + ": "

  var as_str = ""
  for (var i = 0; i < this.bytes.length; i++) {

    as_str += Binfile.ascii_table[this.bytes[i]]

    ret += hex_num(this.bytes[i]) + " "

    if ((i + 1) % 16 == 0) {
      ret += "| " + as_str + "\n"
      ret += hex_num(i + 1, 4, " ") + ": "
      as_str = ""
    } else if ((i + 1) % 8 == 0) {
      ret += " "
    }
  }

  if (this.bytes.length % 16 != 0) {
    var left = 16 - this.bytes.length % 16

    for (var i = 0; i < left; i++) {
      ret += "   "
      if (i == 8) ret += " "
    }

    ret += "| " + as_str + "\n"
  }

  return ret
}

Binfile.base64_chars =
  "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"

Binfile.prototype.base64_encoded = function() {
  var ret = new String()

  var i = 0
  while (i < this.bytes.length) {
    var c1 = this.bytes[i++]
    var c2 = this.bytes[i++]
      if (!c2) c2 = 0
    var c3 = this.bytes[i++]
      if (!c3) c3 = 0

    var i1 = c1 >> 2
    var i2 = ((c1 & 3) << 4) | (c2 >> 4)
    var i3 = ((c2 & 15) << 2) | (c3 >> 6)
    var i4 = c3 & 63

    if (i <= this.bytes.length) {
      ret += Binfile.base64_chars.charAt(i1) +
             Binfile.base64_chars.charAt(i2) +
             Binfile.base64_chars.charAt(i3) +
             Binfile.base64_chars.charAt(i4)
    } else if (i == this.bytes.length + 1) {
      ret += Binfile.base64_chars.charAt(i1) +
             Binfile.base64_chars.charAt(i2) +
             Binfile.base64_chars.charAt(i3) + "="
    } else if (i == this.bytes.length + 2) {
      ret += Binfile.base64_chars.charAt(i1) +
             Binfile.base64_chars.charAt(i2) + "=="
    }
  }

  return ret
}

Binfile.prototype.as_data_uri = function(mime_type) {
  return "data:" + mime_type + ";base64," + this.base64_encoded()
}

Binfile.prototype.writeIcon = function(icon) {
  // header
  this.writeShort(0)  // 00:reserved (=0)
  this.writeShort(1)  // 02:type (=1)
  this.writeShort(1)  // 04:number of icons in file

  // icon entries (just one in this case
  this.writeByte(16)  // 06:width
  this.writeByte(16)  // 07:height
  this.writeByte(16)  // 08:number of colors
  this.writeByte(0)   // 09:reserved (=0)
  this.writeShort(1)  // 0a:planes (=1)
  this.writeShort(4)  // 0c:bits per pixel
  this.writeLong(296)  // 0e:size of icon in bytes
  this.writeLong(0x16) // 12:offset in file for icon header

  // icon data
  // icon header
  this.writeLong(40)  // 16:size of header (always =40 for icons)
  this.writeLong(16)  // 1a:icon width
  this.writeLong(32)  // 1e:icon height (including AND map)
  this.writeShort(1)  // 22:planes (=1)
  this.writeShort(4)  // 24:bits per pixel
  this.writeLong(0)   // 36:compression type (not allowed for icons)
  this.writeLong(128)   // 2a:image size in bytes
                        //    (not required if uncompressed, but I
                        //    specify it anyway for compatibility)
  this.writeLong(0)   // 2e:XpixelsPerM (unused)
  this.writeLong(0)   // 32:YpixelsPerM (unused)
  this.writeLong(16)   // 36:ColorsUsed (not required, but specified anyway)
  this.writeLong(16)   // 3a:ColorsImportant (same)

  // colors (3e)
  for (var i = 0; i < 16; i++) {
    this.writeByte(icon.colors[i][2])
    this.writeByte(icon.colors[i][1])
    this.writeByte(icon.colors[i][0])
    this.writeByte(0)
  }

  // XOR bitmap
  for (var row = 15; row >= 0; row--) {
    for (var col = 0; col < 16; col += 2) {
      this.writeByte(icon.data[row][col] << 4 |
                     icon.data[row][col + 1])
    }
  }

  // AND bitmap (I use all 0s for a totally opaque icon
  for (var row = 15; row >= 0; row--) {
    for (var col = 0; col < 16; col += 8) {
      this.writeByte(0)
    }
    this.writeByte(0)  // 0's to pad line to 32 bits
    this.writeByte(0)
  }
}

function RawIconData() {
  this.data = new Array()

  for (var i = 0; i < 16; i++) {
    this.data.push(new Array())

    for (var j = 0; j < 16; j++) {
      this.data[i][j] = 14
    }
  }

  this.colors = [ [0, 0, 0], [0, 0, 128], [0, 128, 0],
                  [0, 128, 128], [128, 0, 128], [128, 0, 0],
                  [128, 128, 0], [128, 128, 128],
                  [255, 255, 255], [0, 0, 255], [0, 255, 0],
                  [0, 255, 255], [255, 0, 255], [255, 0, 0],
                  [255, 255, 0], [192, 192, 192] ]
}

function show_file() {
  document.getElementById("file_view").value = file.toString("base64_encoded")
}

