MS_TO_KNOTS = 1.9504132 --米每秒换算节
MS_TO_KMH = 3.6--米每秒换算千米时
METER_TO_FEET = 3.2808 -- 米换算英尺

KG_TO_LBS = 2.204623 -- 公斤换算磅
Meter_TO_NM = 1/1852 --米换算海里
KM_TO_NM = 1000/1852 --KM换算海里
LogPrint_switch = 0 --0:disable 1:enable 2:only print_message_to_user
Render_debug_info = 0 --0: disable 1:enable 
function startup_print(...)
    print(...)
end
function DebugPrint(msg,time)
    if msg == true then
        msg = 'true'
    elseif msg == false then
        msg = 'false'
    elseif msg == nil then
        msg = 'nil'
    end
    print_message_to_user(msg,time)
end

function GetIndexOfValInTbl(tbl,val)
    if type(tbl)=="nil" then
        do return end
    end
	for i,v in ipairs(tbl) do
		if(val==tbl[i]) then
			return i
		end
	end
end

function GetTableLength(t)
    local res=0
    for k,v in pairs(t) do
        res=res+1
    end
    return res
end

function Limit(value,min,max)
    if value > max then
        return max
    elseif value < min then
        return min
    else
        return value
    end
end
function ConvertSwitch(num)
    if num == 0 then
        return 'OFF'
    else
        return 'ON'
    end
end

function Input2OutputArgument(input,in_range,out_range)
	local in_range_sum = math.abs(in_range[1])+math.abs(in_range[2])
	local out_range_sum = math.abs(out_range[1])+math.abs(out_range[2])
	input = Limit(input,in_range[1],in_range[2])
	return (input-in_range[1])/in_range_sum*out_range_sum+out_range[1]
end

function LoopPlayAnimal(inputVal,add,minVal,maxVal)
	local result = inputVal + add
    if type(maxVal) == "nil" then
        maxVal = 1
    end
	minVal = minVal or 0
	local range = maxVal - minVal
	if result>maxVal or result <minVal then
        local tmp = result/range
        if tmp<0 then
            result = minVal+result - math.ceil(tmp)*range
        else
            result = minVal+result - math.floor(tmp)*range
        end
	end
    return result
end

--dd：DD格式的坐标
--type: 坐标方向，1：维度,2：经度
function DD_to_DMS(dd,type)
    local d = math.floor(dd)
    local m = (dd-d)*60
    local s = (m-math.floor(m))*60
    local way
    if d > 0 then
        if type == 1 then
            way = 'N'
        else
            way = 'E'
        end
    else
        if type == 1 then
            way = 'S'
        else
            way = 'W'
        end
    end
    d = math.abs(d)
    if type==1 then
        return string.format('%s%02.0f-%02.0f-%05.2f',way,d,m,s)
    else
        return string.format('%s%03.0f-%02.0f-%05.2f',way,d,m,s)
    end

end

function DD_to_D_M_S(dd,type)
    local d = math.floor(dd)
    local mi = (dd - d) * 60
    local m = math.floor(mi)
    local s = (mi-m)*60
    local way
    if d > 0 then
        if type == 1 then
            way = 'N'
        else
            way = 'E'
        end
    else
        if type == 1 then
            way = 'S'
        else
            way = 'W'
        end
    end
    d = math.abs(d)
    return way,d,m,s
end

function DMS_to_DD(way,dd,mm,ss)
    local DD = dd + mm/60+ ss/3600
    if way=='S' or way == 'W' then
        DD = -DD
    end
    return DD
end

function SecondsTo_ddHHmmss(seconds)
    local day = math.floor(seconds / 86400)
    local hour = math.floor((seconds - day * 86400) / 3600)
    local minute = math.floor((seconds - day * 86400 - hour * 3600) / 60)
    local ss = math.floor(seconds - day * 86400 - hour * 3600 - minute * 60)
    return day,hour,minute,ss
end

function SecondsFormat_mmss(seconds)
    local day,hour,mm,ss = SecondsTo_ddHHmmss(seconds)
    return string.format('%02.0f:%02.0f',mm,ss)
end

function StringContains(str, item)
    local t = {}
    local l = {}
    local index = 0
    for i = 1, string.len(str) do
        table.insert(t, string.byte(string.sub(str, i, i)))
    end

    for i = 1, string.len(item) do
        table.insert(l, string.byte(string.sub(item, i, i)))
    end
    if #l > #t then
        return false
    end

    for k, v1 in pairs(t) do
        index = index + 1
        if v1 == l[1] then
            local iscontens = true
            for i = 1, #l do
                if t[index + i - 1] ~= l[i] then
                    iscontens = false
                end
            end
            if iscontens then
                return iscontens
            end
        end
    end
    return false
