--- plugin/external/manager/4d/test-field_4d.lua
--
package.path = "../lib/?.lua;../lib/?.lx;" .. package.path
package.path = "../nc-server/lib/?.lua;../nc-server/lib/?.lx;" .. package.path
package.path = "../../../nc-server/lib/?.lua;" .. package.path
package.path = "../../../../nc-server/lib/?.lua;" .. package.path
local start = require "start"

local util = require "util"
local dprf = util -- require "dprf"
local peg = require "peg"
local ok, dschema, plg4d
ok, dschema = pcall(require, "dschema")
if not ok then
	dschema = require("db/db_schema")
end
ok, plg4d = pcall(require, "plg4d")
if not ok then
	plg4d = require("plugin_4d")
end
-- local unicode4d = require "unicode4d"
-- local fromUnistring = unicode4d.fromUnistring

local schema4d = "4d"
local c4d
if util.from4d() then
	c4d = util.c4d()
end

local function splitRecTypeName(name, recordType)
	local tbl, recType = peg.split(name, "-")
	if recType == "" and recordType then
		if type(recordType) == "table" then
			recType = recordType[1]
		else
			recType = recordType
		end
	end
	return tbl, recType
end

local function externalRec(tblName, schema, recordType)
	if dschema.fieldTable then
		local extf = dschema.fieldTable(schema)
		local locf = dschema.fieldTable()
		local rec = locf[tblName]
		local rec2 = rec and rec.external and rec.external[schema .. "/" .. (recordType or "")]
		if rec2 == nil then
			rec2 = extf[tblName]
			-- util.print("extf: '%s', rec: '%s', table: '%s'", tostring(extf), tostring(rec2), tblName)
			-- util.printTable(rec2 or {}, "rec2")
		end
		return rec2
	else
		return dschema.externalRec(tblName, schema, recordType)
	end
end

local function tableName(tblName, schema, recordType)
	if dschema.fieldTable then
		local rec = externalRec(tblName, schema, recordType)
		if rec then
			return rec.table_name
		end
	else
		return dschema.tableName(tblName, schema, recordType)
	end
end

local function tableNumber(tblName, schema, recordType)
	local ret, rec
	rec = externalRec(tblName, schema, recordType)
	if rec then
		ret = rec.table_number
	end
	return ret
end

local function getRecord(table)
	return plg4d.callMethodReturnString("_lx_FIELD_TEST_REC_TO_TEXT", table)
end

