--[[
-- DSL.walker.Format
Format is the interpreter of DSL's templating language.  It takes as input the opcodes describing the template 
and a DSL.waker.Template containing, which is used to navigate the codeast generated by DSL.walker.Code.
--]]

local format = string.format
local datastructures = require"DSL.datastructures"
local Stack = datastructures.Stack

local patterns = require"dsl.patterns"
local whitespace = patterns.whitespace

local lpeg = require"lpeg"
local P, S, R = lpeg.P, lpeg.S, lpeg.R
local C, V = lpeg.C, lpeg.V
local Ct, Cg, Cc = lpeg.Ct, lpeg.Cg, lpeg.Cc
local Cp, Cmt = lpeg.Cp, lpeg.Cmt
local Cb = lpeg.Cb

local iswhitespace = whitespace * P(-1)

local DBG = false
local _print = print
local print = function(...)
	if(DBG) then _print(...) end
end

local M = {}
M.__index = M

function M:append(elem)
	-- indent and append element
	self.tokens[#self.tokens+1] = elem:gsub("\n", "\n"..self.indent)
end

function M:rewind(idx)
	for i=#self.tokens, idx, -1 do
		self.tokens[i] = nil
	end
end

function M:loop()
	print"loop"
	local ops = self:current()
	local idx = self.positions[ops]
	
	idx = 1
	while(idx <= #ops) do
		self.positions[ops] = idx
		local ok = self:dispatch(ops[idx])
		if(not ok) then
			return false
		end
		idx = idx+1
	end
	return true
end

function M:rep(op)
	print("rep", op.n, #op)
	self.opstack:push(op)
	self.positions[op] = 0
	
	-- loop over a list of ops
	local ntok = #self.tokens
	local loc = self.wtem:loc()
	op.count = 0
	while(true) do
		-- loop
		local ok = self:loop()
		if(not ok) then
			break
		end
		-- increment
		ntok = #self.tokens
		loc = self.wtem:loc()
		op.count = op.count+1
	end
	
	self:rewind(ntok+1)
	self.wtem:rewind(loc)
	self.opstack:pop(op)
	print("end rep", op.n, op.count)
	return true
end

function M:sym(op)
	print("sym", self.wtem:current())
	local v = self.wtem:current()
	if(type(v) == "table")
		then self:append(self.cb(v))
		else return false
	end	
	self.wtem:next()
	return true
end

function M:newline(op)
	print"newline"
	self.tokens[#self.tokens+1] = op
	
	-- lookahead one op to see if it's a whitespace op
	-- if so, set it as the current indent string
	local ops = self:current()
	local idx = self.positions[ops]
	local next_op = ops[idx+1]
	if(
		next_op and 
		type(next_op) == "string" and 
		iswhitespace:match(next_op)
	) then
		self.indent = next_op
	end
	return true
end

function M:whitespace(op)
	print"whitespace"
	self:append(op)
	return true
end

function M:tok(op)
	print("tok", op, self.wtem:current())
	local ops = self:current()
	
	local advance = false
	if(ops.n) then
		if(ops.n == 0) then
			if(op == self.wtem:current()) then
				advance = true
			end
		else
			error"TODO"
		end
	else
		assert(op == self.wtem:current())
		advance = true
	end
	
	if(advance) then
		self:append(op)
		self.wtem:next()
	end
	return advance
end

-- opcode dispatcher
function M:dispatch(op)
	if(type(op) == "table") then
		return self:rep(op)
	elseif(op == "%s") then
		return self:sym(op)
	elseif(op == "\n" or op == "\r") then
		return self:newline(op)
	elseif(iswhitespace:match(op)) then
		return self:whitespace(op)
	else
		return self:tok(op)
	end
end

function M:write()
	local ok = self:loop()
	if(not ok) then
		error"unable to synthesize code"
	end
	return table.concat(self.tokens)
end

function M:current()
	return self.opstack:top()
end

return setmetatable(M, {
	__call = function(_, init)
		assert(init.ops)
		assert(init.wtem)
		assert(init.cb)
		local m = setmetatable(init, M)
		m.opstack = Stack(init.ops)
		m.positions = {
			[init.ops] = 0
		}
		m.tokens = {}
		m.indent = ""
		return m
	end
})