--[[ Converts a detection result into Lightroom develop crop settings.

Detection runs on the exported (display-oriented) JPEG, but crs crop
coordinates are relative to the unrotated "AB" pixel array, so the detected
frame corners must be mapped through the photo's orientation first. The
orientation code names the two original corners (A=top-left, B=top-right,
C=bottom-right, D=bottom-left) that appear along the top of the displayed
image.

Lightroom stores CropLeft/Top and CropRight/Bottom as two opposite corners
of the *tilted* crop polygon — the ones that become top-left and
bottom-right once the image is straightened by CropAngle about the image
center — in unrotated-image normalized coordinates. Which polygon corner
that is depends on the orientation, so the pair is chosen here from the
four mapped corners rather than in the detector.

CropAngle is unchanged by the 90/180 mappings: a proper rotation of the
whole frame preserves the sign and size of the in-frame tilt. Both the
detector and Lightroom use positive = visually clockwise.
]]

local CropApply = {}

-- display-space normalized point -> AB-space. Verified empirically against
-- a Lightroom-exported preview (not derived from the corner letters, whose
-- intuitive reading gives the opposite):
--  BC: displayed image is the original rotated 90° clockwise
--  DA: displayed image is the original rotated 90° counter-clockwise
--  CD: displayed image is the original rotated 180°
local function pointToAB(x, y, orientation)
	if orientation == nil or orientation == 'AB' then
		return x, y
	elseif orientation == 'BC' then
		return y, 1 - x
	elseif orientation == 'DA' then
		return 1 - y, x
	elseif orientation == 'CD' then
		return 1 - x, 1 - y
	end
	return nil -- mirrored orientations (BA/AD/DC/CB) are not supported
end

-- Must be called inside catalog:withWriteAccessDo. Returns true, or
-- false plus a reason.
function CropApply.apply(photo, result)
	local settings = photo:getDevelopSettings()
	local orientation = settings and settings.orientation or 'AB'

	-- AB-space pixel dimensions: the exported preview is display-oriented,
	-- so its axes are swapped for the 90° orientations.
	local w, h = result.width, result.height
	if orientation == 'BC' or orientation == 'DA' then
		w, h = h, w
	end

	local corners = {}
	for i, c in ipairs(result.corners) do
		local x, y = pointToAB(c[1] / result.width, c[2] / result.height,
			orientation)
		if x == nil then
			return false, 'mirrored orientation "' .. tostring(orientation)
				.. '" is not supported'
		end
		corners[i] = { x * w, y * h }
	end

	-- Straighten each corner about the image center to find which become
	-- the crop's top-left and bottom-right; their unrotated positions are
	-- what Lightroom stores.
	local cx, cy = w / 2, h / 2
	local rad = math.rad(-result.CropAngle)
	local cos_a, sin_a = math.cos(rad), math.sin(rad)
	local tl, br, minSum, maxSum
	for _, p in ipairs(corners) do
		local dx, dy = p[1] - cx, p[2] - cy
		local s = (cx + dx * cos_a - dy * sin_a) + (cy + dx * sin_a + dy * cos_a)
		if minSum == nil or s < minSum then
			minSum, tl = s, p
		end
		if maxSum == nil or s > maxSum then
			maxSum, br = s, p
		end
	end

	local function clamp(v)
		return math.max(0, math.min(1, v))
	end
	photo:applyDevelopSettings({
		CropLeft = clamp(tl[1] / w),
		CropTop = clamp(tl[2] / h),
		CropRight = clamp(br[1] / w),
		CropBottom = clamp(br[2] / h),
		CropAngle = result.CropAngle,
		CropConstrainAspectRatio = false,
	}, 'Cut Corners auto-crop')
	return true
end

-- Clears any existing crop so detection sees the full scan.
function CropApply.reset(photo)
	photo:applyDevelopSettings({
		CropLeft = 0,
		CropTop = 0,
		CropRight = 1,
		CropBottom = 1,
		CropAngle = 0,
	}, 'Cut Corners reset crop')
end

function CropApply.hasCrop(photo)
	local cropped = photo:getRawMetadata('croppedDimensions')
	local full = photo:getRawMetadata('dimensions')
	return cropped and full
		and (cropped.width ~= full.width or cropped.height ~= full.height)
end

return CropApply