local recAfter, arrBefore, arrAfter
local delimiter = peg.P("ÿ") -- string.char(255), 4D charset is different from Lua charset
local function getRecordDiff(table, recBefore)
	recAfter = getRecord(table)
	arrBefore = peg.splitToArray(recBefore, delimiter)
	arrAfter = peg.splitToArray(recAfter, delimiter)
	-- util.print("arrBefore: %s", arrBefore)
	if #arrBefore ~= #arrAfter then
		util.print("*** ERROR, table %d, array size before %d, after %d ", table, #arrBefore, #arrAfter)
	end
	local ret = {table = table, field_count = #arrBefore}
	for i, v in ipairs(arrBefore) do
		if v ~= arrAfter[i] then
			if ret.diff == nil then
				ret.diff = {}
			end
			ret.diff[#ret.diff + 1] = {field = i, before = v, after = arrAfter[i]}
			util.print("* table %d, field %d, before '%s', after '%s' ", table, i, v:sub(1, 100), arrAfter[i]:sub(1, 100))
		end
	end
	return ret
end

local function testFieldScript()
	local prf
	if dschema.fieldTable then -- v12
		prf = dprf.prf("manager/4d/test-field_4d.json")
	else
		prf = dprf.prf("plugin/external/manager/4d/test-field_4d.json")
	end
	-- util.print("preference: '%s'", prf)
	-- 4D constants
	-- local kFalse = 0
	-- local _el_kInInput = 32 --  _el_kInBrowse = 16
	-- local _el_kmTblChange = 1100038
	local _prf_kTbl = 1

	local count = #prf.table_to_test
	util.print("* Testing field scripts for %d tables ", count)
	local totalTime = util.seconds()
	local totalFieldCount = 0
	local totalRecCount = 0
	local table4d, tableNum, tableCount, tableRecCount, fieldCount, recCount, prefix, ret, time, fieldNum, skipField
	local field, recBefore, recDiff
	local tblName, recordType
	for i, item in ipairs(prf.table_to_test) do
		tblName, recordType = splitRecTypeName(item.table)
		table4d = tableName(tblName, schema4d, recordType)
		tableNum = tableNumber(tblName, schema4d, recordType)
		tableCount = c4d.PA_CountTables()
		if tableNum == nil then
			tblName = tostring(tblName)
			table4d = tostring(table4d)
			tableNum = tostring(tableNum)
			table4d = tostring(table4d)
			util.print("ERROR: %d/%d, table '%s' record type '%s', '%s', table number: %s / %d ", i, count, tblName, recordType, table4d, tableNum, tableCount)
		else
			time = util.seconds()
			--[[
				plg4d.callMethodNoReturn("_el_ TBL_CHANGE", _el_kmTblChange, tableNum)
			plg4d.callMethodNoReturn("_tbl WRITE_STATE", tableNum, kFalse)
			-- plg4d.callMethodNoReturn("COMPILER_" .. prefix:sub(1, -2)) -- _el_ TBL_CHANGE() runs this
			-- plg4d.setLongVariable("_el lWhere", _el_kInBrowse) -- plg4d.setLongVariable() does not work
			plg4d.callMethodNoReturn("_sel ALL_RECS", tableNum)
			-- void PA_OrderBy ( short table, short field, char ascending );
			plg4d.callMethodNoReturn("_sel REDUCE", tableNum, recCount)
			plg4d.callMethodNoReturn("_el WHERE_SET", _el_kInInput) -- _tbl WRITE_STATE false and _el_kInInput causes _el Where -variable to be _el_kInBrowse ]]
			plg4d.callMethodNoReturn("_lx_FIELD_TEST_TABLE_BEFORE", tableNum)

			fieldCount = c4d.PA_CountFields(tableNum)
			prefix = plg4d.callMethodReturnString("_tbl NumToIdStr", tableNum)
			tableRecCount = c4d.PA_RecordsInSelection(tableNum)
			recCount = tableRecCount
			if item.test_records then
				if recCount > item.test_records then
					recCount = item.test_records
				end
				-- util.print("item.test_records: %d, recCount: %d, tableRecCount: %d", item.test_records, recCount, tableRecCount)
			elseif prf.test_records then
				if recCount > prf.test_records then
					recCount = prf.test_records
				end
				-- util.print("prf.test_records: %d, recCount: %d, tableRecCount: %d", prf.test_records, recCount, tableRecCount)
			end
			plg4d.callMethodNoReturn("_sel REDUCE", tableNum, recCount)

			if item.skip_4d_field and #item.skip_4d_field > 0 then
				skipField = table.concat(item.skip_4d_field, ", ")
			else
				skipField = ""
			end
			util.print("%d/%d, table '%s' record type '%s', '%s, %s', table number: %d / %d, fields: %d, testing: %d / %d records, skip fields: '%s' ", i, count, tblName, recordType, table4d, prefix, tableNum, tableCount, fieldCount, recCount, tableRecCount, skipField)
			for recNum = 1, recCount do
				recBefore = getRecord(tableNum)
				-- util.print("recBefore: '%s' ", recBefore:sub(1, 150) .. "...")
				local fieldStart = 0
				local fieldStartCount = 1 -- fieldCount
				repeat
					for fieldStartLoop = fieldStart, fieldStartCount do -- for long test loop and change fieldStart bigger until all combinations are tested
						for fieldLoop = 1, fieldCount do -- for long test loop and start start
							if prf.call_script then -- and skip_4d_field
								field = fieldStart + fieldLoop
								if field > fieldCount then
									field = fieldLoop
								end
								fieldNum = tableNum * 1000 + field
								ret = plg4d.callMethodReturnLong(prefix .. " Script", fieldNum)
								if ret ~= -10000 and ret ~= fieldNum then
									util.print("loop %s, %d/%d, field %d, %d/%d, return %d ", fieldStartLoop, recNum, recCount, fieldNum, field, fieldCount, ret)
								end
							end
						end
					end
					fieldStart = fieldStart + 1
				until fieldStart >= fieldStartCount
				recDiff = getRecordDiff(tableNum, recBefore)
				c4d.PA_NextRecord(tableNum) -- plg4d.callMethodNoReturn("_rec NEXT", tableNum)
			end
			plg4d.callMethodNoReturn("_lx_FIELD_TEST_TABLE_AFTER", tableNum)
			time = util.seconds(time)
			totalFieldCount = totalFieldCount + fieldCount
			totalRecCount = totalRecCount + recCount
			util.print("** table %s, %s: time %.2f seconds, %d records, %.0f records / second, %.0f fields / second ", table4d, prefix, time, recCount, recCount / time, recCount * fieldCount / time)
		end
	end
	totalTime = util.seconds(totalTime)
	util.print("*** tables %d: total time %.2f seconds, %d records, %.0f records / second, %.0f fields / second ", count, totalTime, totalRecCount, totalRecCount / totalTime, totalRecCount * totalFieldCount / totalTime)
	plg4d.callMethodNoReturn("_lx_FIELD_TEST_AFTER") -- plg4d.callMethodNoReturn("_el_ TBL_CHANGE", _el_kmTblChange, _prf_kTbl)

	return "ok"
end

if not ... then
	util.print("XXX started without argumnets")
	start.setFilePath("~/nc/nc-server/")
	local arg = {}
	require"coro-thread".setArgument(arg, false)
	testFieldScript()
end

return {testFieldScript = testFieldScript}
