"import dir from '../util/debug'"
"import string, flattenLines, flattenArray from '../util/ometa-string'"
"import OutputHTML, OutputXML, OutputXHTML from './output'"
"import php from './php'"
"import assignments, createMacro, resolve, echoLines, translateMultiLoops, isNamespace, elementOutput, startsWithUpperCase from './assignment'"
"import createScope from '../transform/Scope'"
"export PHP5Transpiler, PHP5TranspilerWithDebug"

var g = php()

ometa PHP5Transpiler {

	document = [#document [string:filename number number] anything:source :_ &anything:children enterScope(children.scope) topBlock:xs exitScope(children.scope)]
		-> flattenLines(xs).join('\n'),

	topBlock = [ignore*:x doctype:y node*:xs] -> x.concat(y, xs),
	block = &anything:x enterScope(x.scope) [node*:result] exitScope(x.scope) -> result,

	position = [string:filename number:line number:col savePosition(filename, line, col)]	-> [filename, line, col],
	position = [number:line number:col savePosition('*', line, col)]	-> ['*', line, col], // for compatibility

	ignore	=	[char*:s]	->	string(s), // empty or ignore

	node ignore,
	node [#closeStartTag] -> this.output.closeStartTag(),
	node [string:nodeType position:pos apply(nodeType):x] -> this.attachDebugInfo(x, pos),
	node :unknown -> { this.error('Unknown', unknown) },

	doctype = [#comment position [outputMethod:x]] -> x | -> ["echo '<!doctype html>', \"\\n\";"],
	outputMethod [spaces ("html" spaces? "5"?) spaces] -> ["echo '<!doctype html>', \"\\n\";"],
	outputMethod [spaces ("xhtml" !this.setOutputMethod('xhtml') spaces "mobile") spaces] -> ["echo '<!DOCTYPE html PUBLIC \"-//WAPFORUM//DTD XHTML Mobile 1.2//EN\" \"http://www.openmobilealliance.org/tech/DTD/xhtml-mobile12.dtd\">', \"\\n\";"],
	outputMethod [spaces ("xhtml" !this.setOutputMethod('xhtml') spaces "mp 1.0") spaces] -> ["echo '<!DOCTYPE html PUBLIC \"-//WAPFORUM//DTD XHTML Mobile 1.0//EN\" \"http://www.wapforum.org/DTD/xhtml-mobile10.dtd\">', \"\\n\";"],
	outputMethod [spaces ("xhtml" !this.setOutputMethod('xhtml') spaces "mp 1.1") spaces] -> ["echo '<!DOCTYPE html PUBLIC \"-//WAPFORUM//DTD XHTML Mobile 1.1//EN\" \"http://www.openmobilealliance.org/tech/DTD/xhtml-mobile11.dtd\">', \"\\n\";"],
	outputMethod [spaces ("xhtml" !this.setOutputMethod('xhtml') spaces "mp 1.2") spaces] -> ["echo '<!DOCTYPE html PUBLIC \"-//WAPFORUM//DTD XHTML Mobile 1.2//EN\" \"http://www.openmobilealliance.org/tech/DTD/xhtml-mobile12.dtd\">', \"\\n\";"],
	outputMethod [spaces ("xhtml" !this.setOutputMethod('xhtml') spaces "basic") spaces] -> ["echo '<!doctype html>', \"\\n\";"],
	outputMethod [spaces ("xml" !this.setOutputMethod('xml')) spaces] -> ["echo '<?xml version=\"1.0\" encoding=\"utf-8\"?>', \"\\n\";"],

	tupleItem [#Mapping [#Symbol :key] trans:expr] -> ("'" + key + "' => " + expr),


	literal [#Null]	->	'null',
	literal [#True]	->	'true',
	literal [#False]	->	'false',
	literal [#NaN]	->	'NAN',
	literal [#PosInf]	->	'+INF',
	literal [#NegInf]	->	'-INF',
	literal [#Number :n]	->	String(n),

	spread [#Spread expr:x] -> x,

	stringLiteral [#String :s]	->	("'" + s.replace(/'/g, "\\'") + "'"),
	templateLiteral [#Quasi undefined [trans*:list]]	->	('(' + (list.length === 0 ? "''" : list.join(') . (')) + ')'),
	listLiteral [#List []]	->	'array()',
	listLiteral [#List [listItemGroup:x]]	->	x,
	listLiteral [#List [listItemGroup+:groups]]	->	('array_merge(' + groups.join(', ') + ')'),

	listItemGroup	= spread:x	-> ('\\Jedi\\toArray(' + x + ')')
		| expr+:list	-> ('array(' + list.join(', ') + ')'),

	recordLiteral [#Record []]	->	'\\Jedi\\record(array())',
	recordLiteral [#Record [recordItemGroup:x]]	->	('\\Jedi\\record(' + x + ')'),
	recordLiteral [#Record [recordItemGroup*:groups]]	->	('\\Jedi\\record(array_replace(' + groups.join(', ') + '))'),

	recordItemGroup	= spread:x	-> ('(array)(' + x + ')')
		| recordItem+:list	-> ('array(' + list.join(', ') + ')'),

	recordItem [#RecordItem string:s trans:value]	->	("'" + s + "' => " + value),
	recordItem [#RecordItem trans:key trans:value]	->	(key + ' => ' + value),

	//call ["." [#Symbol 'url'] [#Symbol 'static']]	-> 'UrlBuilder::staticResource',
	call ['.' [#Symbol :s externalSymbol(s, true):x] [#Symbol :y]]	-> (isNamespace(x) ? x.slice(0, -1) + y : x + '::' + y),
	call ['.' (memberSymbol|call):x [#Symbol :y]]	->	(x + '->' + g.varName(y)),
	call ['.' trans:x [#Symbol :y]]	->	('($_ = ' + x + ') && false ?: $_' + '->' + g.varName(y)),
	call ['?.' trans:x [#Symbol :y]]	->	('(' + x + ' ? ' + x + '->' + g.varName(y) + ' : null)'),

	call [#Call trans:x [#List [trans:y]]]	->	(x + '[' + y + ']'),
	call [#Call ['.' [#Symbol :s externalSymbol(s, false):x] [#Symbol 'new']] &[#Tuple :_] trans:y]	->	('(new ' + x + y + ')'),
	call [#Call (callSymbol|call):x &[#Tuple :_] trans:y]	->	(x + y),
	call [#Call (callSymbol|call):x trans:y]	->	(x + '(' + y + ')'),
	call [#Call trans:x &[#Tuple :_] trans:y]	->	('($_ = ' + x + ') && false ?: $_' + y),
	call [#Call trans:x trans:y]	->	('($_ = ' + x + ') && false ?: $_(' + y + ')'),

	range ['...' trans:x trans:y]	->	('(range(' + x + ', ' + y + '))'),
	range ['..<' trans:x trans:y]	->	('(range(' + x + ', ' + y + ' - 1))'),
	bin [#BinaryOp 'mod' trans:x trans:y]	->	('(' + x + ' ' + '%' + ' ' + y + ')'),
	bin [#BinaryOp 'div' trans:x trans:y]	->	('(' + x + ' ' + '/' + ' ' + y + ')'),
	bin [#BinaryOp 'mul' trans:x trans:y]	->	('(' + x + ' ' + '*' + ' ' + y + ')'),
	bin [#BinaryOp :op trans:x trans:y]	->	('(' + x + ' ' + op + ' ' + y + ')'),
	bin [#UnaryOp :op trans:x]	->	('!' + x),
	symbol [#Symbol '*']	->	'$context',
	symbol [#Symbol :s resolve(s):x]	->	x,

	callSymbol [#Symbol :s externalSymbol(s, false):x]	->	x,
	callSymbol [#Symbol :s resolve(s):x warnMissingUse(s, x)]	->	x,

	memberSymbol [#Symbol '*']	->	'$context',
	memberSymbol [#Symbol :s externalSymbol(s, false):x]	->	x,
	memberSymbol [#Symbol :s resolve(s):x !{if (x.startsWith('$data->') && startsWithUpperCase(s)) this.warnMissingUse(s, x) }]	->	x,

	ternary [#Ternary expr:x expr:y undefined]	->	('(' + x + ') ? (' + y + ') : null'),
	ternary [#Ternary expr:x expr:y expr:z]	->	('(' + x + ') ? (' + y + ') : (' + z + ')'),

	expr	=	literal | stringLiteral | templateLiteral | listLiteral | recordLiteral | symbol | call | range | bin | ternary,

	trans	=	expr,

	trans [#String :s :raw]	->	(this.output.convertNewlines("'" + s.replace(/'/g, '\\\'') + "'")),
	trans [#Tuple [trans*:list]]	->	('(' + list.join(', ') + ')'),
	trans [#Tuple [tupleItem*:list]]	->	('[' + list.join(', ') + ']'),

	trans :unknown	->	{ this.error('UnknownExpression', unknown) },

	comment [:line] -> ["echo '<!-- ', " + this.output.escapeComment(g.str(line)) + ", ' -->';"],
	comment [echoComment*:xs] -> [].concat(
			"echo '<!--', \"\\n\";",
			xs,
			"echo '-->';"
		),
	echoComment [char*:s] -> ("echo " + this.output.escapeComment(g.str(s)) + ", \"\\n\";"),

	scriptsource [scriptsourceLine*:lines]	->	echoLines(lines),
	scriptsourceLine string:s	->	(this.output.escapeScriptSource(g.str(s)) + ", \"\\n\""),

	text stringLiteral:s	->	['echo ' + this.output.escapeText(s) + ';'],
	text templateLiteral:s	->	['echo ' + this.output.phpEscapeText(s) + ';'],
	text undefined [textLine*:lines]	->	echoLines(lines),
	text undefined [echoTemplateString*:lines]	->	lines,

	textLine string:s	->	(this.output.escapeText(g.str(s)) + ', "\\n"'),
	echoTemplateString []	->	'',
	echoTemplateString [trans+:exprList]	->	('echo ' + this.output.phpEscapeText('(' + exprList.join(') . (') + ')') + ';'),

	element :pattern (undefined|trans):bind &anything:children block:body
		-> elementOutput(this.output, pattern, bind, children.scope, body),


	attribute :name undefined undefined	->	["echo ' " + name + "';"],
	attribute :name undefined	->	["echo ' " + name + "';"],
	attribute :name :op undefined	->	["echo ' " + name + "';"],
	attribute :name :op [#String :s] -> ["echo ' " + name + '="' + this.output.attrValue(string(s)).replace(/'/g, "\\'") + "\"';"],
	attribute :name :op trans:exp -> ["echo \\jedi\\attribute('" + name + "', " + exp + ");"],

	binding :_ trans:expr [] -> ['echo ' + this.output.phpEscapeText(expr) + ';'],
	//binding trans:expr [] -> ["$__temp = " + expr + "; if (method_exists($__temp, '__toString')) echo htmlspecialchars($__temp); else print_r($__temp);"],
	binding :_ trans:expr &anything:children block:body
		-> g.block(children.scope, body, expr),

	instruction #external ![]:xs [[#Symbol :x !xs.push(x)]+] :_
		-> ('// external ' + xs.join(', ') + '\n'),
	instruction #use [:segments :aliases] :_
		-> ('// use ' + segments.join('/') + '/' + (aliases.length > 1 ? '{' + aliases.map(function (x) { return x[0] + (x[1] ? ' as ' + x[1] : '') }).join(', ') + '}' : aliases.map(function (x) { return x[0] + (x[1] ? ' as ' + x[1] : '') }).join() + '\n')),

	instruction #if trans:expr block:b -> ["if (" + expr + ") {", b, "}"],
	instruction #elseif trans:expr block:b -> ["else if (" + expr + ") {", b, "}"],
	instruction #else :_ block:b -> ["else {", b, "}"],

	instruction #let [([:bind trans:expr] -> [bind, expr])+:binds] &anything:children block:body
		->	assignments(binds, children.scope, body),
	instruction #for [([trans:it :value :key :index] -> [it, value, key, index])+:binds] &anything:children block:body
		->	translateMultiLoops(binds, children, body),

	instruction #unsafe trans:x :_ -> ('echo ' + x + ';'),
	instruction #d trans:x :_	->	('var_dump(' + x + ');'),
	instruction #end :x :_	-> ('// end ' + x),

	suppress :lines -> (lines.map(function(line){ return '// ' + line })),
	fragment :name :_ block:b -> ['//  #' + name, b],
	macro :name :param &anything:children block:body -> createMacro(name, param, children.scope, body),

	endElement -> '',

	inject :x :y resolve(#data) [string*:lines] -> [x + ';'].concat(lines.map(function(l){ return '//' + l })),
	inject :x :y resolve(#data) block:body -> [x, '{', body, '}'],
	/*filter [#Symbol [``if'']] [spaces Expression.expression:exp node(exp):x] block:body
		-> ['if (' + x + ') {', body, '}'],
	filter [#Symbol [``else'']] :x block:body
		-> ['else {', body, '}'],

	Quasi :tag [trans*:templateStrings]
		-> [echo(closeStartTag()) + "Jedi::Quasi(" +
			(tag ? "'" + tag.join('') + "'": null) +
			", " +
			templateStrings.join(', ') +
			');'],

	//directive :type [apply(type):x] -> x,
	//node = directive (#Comment | #Suppress | #Inject | #Element | #Text | #Filter)
	//Element :tag [:cls, :id] :body ->*/

	END
}

PHP5Transpiler.initialize = function() {

	this._scopes = [createScope()]

	this.currentScope = function () {
		return this._scopes[this._scopes.length - 1]
	}

	this.enterScope = function (x) {
		//console.log('enter scope', x)
		if (x) this._scopes.push(x)
	}

	this.exitScope = function (x) {
		//console.log('exit scope', this.currentScope())
		if (x) {
			if (x !== this.currentScope()) {
				//console.log(x)
				//console.log(this.currentScope())
				this.error('scope error')
			}
			this._scopes.pop()
		}
	}

	this.resolve = function (name) {
		var x = resolve(name, this.currentScope())
		if (typeof x === 'string') return x
		if (x[x.length - 1] === '*') {
			this.error('IllegalNamespaceUsage', {symbol: name, namespace: x.join('/')})
		}
		return x.join('\\')
	}

	this.externalSymbol = function(symbol, allowNamespace) {
		var x = resolve(symbol, this.currentScope())
		if (typeof x === 'string') throw fail
		if (!allowNamespace && x[x.length - 1] === '') {
			this.error('IllegalNamespaceUsage', {symbol: symbol, namespace: x.join('/')})
		}
		return x.join('\\')
	}

	this.savePosition = function (filename, line, column) {
		this._currentPosition = [filename, line, column]
	}

	this.error = function (name, data) {
		var err = new Error(name)
		err.position = this._currentPosition.slice()
		err.data = data
		throw err
	}

	this.warnMissingUse = function (symbol, resolved) {
		this.error('missing use/external for symbol "' + symbol + ' (will be resolved to ' + resolved + ')"?')
	}

	this.output = new OutputHTML()

	this.setOutputMethod = function (method) {
		console.log(method)
		switch (method) {
			case 'html':
				this.output = new OutputHTML()
				break
			case 'xhtml':
				this.output = new OutputXHTML()
				break
			case 'xml':
				this.output = new OutputXML()
				break
			default:
				this.error('UnknownOutputMethod', method)
		}
	}

	this.attachDebugInfo = function (x, pos) {
		return x
	}
}

ometa PHP5TranspilerWithDebug <: PHP5Transpiler {}

PHP5TranspilerWithDebug.initialize = function () {
	PHP5Transpiler.initialize.apply(this, arguments)
	this.attachDebugInfo = function (x, pos) {
		return [
			'// ' + pos[1] + ', ' + pos[2] + ' @ ' + pos[0]
		].concat(x)
	}
}
