-- 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

local pgfplotsmath = pgfplots.pgfplotsmath
local error=error
local table=table
local string=string
local tostring=tostring
local type=type
local io=io

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


-------------------------------------------------------
-- A patch type.
-- @see \pgfplotsdeclarepatchclass

PatchType = newClass()

function PatchType:constructor(name, numVertices)
	self.name = name
	self.numVertices = numVertices
end

function PatchType:__tostring()
	return self.name
end

function PatchType:newPatch(coords)
	return Patch.new(self,coords)
end

LinePatchType = newClassExtends(PatchType)

function LinePatchType:constructor()
	PatchType.constructor(self, "line", 2)
end

TrianglePatchType = newClassExtends(PatchType)

function TrianglePatchType:constructor()
	PatchType.constructor(self, "triangle", 3)
end


RectanglePatchType = newClassExtends(PatchType)

function RectanglePatchType:constructor()
	PatchType.constructor(self, "rectangle", 4)
end

-------------------------------------------------------
--
-- a single patch.
-- @see \pgfplotsdeclarepatchclass

Patch = newClass()

function Patch:constructor(patchtype, coords)
	if not patchtype or not coords then error("arguments must not be nil") end
	if #coords ~= patchtype.numVertices then error("Unexpected number of coordinates provided; expected " .. tostring(patchtype.numVertices) .. " but got " .. tostring(#coords)) end

	self.patchtype = patchtype
	self.coords = coords
end

-------------------------------------------------------
-- Replicates \pgfplotsplothandlermesh (to some extend)
MeshPlothandler = newClassExtends(Plothandler)

function MeshPlothandler:constructor(axis, pointmetainputhandler)
    Plothandler.constructor(self,"mesh", axis, pointmetainputhandler)
end

-- see \pgfplot@apply@zbuffer
function MeshPlothandler:reverseScanline(scanLineLength)
    local coords = self.coords
    local tmp
    local scanlineOff
    local numScanLines = #coords / scanLineLength
    for scanline = 0,numScanLines-1,1 do
        scanlineOff = scanline * scanLineLength
        local reverseindex = scanlineOff + scanLineLength
        for i = 0,scanLineLength/2-1,1 do
            tmp = coords[1+scanlineOff+i]
            coords[1+scanlineOff+i] = coords[reverseindex]
            coords[reverseindex] = tmp

            reverseindex = reverseindex-1
        end
    end
end

-- see \pgfplot@apply@zbuffer
function MeshPlothandler:reverseTransposed(scanLineLength)
    local coords = self.coords
    local tmp
    local scanlineOff
    local numScanLines = #coords / scanLineLength
    local reverseScanline = numScanLines-1
    for scanline = 0,numScanLines/2-1,1 do
        scanlineOff = 1+scanline * scanLineLength
        reverseScanlineOff = 1+reverseScanline * scanLineLength
        for i = 0,scanLineLength-1 do
            tmp = coords[scanlineOff+i]
            coords[scanlineOff+i] = coords[reverseScanlineOff+i]
            coords[reverseScanlineOff+i] = tmp
        end

        reverseScanline = reverseScanline-1
    end
end

-- see \pgfplot@apply@zbuffer
function MeshPlothandler:reverseStream()
    local coords = self.coords
    local tmp
    local reverseindex = #coords
    for i = 1,#coords/2 do
        tmp = coords[i]
        coords[i] = coords[reverseindex]
        coords[reverseindex] = tmp
        reverseindex = reverseindex-1
    end
end



-------------------------------------------------------
--
-- The (LUA!) visualizer for patch plots. It prepares stuff such that TeX only needs to work with lowlevel driver (PGF) streams.
--

MeshVisualizer = newClassExtends(PlotVisualizer)

local COORDINATE_VALUE_OF_JUMPS = -16000
local meshVisualizerTagEmptyCoordinates = function(pt)
	pt.pgfXY= { COORDINATE_VALUE_OF_JUMPS, COORDINATE_VALUE_OF_JUMPS }
	pt.x = { COORDINATE_VALUE_OF_JUMPS, COORDINATE_VALUE_OF_JUMPS, COORDINATE_VALUE_OF_JUMPS }
end

