-- This file has dependencies to BOTH, the TeX part of pgfplots and the LUA part.
-- It is the only LUA component with this property.
--
-- Its purpose is to encapsulate the communication between TeX and LUA in a central LUA file
--
--
-- Here is the idea how the TeX backend communicates with the LUA backend:
--
-- * TeX can call LUA methods in order to "do something". The reverse direction is not true: LUA cannot call TeX methods.
--
-- * the only way that LUA can read TeX input values or write TeX output values is the top layer (at the time of this writing: only pgfplotstexio.lua ).
--
-- * The LUA backend has one main purpose: scalability and performance.
--   Its purpose is _not_ to run a standalone visualization.
--
--   The precise meaning of "scalability" is: the LUA backend should do as much
--   as possible which is done for single coordinates. Coordinates constitute
--   "scalability": the number of coordinates can become arbitrarily large.
--
--   "Performance" is related to scalability. But it is more: some dedicated
--   utility function might have a TeX backend and will be invoked whenever it
--   is needed. Candidates are colormap functions, z buffer arithmetics etc.
--
--   Thus, the best way to ensure "scalability" is to move _everything_ which is to be done for a single coordinate to LUA.
--
--   Sometimes, this will be impossible or too expensive.
--	 Here, "Performance" might still be optimized by some dedicated LUA function.
--
--
-- Unfortunately, the LUA backend does not simplify the code base - it makes it more complicated.
-- This is due to the way it is used: one needs to know when the TeX backend
-- delegates all its work to LUA. It is also due to the fact that it
-- duplicates code: the same code is present in TeX and in LUA. I chose to keep
-- the two code bases close to each other. This has a chance to simplify maintenance: if I know
-- how something works in TeX, and I find some entry point in LUA, it will
-- hopefully be closely related.
--
--
-- It follows an overview over entry points into the LUA backend:
--
-- * \begin{axis}. It invokes \pgfplots@prepare@LUA@api .
--     The purpose is to define the global pgfplots.gca "_g_et _c_urrent _a_xis" and to transfer some key presets.
--
--     Log message: "lua backend=true: Activating LUA backend for axis."
--
-- * \end{axis}. It invokes \pgfplots@LUA@visualization@update@axis .
--     The purpose is to transfer results of the survey phase to LUA; in particular axis properties like
--     the view direction, data scale transformations, axis wide limits and some related properties.
--     This has nothing to do with coordinates; the survey phase of coordinates is handled in a different way (see below).
--
--     Eventually, \pgfplots@LUA@cleanup will clear the global pgfplots.gca .
--
-- * \addplot . This has much to do with scalability, so much of its functionality is done in the LUA backend.
--
--     Keep in mind that \addplot is part of the survey phase: it collects coordinates and updates axis limits.
--     Afterwards, it stores the survey results in the current axis.
--
--     The survey phase currently has two different ways to communicate with the LUA backend:
--
--      1. PARTIAL MODE. In this mode, the coordinate stream comes from TeX:
--      some TeX code generates the coordinates. Whenever the stream is ready,
--      it will invoke \pgfplots@LUA@survey@point .  This, in turn, calls the
--      LUA  backend in order to complete the survey (which boils down to
--      pgfplots.texSurveyPoint). PARTIAL MODE saves lots of time, but its
--      scalability is limited due to the intensive use of TeX, it is less
--      powerful than COMPLETE MODE.
--
--      2. COMPLETE MODE. In this mode, the entire coordinate stream is on the
--      LUA side. The TeX code will merely call start the survey phase, call
--      LUA, and end the survey phase. This is the most efficient
--      implementation. At the time of this writing, it is limited to `\addplot
--      expression`: the code for `\addplot expression` tries to transfer the
--      entire processing to the LUA backend. If it succeeds, it will do
--      nothing on the TeX side.
--
--      Both PARTIAL MODE and COMPLETE MODE call
--        \pgfplots@LUA@survey@start : transfer plot type and current axis arguments to LUA
--      and
--        \pgfplots@LUA@survey@end : copy LUA axis arguments back to TeX.
--
--     Eventually,  the axis will initiate the visualization phase for each plot. This is done by
--        a) \pgfplots@LUA@visualization@init : it calls pgfplots.texVisualizationInit() and results in the log message
--               "lua backend=true: Activating partial LUA backend for visualization of plot 0".
--        b) \pgfplots@LUA@visualization@of@current@plot : it transfers control
--               to LUA (pgfplots.texVisualizePlot) and does as much with the
--               coordinates as possible. Eventually, it streams the result back to
--               TeX which will visualize the stream by means of PGF's plot streams.
--               This is somewhat complicated since it modifies the TeX streaming.
local pgfplotsmath = pgfplots.pgfplotsmath
local tex=tex
local tostring=tostring
local error=error
local table=table
local string=string
local pairs=pairs
local pcall=pcall
local type=type
local lpeg = require("lpeg")
local math = math