end

-- 代码来自JNelson
-- code from JNelson
function Recursively_print(table_to_print, max_depth, max_number_tables, filepath)
	local file = io.open(filepath, "a")
    if file == nil then
        return
    end
	file:write("Key,Value\n")

	local stack = {}

	table.insert(stack, {key = "start", value = table_to_print, level = 0})

	local total = 0

	local hash_table = {}

	hash_table[tostring(hash_table)] = 2
	hash_table[tostring(stack)] = 2

	local item = true
	while (item) do
		item = table.remove(stack)

		if (item == nil) then
			break
		end
		local key = item.key
		local value = item.value
		local level = item.level

		file:write(string.rep("\t", level)..tostring(key).." = "..tostring(value).."\n")

		local hash = hash_table[tostring(value)]
		local valid_table = (hash == nil or hash < 2)

		if (type(value) == "table" and valid_table) then
			for k,v in pairs(value) do
				if (v ~= nil and level <= max_depth and total < max_number_tables) then
					table.insert(stack, {key = k, value = v, level = level+1})
					if (type(v) == "table") then
						if (hash_table[tostring(v)] == nil) then
							hash_table[tostring(v)] = 1
						elseif (hash_table[tostring(v)] < 2) then
							hash_table[tostring(v)] = 2
						end
						total = total + 1
					end
				end
			end
		end

		if (getmetatable(value) and valid_table) then
			for k,v in pairs(getmetatable(value)) do
				if (v ~= nil and level <= max_depth and total < max_number_tables) then
					table.insert(stack, {key = k, value = v, level = level+1})
					if (type(v) == "table") then
						if (hash_table[tostring(v)] == nil) then
							hash_table[tostring(v)] = 1
						elseif (hash_table[tostring(v)] < 2) then
							hash_table[tostring(v)] = 2
						end
						total = total + 1
					end
				end
			end
		end
	end

	file:close()
end

function WriteLog(msg,path)
    local _path
    if path then
        _path = path
    else
        _path = 'Logs/'..get_aircraft_type()..'_cockpit.log'
    end
    local file = io.open(_path, "a")
    if file == nil then
        return
    end
    file:write(msg..'\n')
    file:close()
end

function OnGround()
    return Limit(get_aircraft_draw_argument_value(1)+get_aircraft_draw_argument_value(4)+get_aircraft_draw_argument_value(6),0,1)>0
end

function InRange(val,min,max)
    return val > min and val < max
end

function InEqRange(val,min,max)
    return val >= min and val <= max
end

-- Custom clamp function
function math.clamp(value, min, max)
	return math.max(min, math.min(max, value))
end

-- 辅助函数：确保指定目录存在（主要适用于 Windows 环境）
local function ensureDirectoryExists(dir)
    local ok, err, code = os.rename(dir, dir)
    if not ok then
        -- 目录不存在时，尝试创建目录（带双引号以处理路径中可能出现的空格）
        os.execute('mkdir "' .. dir .. '"')
    end
end

-- 定义全局变量保存生成的日志文件名，保证每个运行周期只创建一个日志文件
local logFileName = nil

-- 重构后的 Log_and_print 函数，利用 LockOn_Options.script_path 构造日志文件路径，
-- 文件名中增加当前时间后缀以区分每个启动周期的日志
function Log_and_print(...)
	if LogPrint_switch == 0 then return end
    -- 1. 收集所有传入的参数，转换为字符串后用空格拼接
    local args = { ... }
    local messageParts = {}
    for i, v in ipairs(args) do
        table.insert(messageParts, tostring(v))
    end
    local message = table.concat(messageParts, " ")
    -- 当 LogPrint_switch == 2 时，仅调用 print_message_to_user，不进行日志写入
    if LogPrint_switch == 2 then
        print_message_to_user(message)
        return
    end
    -- 2. 添加时间戳，生成日志内容（日志内容中附带时间戳，便于查看每条记录的写入时间）
    local timestamp = os.date("%Y-%m-%d %H:%M:%S")
    local logMessage = timestamp .. " - " .. message

    -- 3. 如果还未生成日志文件名，则构造日志目录与文件名（带有时间后缀）
    if logFileName == nil then
        -- 构造日志目录，位于模组脚本根目录下的 Logs 文件夹
        local logDir = LockOn_Options.script_path .. "../../../../../Logs/"
        ensureDirectoryExists(logDir)  -- 如果 Logs 目录不存在，则自动创建

        -- 利用当前时间生成后缀（格式：YYYYMMDD_HHMMSS），使日志文件名唯一
        local timeSuffix = os.date("%Y%m%d_%H%M%S")
        logFileName = logDir .. get_aircraft_type() .. "_cockpit_" .. timeSuffix .. ".log"
    end

    -- 4. 调用已有的 WriteLog 函数将带时间戳的日志内容写入日志文件
    WriteLog(logMessage, logFileName)

    -- 5. 调用 print_message_to_user 保持屏幕输出效果（不附加时间戳）
    print_message_to_user(message)