function MeshVisualizer:constructor(sourcePlotHandler, patchType, rows, cols, isXVariesOrdering, isMatrixInput, isMatrixOutput, isZBufferSort)
	PlotVisualizer.constructor(self,sourcePlotHandler)
	self.patchType = patchType
	self.isMatrixInput = isMatrixInput
	self.isMatrixOutput = isMatrixOutput
	self.isZBufferSort = isZBufferSort
	self.rows = rows
	self.cols = cols
	self.isXVariesOrdering =isXVariesOrdering

	self.isOneDimMode= false
	self.scanLineLength =-1
	if isMatrixInput then
		-- isOneDimMode is ONLY interesting for matrix input
		if cols <= 1 or rows <=1 then
			self.isOneDimMode = true
			self.patchType = LinePatchType.new()
			-- this is not yet implemented (and cannot happen since the TeX call does catch this)
			error("UNSUPPORTED OPERATION EXCEPTION")
		end

		if isXVariesOrdering then
			-- x varies (=rowwise)
			self.scanLineLength = cols
		else
			-- y varies (=colwise)
			self.scanLineLength = rows
		end

		self.notifyJump = meshVisualizerTagEmptyCoordinates
	else
		-- disable any special handling
		self.isXVariesOrdering = true
	end

	-- log("initialized MeshVisualizer with " .. tostring(sourcePlotHandler) .. ", " .. tostring(patchType) .. ", isMatrixInput = " .. tostring(isMatrixInput) .. ", isMatrixOutput = " .. tostring(isMatrixOutput) .. ", isZBufferSort = " .. tostring(isZBufferSort) .. " rows = " ..tostring(rows) .. " cols = " ..tostring(cols) .. " is x varies=" .. tostring(isXVariesOrdering))
end

function MeshVisualizer:getVisualizationOutput()
	local result = PlotVisualizer.getVisualizationOutput(self)

	if self.isMatrixInput and not self.isMatrixOutput then
		result = self:decodeIntoPatches(result)
	end

	if self.isZBufferSort then
		result = self:applyZBufferSort(result)
	end

	return result
end

-- @param coords an array of Coord
function MeshVisualizer:applyZBufferSort(coords)
	-- in order to sort this thing, we need to compute the sort key (view depth) for each coord.
	-- furthermore, each list entry must be single patch... that means we need a (huge?) temporary table.

	local patchType = self.patchType
	local numVertices = patchType.numVertices

	if (#coords % numVertices) ~= 0 then error("Got an unexpected number of input coordinates: each patch has " .. tostring(numVertices) .. " vertices, but the number of coords " .. tostring(#coords) .. " is no multiple of this number") end
	local numPatches = #coords / numVertices

	-- STEP 1: compute an array of patches.
	local patches = {}
	local off=1
	for i = 1,numPatches do
		local patchCoords = {}
		for j = 1,numVertices do
			local pt = coords[off]
			off = off+1
			patchCoords[j] = pt
		end
		local patch = patchType:newPatch(patchCoords)
		patches[i] = patch
	end
	if off ~= 1+#coords then error("Internal error: not all coordinates are part of patches (got " .. tostring(off) .. "/" .. tostring(#coords) ..")") end

	-- STEP 2: assign the sort key: the "element depth".
	--
	-- the "element depth" is defined to be the MEAN of all
	-- vertex depths.
	-- And since the mean is 1/n * sum_{i=1}^n V_i, we can
	-- directly omit the 1/n --- it is the same for every
	-- vertex anyway, and we only want to compare the depth
	-- values.
	local axis = self.axis
	local getVertexDepth = axis.getVertexDepth
	for i=1,numPatches do
		local patch = patches[i]
		local patchcoords = patch.coords

		local sumOfVertexDepth = 0
		for j = 1,numVertices do
			local vertex = patchcoords[j]

			local vertexDepth = getVertexDepth(axis,vertex)

			sumOfVertexDepth = sumOfVertexDepth + vertexDepth
		end
		patch.elementDepth = sumOfVertexDepth
	end

	-- STEP 3: SORT.
	local comparator = function(patchA, patchB)
		return patchA.elementDepth > patchB.elementDepth
	end
	table.sort(patches, comparator)

	-- STEP 4: convert back into a list (in-place).
	local off = 1
	for i=1,numPatches do
		local patch = patches[i]
		local patchcoords = patch.coords
		for j = 1,numVertices do
			coords[off] = patchcoords[j]
			off = off+1
		end
	end
	if off ~= 1+#coords then error("Internal error: not all coordinates are part of patches (got " .. tostring(off) .. "/" .. tostring(#coords) ..")") end

	return coords
end

function MeshVisualizer:decodeIntoPatches(coords)
	local result = {}

	local scanLineLength = self.scanLineLength
	local length = #coords

	local i = scanLineLength
	while i < length do
		local im = i-scanLineLength

		for j = 2,scanLineLength do
			table.insert(result, coords[im+j-1]) -- (i-1,j-1)
			table.insert(result, coords[im+j])   -- (i-1,j  )
			table.insert(result, coords[i+j])    -- (i  ,j  )
			table.insert(result, coords[i+j-1])  -- (i  ,j-1)
		end

		i = i + scanLineLength
	end

	return result
end

end