do
-- all globals will be read from/defined in pgfplots:
local _ENV = pgfplots

local pgftonumber = pgfluamathfunctions.tonumber

-- will be assigned by pgfplots at boot time.
LOAD_TIME_CATCODETABLE = nil

-- Called during \addplot, i.e. during the survey phase. It is only called in PARTIAL MODE (see above).
function texSurveyPoint(x,y,z,meta)
	local pt = Coord.new()
	pt.x[1] = x
	pt.x[2] = y
	pt.x[3] = z
	pt.meta = meta

	gca.currentPlotHandler:surveypoint(pt)
end

-- Called during \addplot, i.e. during the survey phase. It is only called in PARTIAL MODE (see above).
function texSurveyAddJump()
	local pt = Coord.new()
	pt.x[1] = nil
	pt.x[2] = nil
	pt.x[3] = nil
	pt.meta = nil

	gca:addSurveyedJump(gca.currentPlotHandler, pt)
end

-- Copies survey results of the current plot back to TeX. It prints a couple of executable TeX statements as result.
-- @see \pgfplots@LUA@survey@end
function texSurveyEnd()
	local result = gca:surveyToPgfplots(gca.currentPlotHandler, true)
	--log("returning " .. result .. "\n\n")

	tex.sprint(LOAD_TIME_CATCODETABLE, result);
	gca.currentPlotHandler=nil
end

-- A performance optimization: point meta transformation is done on the LUA side.
--
-- expands to the transformed point meta
function texPerpointMetaTrafo(metaStr)
    local meta = pgftonumber(metaStr)
    local transformed = gca.currentPlotHandler:visualizationTransformMeta(meta);
    tex.sprint(LOAD_TIME_CATCODETABLE, tostringfixed(transformed));
end

-- Called at the beginning of each plot visualization.
--
-- expands to '1' if LUA is available for this plot and '0' otherwise.
-- @see texVisualizePlot
function texVisualizationInit(plotNum, plotIs3d)
	if not plotNum or plotIs3d==nil then error("arguments must not be nil") end

    local currentPlotHandler = gca.plothandlers[plotNum+1]
    gca.currentPlotHandler = currentPlotHandler;
    if currentPlotHandler then
		currentPlotHandler.plotIs3d = plotIs3d
        currentPlotHandler:visualizationPhaseInit();
        tex.sprint("1")
    else
        -- ok, this plot has no LUA support.
        tex.sprint("0")
    end
end

local pgfXyCoordSerializer = function(pt)
	-- FIXME : it is unsure of whether this here really an improvement - or if it would be faster to compute that stuff in TeX...
	if pt.pgfXY ~=nil then
		return "{" .. tostringfixed(pt.pgfXY[1]) .. "}{" .. tostringfixed(pt.pgfXY[2]) .. "}"
	else
		return "{0}{0}"
	end
end

-- Actually does as much of the visualization of the current plot: it transforms all coordinates to some point where the TeX visualization mode continues.
--
-- It expands to the resulting coordinates. Note that these coordinates are already mapped somehow (typically: to fixed point)
-- @see texVisualizationInit
function texVisualizePlot(visualizerFactory)
	if not visualizerFactory then error("arguments must not be nil") end
	if type(visualizerFactory) ~= "function" then error("arguments must be a function (a factory)") end

    local currentPlotHandler = gca.currentPlotHandler
    if not currentPlotHandler then error("Illegal state: The current plot has no LUA plot handler!") end

	local visualizer = visualizerFactory(currentPlotHandler)

	local result = visualizer:getVisualizationOutput()
	local result_str = currentPlotHandler:getCoordsInTeXFormat(gca, result, pgfXyCoordSerializer)
	--log("returning " .. result_str .. "\n\n")
    tex.sprint(LOAD_TIME_CATCODETABLE, result_str)
end

-- Modifies the Surveyed coordinate list.
-- Expands to nothing
function texApplyZBufferReverseScanline(scanLineLength)
    local currentPlotHandler = gca.currentPlotHandler
    if not currentPlotHandler then error("This function cannot be used in the current context") end

    currentPlotHandler:reverseScanline(scanLineLength)
end

-- Modifies the Surveyed coordinate list.
-- Expands to nothing
function texApplyZBufferReverseTransposed(scanLineLength)
    local currentPlotHandler = gca.currentPlotHandler
    if not currentPlotHandler then error("This function cannot be used in the current context") end

    currentPlotHandler:reverseTransposed(scanLineLength)
