/*
	golpattern.js - ModernCA support for Game of Life pattern strings
*/

var zeros = new Array(
'0',
'0',
'00',
'000',
'0000',
'00000',
'000000',
'0000000',
'00000000',
'000000000',
'0000000000',
'00000000000',
'000000000000',
'0000000000000',
'00000000000000',
'000000000000000',
'0000000000000000',
'00000000000000000',
'000000000000000000',
'0000000000000000000',
'00000000000000000000') // 20 zeros

var ones = new Array(
'1',
'1',
'11',
'111',
'1111',
'11111',
'111111',
'1111111',
'11111111',
'111111111',
'1111111111') // 10 ones

var newlines = new Array(
'0.',
'0.',
'0.0.',
'0.0.0.',
'0.0.0.0.',
'0.0.0.0.0.',
'0.0.0.0.0.0.',
'0.0.0.0.0.0.0.',
'0.0.0.0.0.0.0.0.',
'0.0.0.0.0.0.0.0.0.',
'0.0.0.0.0.0.0.0.0.0.') // 10 newlines

function RLEDecode(rle)
{
	var result = ''
	var out = ''
	var repeat = 0
	var len = rle.length
	for (var i = 0; i < len; i++) {
		var c = rle.charAt(i)
		if (c >= '0' && c <= '9') {
			repeat = 10 * repeat + (c - '0')
		}
		else if (c == 'b') {
			while (repeat >= 21) {
				out += '00000000000000000000'
				repeat -= 20
			}
			out += zeros[repeat]
			repeat = 0
			if (out.length > 500) {
				result += out
				out = ''
			}
		}
		else if (c == 'o' || c == 'x' || c == 'y' || c == 'z') {
			while (repeat >= 11) {
				out += '1111111111'
				repeat -= 10
			}
			out += ones[repeat]
			repeat = 0
		}
		else if (c == '$') {
			while (repeat >= 11) {
				out += '0.0.0.0.0.0.0.0.0.0.'
				repeat -= 10
			}
			out += newlines[repeat]
			repeat = 0
		}
		else if (c == '!') {
			return result + out
		}
	}
}

function Block105Add(line)
{
	var out = ''
	for (var i = 0; i < line.length; i++) {
		out += line.charAt(i) == '*' ? 1 : line.charAt(i) == '.' ? 0 : ''
	}
	if (out.length == 0) {
		return
	}
	
	if (out.length > this.width) {
		this.width = out.length
		this.xmax = this.x + this.width - 1
	}
	
	this.height += 1
	if (this.y + this.height - 1 > this.ymax) {
		this.ymax = this.y + this.height - 1
	}
	
	this.lines[this.lines.length] = out
}

function Block105(x, y) {
	this.Add = Block105Add
	this.x = x
	this.y = y
	this.xmax = x
	this.ymax = y
	this.width = 0
	this.height = 0
	this.lines = new Array()
}

function BlockCompareX(a, b)
{
	return a.x == b.x ? 0 : a.x > b.x ? 1 : -1;
}