end

-------------------------------------------------------------
-- 四元数辅助函数定义
-------------------------------------------------------------
-- 将欧拉角（roll, pitch, yaw）转换成四元数（顺序：yaw, pitch, roll）  
-- 注意本实现采用右手系，且欧拉角均以弧度表示。
function Quat_from_euler(roll, pitch, yaw)
    local cy = math.cos(yaw * 0.5)
    local sy = math.sin(yaw * 0.5)
    local cp = math.cos(pitch * 0.5)
    local sp = math.sin(pitch * 0.5)
    local cr = math.cos(roll * 0.5)
    local sr = math.sin(roll * 0.5)
    return { cr * cp * cy + sr * sp * sy,   -- w
             sr * cp * cy - cr * sp * sy,   -- x
             cr * sp * cy + sr * cp * sy,   -- y
             cr * cp * sy - sr * sp * cy }  -- z
end
-- 输入 q = {w, x, y, z}，输出 roll,pitch,yaw（三个都是弧度）
function Quat_to_euler(q)
    local w, x, y, z = q[1], q[2], q[3], q[4]

    -- pitch = asin(2*(w*y - z*x)), 需要夹住 ±1
    local sinp = 2 * (w * y - z * x)
    local pitch
    if sinp >=  1 then
        pitch = math.pi * 0.5
    elseif sinp <= -1 then
        pitch = -math.pi * 0.5
    else
        pitch = math.asin(sinp)
    end

    -- roll  = atan2(2*(w*x + y*z), 1 - 2*(x^2 + y^2))
    local sinr = 2 * (w * x + y * z)
    local cosr = 1 - 2 * (x * x + y * y)
    local roll  = math.atan2(sinr, cosr)

    -- yaw   = atan2(2*(w*z + x*y), 1 - 2*(y^2 + z^2))
    local siny = 2 * (w * z + x * y)
    local cosy = 1 - 2 * (y * y + z * z)
    local yaw   = math.atan2(siny, cosy)

    return roll, pitch, yaw
end
-- 四元数乘法：q = q1 * q2
function Quat_mul(q1, q2)
    return {
        q1[1] * q2[1] - q1[2] * q2[2] - q1[3] * q2[3] - q1[4] * q2[4],
        q1[1] * q2[2] + q1[2] * q2[1] + q1[3] * q2[4] - q1[4] * q2[3],
        q1[1] * q2[3] - q1[2] * q2[4] + q1[3] * q2[1] + q1[4] * q2[2],
        q1[1] * q2[4] + q1[2] * q2[3] - q1[3] * q2[2] + q1[4] * q2[1]
    }
end

-- 四元数共轭，对于单位四元数，其共轭即为逆
function Quat_conjugate(q)
    return { q[1], -q[2], -q[3], -q[4] }
end

-- 利用四元数 q (单位) 旋转3D向量 v = {x, y, z}
function Rotate_vector_by_quaternion(v, q)
    -- 将 v 表示成纯虚四元数： vq = {0, v.x, v.y, v.z}
    local vq = { 0, v[1], v[2], v[3] }
    -- 计算 q * vq
    local qv = Quat_mul(q, vq)
    -- 再乘以 q 的共轭
    local q_conj = Quat_conjugate(q)
    local result = Quat_mul(qv, q_conj)
    -- 返回旋转后向量部分（result 的后三个分量）
    return { result[2], result[3], result[4] }
end
-- 将地面系 Pitch 与 Roll 还原为机体系 Pitch