end

-- Modifies the Surveyed coordinate list.
-- Expands to nothing
function texApplyZBufferReverseStream()
    local currentPlotHandler = gca.currentPlotHandler
    if not currentPlotHandler then error("This function cannot be used in the current context") end

    currentPlotHandler:reverseStream(scanLineLength)
end

-- Modifies the Surveyed coordinate list.
--
-- Note that this is UNRELATED to mesh/surface plots! They have their own (patch-based) z buffer.
--
-- Expands to nothing
function texApplyZBufferSort()
    local currentPlotHandler = gca.currentPlotHandler
    if not currentPlotHandler then error("This function cannot be used in the current context") end

   currentPlotHandler:sortCoordinatesByViewDepth()
end

-- Modifies the Surveyed coordinate list.
-- Expands to the resulting coordinates
function texGetSurveyedCoordsToPgfplots()
    local currentPlotHandler = gca.currentPlotHandler
    if not currentPlotHandler then error("This function cannot be used in the current context") end

    tex.sprint(LOAD_TIME_CATCODETABLE, currentPlotHandler:surveyedCoordsToPgfplots(gca))
end

function texColorMapSetScaleOrderZ(mapName, scaleOrderZ)
	local colormap = ColorMaps[mapName];
	if colormap then
		colormap:setScaleOrderZ(scaleOrderZ)
	end
end

-- Performance optimization: computes the colormap lookup.
function texColorMapPrecomputed(mapName, inMin, inMax, x)
	local colormap = ColorMaps[mapName];
	if colormap then
		local result = colormap:findPrecomputed(
			pgftonumber(inMin),
			pgftonumber(inMax),
			pgftonumber(x))

		local str = ""
		for i = 1,#result do
			if i>1 then str = str .. "," end
			str = str .. tostringfixed(result[i])
		end
		tex.sprint(LOAD_TIME_CATCODETABLE, str)
	end
end

-- Performance optimization: computes the colormap lookup.
function texColorMapFindPiecewiseConst(mapName, inMin, inMax, x)
	local colormap = ColorMaps[mapName];
	if colormap then
		local result = colormap:findPiecewiseConst(
			pgftonumber(inMin),
			pgftonumber(inMax),
			pgftonumber(x))

		local str = ""
		for i = 1,#result do
			if i>1 then str = str .. "," end
			str = str .. tostringfixed(result[i])
		end
		tex.sprint(LOAD_TIME_CATCODETABLE, str)
	end
end

local function isStripPrefixOrSuffixChar(char)
	return char == ' ' or char == '{' or char == "}"
end

-- Expressions can be something like
-- 	( {(6+(sin(3*(x+3*y))+1.25)*cos(x))*cos(y)},
--    {(6+(sin(3*(x+3*y))+1.25)*cos(x))*sin(y)},
--    {((sin(3*(x+3*y))+1.25)*sin(x))} );
--
-- These result in expressions = { " {...}", " {...}", " {...} " }
-- -> this function removes the surrounding braces and the white spaces.
local function removeSurroundingBraces(expressions)
	for i=1,#expressions do
		local expr = expressions[i]
		local startIdx
		local endIdx
		startIdx=1
		while startIdx<#expr and isStripPrefixOrSuffixChar(string.sub(expr,startIdx,startIdx)) do
			startIdx = startIdx+1
		end
		endIdx = #expr
		while endIdx > 0 and isStripPrefixOrSuffixChar(string.sub(expr,endIdx,endIdx)) do
			endIdx = endIdx-1
		end

		expr = string.sub(expr, startIdx, endIdx )
		expressions[i] = expr
	end
end

-------------
-- A parser for foreach statements - at least those which are supported in this LUA backend.
--
local samplesAtToDomain
do
	local P = lpeg.P
	local C = lpeg.C
	local V = lpeg.V
	local match = lpeg.match
	local space_pattern = P(" ")^0

	local Exp = V"Exp"
	local comma = P"," * space_pattern
	-- this does not catch balanced braces. Ok for now... ?
	local argument = C( ( 1- P"," )^1 ) * space_pattern
	local grammar = P{ "initialRule",
		initialRule = space_pattern * Exp * -1,
		Exp = lpeg.Ct(argument * comma * argument * comma * P"..." * space_pattern * comma *argument )
	}

	-- Converts very simple "samples at" values to "domain=A:B, samples=N"
	--
	-- @param foreachString something like -5,-4,...,5
	-- @return a table where
	-- 	[0] = domain min
	-- 	[1] = domain max
	-- 	[2] = samples
	-- 	It returns nil if foreachString is no "very simple value of 'samples at'"
	samplesAtToDomain = function(foreachString)
		local matches = match(grammar,foreachString)

		if not matches or #matches ~= 3 then
			return nil
		else
			local arg1 = matches[1]
			local arg2 = matches[2]
			local arg3 = matches[3]
			arg1= pgfluamathparser.pgfmathparse(arg1)
			arg2= pgfluamathparser.pgfmathparse(arg2)
			arg3= pgfluamathparser.pgfmathparse(arg3)

			if not arg1 or not arg2 or not arg3 then
				return nil
			end

			if arg1 > arg2 then
				return nil
			end

			local domainMin = arg1
			local h = arg2-arg1
			local domainMax = arg3

			-- round to the nearest integer (using +0.5, should be ok)
			local samples = math.floor((domainMax - domainMin)/h + 0.5) + 1

			return domainMin, domainMax, samples
		end
	end