function Parse105(lines) {

	var ln = 0
	if (lines[0].indexOf('#Life 1.05') == 0) {
		if (++ln == lines.length) {
			return
		}
	}
	
	while (lines[ln].indexOf('#D') == 0 || lines[ln].indexOf('#C') == 0) {
		this.comments.push(lines[ln].substr(2))
		if (++ln == lines.length) {
			return
		}
	}
	
	if (lines[ln].indexOf('#R') == 0) {
		var sb = lines[ln].replace(/ /g, '').substr(2).split('/')
		if (sb.length >= 2) {
			this.rule = sb[1] + '/' + sb[0]
		}
		if (++ln == lines.length) {
			return
		}
	}
	
	if (lines[ln].indexOf('#') == 0) {
		if (++ln == lines.length) {
			return
		}
	}

	if (lines[ln].length > 0 && (lines[ln].charAt(0) == '*' || lines[ln].charAt(0) == '.')) {
		while (ln < lines.length) {
			var out = lines[ln].replace(/\*/g, '1').replace(/\./g, '0')
			if (out.length > this.width) {
				this.width = out.length
			}
			this.height += 1
			this.pattern += out + '.'
			if (++ln == lines.length || (lines[ln].charAt(0) != '*' && lines[ln].charAt(0) != '.')) {
				return
			}
		}
		return
	}

	var blocks = new Array()
	while (ln < lines.length - 1 && lines[ln].indexOf('#P') == 0) {
		var sizes = lines[ln++].substr(3).split(' ')
		if (sizes.length < 2) {
			return
		}
		var x = parseInt(sizes[0], 10)
		var y = parseInt(sizes[1], 10)
		if (isNaN(x) || isNaN(y)) {
			return
		}
		var block = new Block105(x, y)
		while (ln < lines.length && (lines[ln].charAt(0) == '*' || lines[ln].charAt(0) == '.')) {
			block.Add(lines[ln++])
		}
		while (ln < lines.length && lines[ln].length == 0) {
			ln += 1;
		}
		blocks.push(block)
	}
	
	if (blocks.length == 0) {
		return
	}

	blocks.sort(BlockCompareX)
	
	var xmin = blocks[0].x
	var ymin = blocks[0].y
	var xmax = blocks[0].xmax
	var ymax = blocks[0].ymax
	for (var i = 1; i < blocks.length; i++) {
		var x = blocks[i].x
		if (x < xmin) {
			xmin = x
		}
	
		x = blocks[i].xmax
		if (x > xmax) {
			xmax = x
		}
	
		var y = blocks[i].y
		if (y < ymin) {
			ymin = y
		}
		
		y = blocks[i].ymax
		if (y > ymax) {
			ymax = y
		}
	}
	this.width = xmax - xmin + 1
	this.height = ymax - ymin + 1
	
	for (var i = 0; i < blocks.length; i++) {
		blocks[i].x -= xmin
		blocks[i].xmax -= xmin
		blocks[i].y -= ymin
		blocks[i].ymax -= ymin
	}
	
	for (var y = 0; y < this.height; y++) {
		var x = 0
		var out = ''
		for (var b = 0; b < blocks.length; b++) {
			if (y >= blocks[b].y && y <= blocks[b].ymax) {
				var repeat = blocks[b].x - out.length
				if (repeat > 0) {
					while (repeat >= 21) {
						out += '00000000000000000000'
						repeat -= 20
					}
					out += zeros[repeat]
				}
				else if (repeat <= -1) {
					// Logical OR of two blocks which specify the same cell(s) (WING.LIF)
					var overlap = - repeat// - 1
					var additional = blocks[b].lines[y - blocks[b].y]
					if (additional.length < overlap) {
						overlap = additional.length
					}
					var existing = out.substr(blocks[b].x, overlap)
					var merged = ''
					for (var i = 0; i < overlap; i++) {
						merged += existing.charAt(i) == '1' ? '1' : additional.charAt(i)
					}
					merged += additional.substr(overlap)
					out = out.substr(0, blocks[b].x) + merged + out.substr(blocks[b].xmax + 1)
					continue
				}
				out += blocks[b].lines[y - blocks[b].y]
			}
		}
		this.pattern += out + '0.'
	}
}

function lt(a, b)
{
	return a < b ? -1 : a > b ? 1 : 0
}

function Parse106(lines) {

	var ln = 0
	if (lines[0].indexOf('#Life 1.06') == 0) {
		if (++ln == lines.length) {
			return
		}
	}
	
	while (lines[ln].indexOf('#D') == 0 || lines[ln].indexOf('#C') == 0) {
		this.comments.push(lines[ln].substr(2))
		if (++ln == lines.length) {
			return
		}
	}
	
	if (lines[ln].indexOf('#R') == 0) {
		var sb = lines[ln].replace(/ /g, '').substr(2).split('/')
		if (sb.length >= 2) {
			this.rule = sb[1] + '/' + sb[0]
		}
		if (++ln == lines.length) {
			return
		}
	}
	
	while (lines[ln].indexOf('#') == 0) {
		if (++ln == lines.length) {
			return
		}
	}
	
	var ax = new Array()
	var ay = new Array()
	var xmin = null
	var xmax = null
	var ymin = null
	var ymax = null
	
	while (ln < lines.length) {
		var xy = lines[ln++].split(' ')
		if (xy.length >= 2) {
			x = parseInt(xy[0], 10)
			y = parseInt(xy[1], 10)
			if (!(isNaN(x) || isNaN(y))) {
				if (xmin == null) {
					xmin = x
					xmax = x
					ymin = y
					ymax = y
				}
				else {
					if (x < xmin) {
						xmin = x
					}
					if (x > xmax) {
						xmax = x
					}
					if (y < ymin) {
						ymin = y
					}
					if (y > ymax) {
						ymax = y
					}
				}
				ax.push(x)
				ay.push(y)
			}
		}
	}
	
	this.width = xmax - xmin + 1
	this.height = ymax - ymin + 1
	
	var pts = new Array(ymax - ymin + 1)
	for (var i = 0; i < pts.length; i++) {
		pts[i] = new Array()
	}
	for (var i = 0; i < ax.length; i++) {
		pts[ay[i] - ymin].push(ax[i] - xmin)
	}
	
	for (var i = 0; i < pts.length; i++) {
		var out = ''
		var xvalues = pts[i]
		xvalues.sort(lt)
		var pos = 0
		for (var j = 0; j < xvalues.length; j++) {
			if (pos < xvalues[j]) {
				var fill = xvalues[j] - pos
				while (fill >= 21) {
					out += '00000000000000000000'
					fill -= 20
				}
				out += zeros[fill]
			}
			out += '1'
			pos = xvalues[j] + 1
		}	   
		this.pattern += out + '0.'        
	}
}

