CylinderedExtension = {
	MOD_NAME = g_currentModName,
	SPEC_NAME = g_currentModName .. ".cylinderedExtension"
}
CylinderedExtension.SPEC_TABLE_NAME = "spec_" .. CylinderedExtension.SPEC_NAME

function CylinderedExtension.prerequisitesPresent(specializations)
	return true
end

function CylinderedExtension.initSpecialization()
	local schema = Vehicle.xmlSchema

	schema:setXMLSpecializationType("CylinderedExtension")
	schema:addDelayedRegistrationFunc("AnimatedVehicle:part", function (cSchema, cKey)
		cSchema:register(XMLValueType.NODE_INDEX, cKey .. "#startReferencePoint", "Start reference point")
		cSchema:register(XMLValueType.NODE_INDEX, cKey .. "#endReferencePoint", "End reference point")
	end)

	local partKey = Cylindered.MOVING_PART_XML_KEY

	schema:register(XMLValueType.BOOL, partKey .. "#do3DLineAlignment", "Do 3D line alignment", false)
	schema:register(XMLValueType.NODE_INDEX, partKey .. ".orientationLine#referenceTransNode", "Node that is moved to the current line position and at the same time is used a referencePoint for the directional alignment of the movingPart")
	schema:setXMLSpecializationType()
end

function CylinderedExtension.registerFunctions(vehicleType)
end

function CylinderedExtension.registerOverwrittenFunctions(vehicleType)
	SpecializationUtil.registerOverwrittenFunction(vehicleType, "loadMovingPartFromXML", CylinderedExtension.loadMovingPartFromXML)
end

function CylinderedExtension.registerEventListeners(vehicleType)
	SpecializationUtil.registerEventListener(vehicleType, "onRegisterAnimationValueTypes", CylinderedExtension)
end

function CylinderedExtension:onRegisterAnimationValueTypes()
	self:registerAnimationValueType("movingPartReferencePoint", "", "", false, AnimationValueFloat, function (value, xmlFile, xmlKey)
		value.node = xmlFile:getValue(xmlKey .. "#node", nil, value.part.components, value.part.i3dMappings)
		value.startReferencePoint = xmlFile:getValue(xmlKey .. "#startReferencePoint", nil, value.part.components, value.part.i3dMappings)
		value.endReferencePoint = xmlFile:getValue(xmlKey .. "#endReferencePoint", nil, value.part.components, value.part.i3dMappings)

		if value.node ~= nil and value.startReferencePoint ~= nil and value.endReferencePoint ~= nil then
			value:setWarningInformation("node: " .. getName(value.node))
			value:addCompareParameters("node")

			value.animatedReferencePoint = createTransformGroup("animatedReferencePoint_" .. getName(value.node))

			link(getParent(value.node), value.animatedReferencePoint)
			setWorldTranslation(value.animatedReferencePoint, getWorldTranslation(value.startReferencePoint))

			value.startValue = {
				0
			}
			value.endValue = {
				1
			}

			return true
		end

		return false
	end, function (value)
		local startTime = value.startValue or value.endValue

		if value.animation.currentSpeed < 0 then
			startTime = value.endValue or value.startValue
		end

		return startTime[1]
	end, function (value, alpha)
		if value.movingPart == nil then
			value.movingPart = self:getMovingPartByNode(value.node)
		end

		local x1, y1, z1 = localToLocal(value.startReferencePoint, getParent(value.node), 0, 0, 0)
		local x2, y2, z2 = localToLocal(value.endReferencePoint, getParent(value.node), 0, 0, 0)
		local x, y, z = MathUtil.vector3Lerp(x1, y1, z1, x2, y2, z2, alpha)

		setTranslation(value.animatedReferencePoint, x, y, z)

		if value.movingPart ~= nil then
			if alpha == 1 then
				value.movingPart.referencePoint = value.endReferencePoint
			elseif alpha == 0 then
				value.movingPart.referencePoint = value.startReferencePoint
			else
				value.movingPart.referencePoint = value.animatedReferencePoint
			end
		end
	end)
end

function CylinderedExtension:loadMovingPartFromXML(superFunc, xmlFile, key, entry)
	if superFunc(self, xmlFile, key, entry) then
		if entry.doLineAlignment then
			entry.do3DLineAlignment = xmlFile:getValue(key .. "#do3DLineAlignment", false)

			if entry.do3DLineAlignment then
				if #entry.orientationLineNodes == 2 then
					entry.orientationLineTransNode = xmlFile:getValue(key .. ".orientationLine#referenceTransNode", nil, self.components, self.i3dMappings)

					if entry.orientationLineTransNode == nil then
						Logging.xmlWarning(xmlFile, "Failed to load 3D line alignment from xml. Missing referenceTransNode!")

						entry.do3DLineAlignment = false
					end
				else
					Logging.xmlWarning(xmlFile, "Failed to load 3D line alignment from xml. Requires exactly two line nodes!")

					entry.do3DLineAlignment = false
				end
			end
		end

		return true
	end

	return false
end

