--[[ Runs detect.py on an exported preview and parses the result JSON. ]]

local LrFileUtils = import 'LrFileUtils'
local LrPathUtils = import 'LrPathUtils'
local LrTasks = import 'LrTasks'

local Json = require 'Json'

local Detect = {}

local function quote(s)
	return '"' .. s .. '"'
end

local function scriptPath()
	return LrPathUtils.child(LrPathUtils.child(_PLUGIN.path, 'bin'), 'detect.py')
end

-- A frozen detector (tools/build_engine.sh output shipped inside the plugin)
-- removes the Python/OpenCV install requirement entirely; when present it is
-- preferred over whatever pythonPath points at. Exposed for diagnostics.
function Detect.enginePath()
	local name = WIN_ENV and 'cut-corners-detect.exe' or 'cut-corners-detect'
	local path = LrPathUtils.child(LrPathUtils.child(
		LrPathUtils.child(_PLUGIN.path, 'bin'), 'engine'), name)
	if LrFileUtils.exists(path) then
		return path
	end
	return nil
end

local function writeParams(path, opts)
	local fh = io.open(path, 'w')
	if not fh then
		return false
	end
	local parts = {
		string.format('"film_type": "%s"', opts.filmType),
		string.format('"edge_band": %.4f', opts.edgeBand),
		string.format('"inset": %.4f', opts.inset),
		string.format('"border": %.4f', tonumber(opts.border) or 0),
		string.format('"aspect_target": %.4f', opts.aspectTarget),
		string.format('"aspect_tolerance": %.4f', opts.aspectTolerance),
		string.format('"min_area": %.4f', opts.minArea),
		string.format('"max_area": %.4f', opts.maxArea),
		string.format('"manual_threshold": %d',
			math.floor((tonumber(opts.manualThreshold) or 0) + 0.5)),
		string.format('"dark_edge_mask": %s',
			opts.darkEdgeMask and 'true' or 'false'),
		string.format('"debug": %s', opts.debug and 'true' or 'false'),
	}
	-- Roll-consensus rescue hint (see AutoCrop.lua): present only on the
	-- second detection pass, where it replaces the whole strategy chain.
	if tonumber(opts.hintW) and tonumber(opts.hintW) > 0 then
		parts[#parts + 1] = string.format('"hint_cx": %.5f', opts.hintCx)
		parts[#parts + 1] = string.format('"hint_cy": %.5f', opts.hintCy)
		parts[#parts + 1] = string.format('"hint_w": %.5f', opts.hintW)
		parts[#parts + 1] = string.format('"hint_h": %.5f', opts.hintH)
		parts[#parts + 1] = string.format('"hint_angle": %.3f', opts.hintAngle)
	end
	fh:write('{' .. table.concat(parts, ', ') .. '}')
	fh:close()
	return true
end

local function readFile(path)
	if not LrFileUtils.exists(path) then
		return nil
	end
	return LrFileUtils.readFile(path)
end

-- Returns a result table ({skip=..., CropLeft=..., ...}) or nil plus an
-- error message suitable for the summary report.
function Detect.run(pythonPath, imagePath, opts)
	local paramsPath = imagePath .. '.params.json'
	local resultPath = imagePath .. '.result.json'
	local stderrPath = imagePath .. '.stderr.txt'

	if not writeParams(paramsPath, opts) then
		return nil, 'could not write params file'
	end

	local engine = Detect.enginePath()
	local runner = engine and quote(engine)
		or (quote(pythonPath) .. ' ' .. quote(scriptPath()))
	local cmd = runner .. ' '
		.. quote(imagePath) .. ' ' .. quote(paramsPath)
		.. ' 2>' .. quote(stderrPath)
	if WIN_ENV then
		cmd = '"' .. cmd .. '"'
	end

	local exitCode = LrTasks.execute(cmd)

	local raw = readFile(resultPath)
	if raw == nil then
		local stderr = readFile(stderrPath) or ''
		if stderr:find('ModuleNotFoundError') then
			return nil, 'Python is missing OpenCV. Run: '
				.. pythonPath .. ' -m pip install opencv-python numpy'
		elseif exitCode ~= 0 and stderr ~= '' then
			return nil, 'detector failed (exit ' .. exitCode .. '): '
				.. stderr:sub(1, 300)
		end
		return nil, 'detector produced no result (exit ' .. exitCode
			.. '). Check the Python path in the Plug-in Manager.'
	end

	local result, err = Json.decode(raw)
	if result == nil then
		return nil, 'could not parse detector output: ' .. tostring(err)
	end
	if result.error then
		return nil, 'detector error: ' .. result.error
	end
	return result
end

return Detect