end

-- This is the code which attempts to transfer control from `\addplot expression' to LUA.
--
-- If it succeeds, the entire plot stream and the entire survey phase has been done in LUA.
--
-- generates TeX output '1' on success and '0' on failure
-- @param debugMode one of a couple of strings: "off", "verbose", or "compileerror"
function texAddplotExpressionCoordinateGenerator(
	is3d,
	xExpr, yExpr, zExpr,
	sampleLine,
	domainxmin, domainxmax,
	domainymin, domainymax,
	samplesx, samplesy,
	variablex, variabley,
	samplesAt,
	debugMode
)
	local plothandler = gca.currentPlotHandler
	local coordoutputstream = SurveyCoordOutputStream.new(plothandler)

	if samplesAt and string.len(samplesAt) >0 then
		-- "samples at" has higher priority than domain.
		-- Use it!

		domainxmin, domainxmax, samplesx = samplesAtToDomain(samplesAt)
		if not domainxmin then
			-- FAILURE: could not convert "samples at".
			-- Fall back to a TeX based survey.
			log("log", "LUA survey failed: The value of 'samples at= " .. tostring(samplesAt) .. "' is unsupported by the LUA backend (currently, only 'samples at={a,b,...,c}' is supported).\n")
			tex.sprint("0")
			return
		end

	else
		domainxmin= pgftonumber(domainxmin)
		domainxmax= pgftonumber(domainxmax)
		samplesx= pgftonumber(samplesx)
	end

	local expressions
	local domainMin
	local domainMax
	local samples
	local variableNames

	-- allow both, even if sampleLine=1. We may want to assign a dummy value to y.
	variableNames = { variablex, variabley }

	if sampleLine==1 then
		domainMin = { domainxmin }
		domainMax = { domainxmax }
		samples = { samplesx }
	else
		local domainymin = pgftonumber(domainymin)
		local domainymax = pgftonumber(domainymax)
		local samplesy = pgftonumber(samplesy)

		domainMin = { domainxmin, domainymin }
		domainMax = { domainxmax, domainymax }
		samples = { samplesx, samplesy }
	end
	if is3d then
		expressions = {xExpr, yExpr, zExpr}
	else
		expressions = {xExpr, yExpr}
	end
	removeSurroundingBraces(expressions)

	local generator = AddplotExpressionCoordinateGenerator.new(
		coordoutputstream,
		expressions,
		domainMin, domainMax,
		samples,
		variableNames)

	local messageOnFailure
	local compileErrorOnFailure
	if debugMode == "compileerror" then
		compileErrorOnFailure = true
		messageOnFailure = true
	elseif debugMode == "off" or debugMode == "verbose" then
		messageOnFailure = true
		compileErrorOnFailure = false
	elseif debugMode == "off and silent" then
		messageOnFailure = false
		compileErrorOnFailure = false
	else
		error("Got unknown debugMode = " .. debugMode )
	end

	local success
	if compileErrorOnFailure then
		success = generator:generateCoords()
	else
		local resultOfGenerator
		success, resultOfGenerator = pcall(generator.generateCoords, generator)
		if success then
			-- AH: "pcall" returned 'true'. In this case, 'success' is the boolean returned by generator
			success = resultOfGenerator
		end

		if messageOnFailure and not success and type(resultOfGenerator) ~= "boolean" then
			log("log", "LUA survey failed: " .. resultOfGenerator .. ". Use \\pgfplotsset{lua debug} to see more.\n")
		end
	end

	if not type(success) == 'boolean' then error("Illegal state: expected boolean result") end

	if success then
		tex.sprint("1")
	else
		tex.sprint("0")
	end
end

-- Creates the default plot visualizer factory. It simply applies data scale trafos.
function defaultPlotVisualizerFactory(plothandler)
	return PlotVisualizer.new(plothandler)
end

end