function ParseRLE(lines) {
	var repeat = 0
	var rle = ''
	for (var i = 0; i < lines.length; i++) {
		var line = lines[i]
		var len = line.length
		if (len == 0) {
			continue
		}
		var c = line.charAt(0)
		if (c == '#') {
			c = line.charAt(1)
			if (c == 'C' || c == 'c' || c == 'D' || c == 'd' || c == 'O') {
				this.comments.push(line.substr(2))
			}
			if (c == 'r') {
				this.rule = lines[i].replace(/ /g, '').substr(2)
			}
			continue
		}
		if (c == 'x' && rle.length == 0) {
			var terms = lines[i].replace(/ /g, '').split(',')
			if (terms.length >= 2 && terms[0].indexOf('x=') + terms[1].indexOf('y=') == 0) {
				this.width = parseInt(terms[0].substr(2), 10)
				this.height = parseInt(terms[1].substr(2), 10)
				if (this.width > 4000 || this.height > 4000) {
				    this.width = this.height = 0; return // limit pattern size
				}
				for (var t = 2; t < terms.length; t++) {
					if (terms[t].indexOf("rule=") == 0) {
						var bs = terms[t].substr(5).split('/')
						if (bs.length >= 2) {
							if (bs[1].charAt(0) == 'S' || bs[1].charAt(0) == 's') {
								this.rule = bs[0].substr(1) + '/' + bs[1].substr(1)
							}
							else {
								this.rule = bs[1].substr(1) + '/' + bs[0].substr(1)
							}
						}
					}
					else if (terms[t].indexOf('fps=') == 0) {
						this.fps = parseInt(terms[t].substr(4), 10)
					}
				}
			}
			continue
		}

		rle += line
		if (line.indexOf('!') >= 0) {
			this.pattern = RLEDecode(rle)
			if (this.comments.length == 0) {
				while (++i < lines.length) {
				    var s = lines[i].indexOf('#C') == 0 ? 2 : lines[i].indexOf('#') == 0 ? 1 : 0
					this.comments.push(lines[i].substr(s))
				}
			}
			return
		}
	}
}

function ParsePic(lines)
{
	var i = 1;
	while (i < lines.length && lines[i].indexOf('!') == 0) {
		this.comments.push(lines[i++].substr(2))
	}
	i += 1
	this.pattern = ''
	var width = 0
	var height = 0
	while (i < lines.length) {
		var ln = lines[i++]
		var out = ''
		var repeat = 0
		for (var j = 0; j < ln.length; j++) {
			var c = ln.charAt(j)
			if (c >= '0' && c <= '9') {
				repeat = 10 * repeat + (c - '0')
			}
			else if (c == '.') {
				while (repeat >= 21) {
					out += '00000000000000000000'
					repeat -= 20
				}
				out += zeros[repeat]
				repeat = 0
			}
			else if (c == 'O') {
				while (repeat >= 11) {
					out += '1111111111'
					repeat -= 10
				}
				out += ones[repeat]
				repeat = 0
			}
		}
		this.width = Math.max(this.width, out.length)
		this.height += 1
		this.pattern += out + '0.'
	}
}

function Comments(delim) {
	return this.comments.join(delim)
}

function Lines(delim) {
	return this.lines.join(delim)
}

function GOLPattern(str) {

	this.Parse105 = Parse105
	this.Parse106 = Parse106
	this.ParseRLE = ParseRLE
	this.ParsePic = ParsePic
	this.Comments = Comments
	this.Lines = Lines

	this.width = 0
	this.height = 0
	this.comments = new Array()
	this.rule = "3/23"
	this.fps = null
	this.pattern = ''
	
	this.lines = str.split('\r')
	if (this.lines.length == 1) {
		this.lines = str.split('\n')
	}
	else {
		for (var i = 0; i < this.lines.length; i++) {
			if (this.lines[i].charAt(0) == '\n') {
				this.lines[i] = this.lines[i].substr(1)
			}
		}
	}

	if (this.lines[0].indexOf('#Life 1.06') == 0) {
		this.Parse106(this.lines)
	}
	else if (this.lines[0].indexOf('#Life 1.05') == 0) {
		this.Parse105(this.lines)
	}
	else if (this.lines[0].indexOf('!') == 0) {
	    this.ParsePic(this.lines)
	}
	else {
		for (var i = 0; i < this.lines.length; i++) {
			if (this.lines[i].length > 0 && this.lines[i].charAt(0) == 'x') {
				this.ParseRLE(this.lines)
				return
			}
		}
		this.Parse105(this.lines)
	}
}