Cylindered.updateMovingPart = Utils.overwrittenFunction(Cylindered.updateMovingPart, function (self, superFunc, part, placeComponents, updateDependentParts, isActive, updateSounds, ...)
	if part.do3DLineAlignment then
		local changed = false
		local partLength = part.partLength

		if part.partLengthNode ~= nil then
			partLength = calcDistanceFrom(part.node, part.partLengthNode)
		end

		local startNode = part.orientationLineNodes[1]
		local endNode = part.orientationLineNodes[2]
		local x, y, z = getWorldTranslation(part.node)
		local sx, sy, sz = getWorldTranslation(startNode)
		local ex, ey, ez = getWorldTranslation(endNode)
		local startDistance = MathUtil.vector3Length(sx - x, sy - y, sz - z)
		local endDistance = MathUtil.vector3Length(ex - x, ey - y, ez - z)
		local alpha = MathUtil.clamp(MathUtil.inverseLerp(startDistance, endDistance, partLength), 0, 1)
		local rx, ry, rz = MathUtil.vector3Lerp(sx, sy, sz, ex, ey, ez, alpha)
		local dirX, dirY, dirZ = MathUtil.vector3Normalize(rx - x, ry - y, rz - z)

		setWorldTranslation(part.orientationLineTransNode, rx, ry, rz)

		if dirX ~= 0 or dirY ~= 0 or dirZ ~= 0 then
			local upX, upY, upZ = localDirectionToWorld(part.referenceFrame, 0, 1, 0)

			if part.invertZ then
				dirX = -dirX
				dirY = -dirY
				dirZ = -dirZ
			end

			local directionThreshold = part.directionThresholdActive

			if not self.isActive and part.directionThreshold ~= nil and part.directionThreshold > 0 then
				directionThreshold = part.directionThreshold
			end

			local lDirX, lDirY, lDirZ = worldDirectionToLocal(part.parent, dirX, dirY, dirZ)
			local lastDirection = part.lastDirection
			local lastUpVector = part.lastUpVector

			if directionThreshold < math.abs(lastDirection[1] - lDirX) or directionThreshold < math.abs(lastDirection[2] - lDirY) or directionThreshold < math.abs(lastDirection[3] - lDirZ) or directionThreshold < math.abs(lastUpVector[1] - upX) or directionThreshold < math.abs(lastUpVector[2] - upY) or directionThreshold < math.abs(lastUpVector[3] - upZ) then
				I3DUtil.setWorldDirection(part.node, dirX, dirY, dirZ, upX, upY, upZ, part.limitedAxis, part.minRot, part.maxRot)

				if part.debug then
					local x, y, z = getWorldTranslation(part.node)
					local length = 1
					local _ = nil

					if part.referencePoint ~= nil then
						_, _, length = worldToLocal(part.node, refX, refY, refZ)
					end

					drawDebugLine(x, y, z, 1, 0, 0, x + dirX * length, y + dirY * length, z + dirZ * length, 0, 1, 0, true)
				end

				lastDirection[3] = lDirZ
				lastDirection[2] = lDirY
				lastDirection[1] = lDirX
				lastUpVector[3] = upZ
				lastUpVector[2] = upY
				lastUpVector[1] = upX
				changed = true
			else
				changed = false
			end

			if part.scaleZ and part.localReferenceDistance ~= nil then
				local len = MathUtil.vector3Length(dirX, dirY, dirZ)

				setScale(part.node, 1, 1, len / part.localReferenceDistance)

				if part.debug then
					DebugUtil.drawDebugNode(part.node, string.format("scale:%.2f", len / part.localReferenceDistance), false)
				end
			end
		end

		if changed then
			if part.copyLocalDirectionParts ~= nil then
				for _, copyLocalDirectionPart in pairs(part.copyLocalDirectionParts) do
					local dx, dy, dz = localDirectionToWorld(part.node, 0, 0, 1)
					dx, dy, dz = worldDirectionToLocal(getParent(part.node), dx, dy, dz)
					dx = dx * copyLocalDirectionPart.dirScale[1]
					dy = dy * copyLocalDirectionPart.dirScale[2]
					dz = dz * copyLocalDirectionPart.dirScale[3]
					local ux, uy, uz = localDirectionToWorld(part.node, 0, 1, 0)
					ux, uy, uz = worldDirectionToLocal(getParent(part.node), ux, uy, uz)
					ux = ux * copyLocalDirectionPart.upScale[1]
					uy = uy * copyLocalDirectionPart.upScale[2]
					uz = uz * copyLocalDirectionPart.upScale[3]

					setDirection(copyLocalDirectionPart.node, dx, dy, dz, ux, uy, uz)

					if self.isServer then
						Cylindered.updateComponentJoints(self, copyLocalDirectionPart, placeComponents)
					end
				end
			end

			if self.isServer then
				Cylindered.updateComponentJoints(self, part, placeComponents)
				Cylindered.updateAttacherJoints(self, part)
				Cylindered.updateWheels(self, part)
			end

			Cylindered.updateWheels(self, part)
		end

		if updateDependentParts then
			for _, data in pairs(part.dependentPartData) do
				if self.currentUpdateDistance < data.maxUpdateDistance then
					local dependentPart = data.part
					local dependentIsActive = self:getIsMovingPartActive(dependentPart)

					if dependentIsActive or dependentPart.smoothedDirectionScale and dependentPart.smoothedDirectionScaleAlpha ~= 0 then
						Cylindered.updateMovingPart(self, dependentPart, placeComponents, updateDependentParts, dependentIsActive)
					end
				end
			end
		end

		part.isDirty = false
	else
		superFunc(self, part, placeComponents, updateDependentParts, isActive, updateSounds, ...)
	end
end)