-- --------------------------------------------------------------------
-- 在 utils.lua 已定义的 Quat_from_euler / Quat_conjugate / 
-- Rotate_vector_by_quaternion / Vector_to_angles 等基础函数之上，
-- 增加以下全局校正函数 AdjustBodyAngles：
-- --------------------------------------------------------------------
function AdjustBodyAngles(roll, pitch_ground, yaw_ground)
    -- 1. 构造世界→机体的完整姿态四元数
    local q_body = Quat_from_euler(roll, pitch_ground, yaw_ground)
    local q_conj = Quat_conjugate(q_body)

    -- 2. 把机体“前向”(1,0,0) 逆向旋转到机体坐标系
    local v_fwd = Rotate_vector_by_quaternion({1, 0, 0}, q_conj)

    -- 3. 从 v_fwd 提取机体系俯仰：Z 分量 = sin(pitch_body)
    local sinp = math.clamp(v_fwd[3], -1, 1)
    local pitch_body = math.asin(sinp)

    -- 4. 从 v_fwd 在水平面上的投影提取机体系偏航
    --    yaw_body = atan2(v_fwd.y, v_fwd.x)
    local yaw_body = math.atan2(v_fwd[2], v_fwd[1])

    return pitch_body, yaw_body
end


-- 将3D单位向量转换为球坐标（azimuth, elevation）, 单位：弧度  
-- azimuth = arctan2(y, x)  
-- elevation = arcsin(z)
function Vector_to_angles(v)
    local az = math.atan2(v[2], v[1])
    local el = math.asin(v[3] / math.sqrt(v[1]*v[1] + v[2]*v[2] + v[3]*v[3]))
    return az, el
end

function Quat_normalize(q)
    local mag = math.sqrt(q[1]*q[1] + q[2]*q[2] + q[3]*q[3] + q[4]*q[4])
    return { q[1]/mag, q[2]/mag, q[3]/mag, q[4]/mag }
end

function Vector_to_angles_safe(v)
    local az = math.atan2(v[2], v[1])
    local el = math.asin(math.max(-1, math.min(1, v[3] / math.sqrt(v[1]*v[1] + v[2]*v[2] + v[3]*v[3]))))
    return az, el
end
-- 将3D单位向量转换为屏幕投影视野中的角度:
-- 这里采用透视投影：水平角 = atan2(vy, vx)；垂直角 = atan2(vz, vx)
function Vector_to_view_angles(v)
    local view_az = math.atan2(v[2], v[1])
    local view_el = math.atan2(v[3], v[1])
    return view_az, view_el
end

function Normalize_angle(angle)
    -- 归一化到 -pi ~ +pi
    angle = angle % (2 * math.pi)
    if angle > math.pi then
        angle = angle - 2 * math.pi
    end
    return angle
end
-- 最多打印几级嵌套，防止过深
local MAX_DEPTH = 8

-- 递归遍历并打印表 t
-- indent: 缩进字符串，visited: 已访问表的集合，depth: 当前深度
function DumpTable(t, indent, visited, depth)
    indent  = indent  or ""
    visited = visited or {}
    depth   = depth   or 0

    -- 深度限制
    if depth > MAX_DEPTH then
        Log_and_print(indent .. "*depth limit reached*")
        return
    end

    -- 避免循环引用
    if visited[t] then
        Log_and_print(indent .. "*circular*")
        return
    end
    visited[t] = true

    for k, v in pairs(t) do
        local prefix = indent .. tostring(k) .. " = "
        if type(v) == "table" then
            Log_and_print(prefix .. "table")
            -- 进入下一层
            DumpTable(v, indent .. "  ", visited, depth + 1)
        else
            Log_and_print(prefix .. tostring(v))
        end
    end
end

-- 全局映射表：cmd（数字） → “表名.键名”  
local _cmd_name_map = nil

-- 初始化映射表，只执行一次  
local function init_cmd_name_map()
    _cmd_name_map = {}
    for tbl_name, tbl in pairs(_G) do
        if type(tbl) == "table" then
            for key, val in pairs(tbl) do
                if type(val) == "number" then
                    _cmd_name_map[val] = tbl_name.."."..key
                end
            end
        end
    end
end

-- 获取命令名函数  
function Get_command_name(cmd)
    -- 第一次调用时构建映射表  
    if not _cmd_name_map then
        init_cmd_name_map()
    end
    -- 返回映射名称，找不到则 tostring(cmd)  
    if _cmd_name_map == nil then
        return tostring(cmd)
    end
    return _cmd_name_map[cmd]
end
--DebugPrint('XX系统：'..Get_command_name(command)..'..........'..value)


--print(Input2OutputArgument(0.9,{-1,1},{-180,180}))