var renderClass = "jp.ngt.rtm.render.MachinePartsRenderer";
importPackage(Packages.org.lwjgl.opengl);
importPackage(Packages.jp.ngt.ngtlib.renderer);
importPackage(Packages.jp.ngt.rtm.render);

importPackage(Packages.net.minecraft.tileentity);
importPackage(Packages.jp.ngt.ngtlib.io);
importPackage(Packages.jp.ngt.ngtlib.util);
importPackage(Packages.net.minecraft.command.server);
importPackage(Packages.net.minecraft.init);
importClass(java.util.HashMap);
importPackage(Packages.jp.ngt.rtm.electric);

importPackage(Packages.jp.ngt.rtm);
importPackage(Packages.jp.ngt.rtm.modelpack);
importPackage(Packages.net.minecraft.util.math);  // BlockPos (1.12.2)

// --- Java NIO 導入 (用於優化檔案讀取) ---
var Files = Java.type("java.nio.file.Files");
var Paths = Java.type("java.nio.file.Paths");
var StandardCharsets = Java.type("java.nio.charset.StandardCharsets");
var FileTime = Java.type("java.nio.file.attribute.FileTime");
var NBTTagCompound = Java.type("net.minecraft.nbt.NBTTagCompound");
var Base64 = Java.type("java.util.Base64");

var tessellator = NGTTessellator.instance;
var DEFAULT_LOOKUP_FILE = "TR_TIDS_lookup.json";
var LookupCache = {
    path: null,
    lastModified: null,
    data: null
};

// --- 全域變數 (改為 HashMap 緩存，避免變數污染) ---
var EntityCache = new HashMap(); // 用於儲存每個方塊的獨立數據
// 以下舊的全域變數保留定義但不使用，以防有其他依賴，實際上已被 Cache 取代
var ErrorData = new HashMap(); 
var lastFilePath = null; 
var lastModifiedTime = null; 
var Timetable = null; 

function init(par1, par2) {
    base = renderer.registerParts(new Parts("body", "time_led_0"));
    trianLed = renderer.registerParts(new Parts("train_led"));
    timeColon = renderer.registerParts(new Parts("time_led_1"));
    mat2 = renderer.registerParts(new Parts("mat2"));

    hour_tens = registerDigitParts("hour_tens");
    hour_units = registerDigitParts("hour_units");
    minute_tens = registerDigitParts("minute_tens");
    minute_units = registerDigitParts("minute_units");

    approachingLight = [
        [renderer.registerParts(new Parts("approachingLight_1_0")), renderer.registerParts(new Parts("approachingLight_1_1"))],
        [renderer.registerParts(new Parts("approachingLight_2_0")), renderer.registerParts(new Parts("approachingLight_2_1"))],
        [renderer.registerParts(new Parts("approachingLight_3_0")), renderer.registerParts(new Parts("approachingLight_3_1"))]
    ];

    platforms = {};
    var pList = ["1", "1a", "1b", "2", "2a", "2b", "3", "3a", "3b", "4", "4a", "4b"];
    for (var i in pList) {
        platforms[pList[i]] = renderer.registerParts(new Parts("Plat_" + pList[i]));
    }

    displayBoardPorperty = {
        size : renderer.getModelName().indexOf("small") !== -1 ? "Small" : "Large",
        side : renderer.getModelName().indexOf("_L") !== -1 ? "Left" : "Right",
        doubleSide : renderer.getModelName().indexOf("_Double") !== -1 ? true : false
    };
}

// 輔助註冊函數
function registerDigitParts(name) {
    var arr = [];
    for (var i = 0; i <= 9; i++) {
        arr.push(renderer.registerParts(new Parts(name + "_" + i)));
    }
    arr.push(renderer.registerParts(new Parts(name))); 
    return arr;
}

// 取得該 Entity 的專屬緩存
function getCache(entity) {
    var data = EntityCache.get(entity);
    if (data === null) {
        data = {
            lastCheckTime: 0,
            settings: null,         // JSON 解析後的設定
            timetable: [],          // JSON 解析後的時刻表
            timetableTimes: [],
            lastFilePath: null,     // 上次讀取的檔案路徑
            lastFileModified: null, // 上次檔案修改時間
            commandStr: null,       // 上次讀取的指令字串
            lookupPath: null,
            lastTimeRenderUpdate: 0,
            digits: null,
            digitsMC: null,
            totalSec: 0,
            visibleRows: [],
            visibleRowsLastSec: -1,
            visibleRowsDelayTime: -1,
            // 邏輯狀態
            isError: false,
            errorInfo: "",
            errorDrawn: false,
            
            platform: "1",
            timeZone: 0,
            isRealTime: false,
            
            approachingState: [false, false, false],
            isApproaching: false,
            approachLevel: [0, 0, 0],
            
            isDelaying: false,
            delayTime: 0
        };
        EntityCache.put(entity, data);
    }
    return data;
}

function updateTimeRenderCache(cache, currentTime) {
    if (cache.digits === null || currentTime - cache.lastTimeRenderUpdate > 200) {
        var MCTime = (renderer.getMCTime() + 6000) % 24000 * 3.6;
        cache.digits = getTimeDigits(cache.timeZone);
        cache.digitsMC = convertSecondsToHMS(MCTime);
        cache.totalSec = cache.isRealTime ? cache.digits.totalSec : MCTime;
        cache.lastTimeRenderUpdate = currentTime;
    }
}

function clearTimetable(cache) {
    cache.timetable = [];
    cache.timetableTimes = [];
    cache.visibleRows = [];
    cache.visibleRowsLastSec = -1;
    cache.visibleRowsDelayTime = -1;
}

function setTimetable(cache, list) {
    clearTimetable(cache);
    if (!list) return;

    for (var i = 0; i < list.length; i++) {
        var entry = list[i];
        if (!isValidTimetableEntry(entry)) continue;

        cache.timetable.push(entry);
        cache.timetableTimes.push(decordTime(entry[4]));
    }
}

function updateVisibleRowsCache(cache, totalSec) {
    var totalSecInt = Math.floor(totalSec);
    if (cache.visibleRowsLastSec === totalSecInt && cache.visibleRowsDelayTime === cache.delayTime) return;

    cache.visibleRows = [];
    cache.visibleRowsLastSec = totalSecInt;
    cache.visibleRowsDelayTime = cache.delayTime;

    if (!cache.timetable || cache.timetable.length <= 0) return;

    var offsetTime = 20 * 3600;
    var currentOffsetSec = (totalSec + offsetTime) % 86400;

    for (var tableIdx = 0; tableIdx < cache.timetable.length; tableIdx++) {
        var entry = cache.timetable[tableIdx];
        var trainTime = cache.timetableTimes[tableIdx];
        var delaySec = cache.isDelaying ? cache.delayTime : 0;
        var trainOffsetSec = (trainTime + delaySec + offsetTime) % 86400;

        var isFutureTrain = (trainOffsetSec >= currentOffsetSec);

        if (isFutureTrain) {
            cache.visibleRows.push({
                entry: entry,
                isDelayed: cache.isDelaying,
                delayMin: Math.floor(delaySec / 60),
                trainTime: trainTime
            });

            if (cache.visibleRows.length >= 3) break;
        }
    }
}

function render(entity, pass, par3) {
    if (entity === null) return;

    // 1. 取得緩存
    var cache = getCache(entity);

    // 2. 邏輯更新 (每 1000ms 執行一次，避免每幀 I/O 造成卡頓)
    var currentTime = java.lang.System.currentTimeMillis();
    if (currentTime - cache.lastCheckTime > 1000) {
        updateEntityLogic(entity, cache);
        cache.lastCheckTime = currentTime;
    }

    // 3. 渲染處理 (每幀執行，但只讀取緩存數據)
    if (0 <= pass && pass <= 2) {
        GL11.glPushMatrix();
        base.render(renderer);
        trianLed.render(renderer);
        GL11.glPopMatrix();

        // 錯誤顯示邏輯
        if (cache.isError) {
            if (!cache.errorDrawn) {
                var player = NGTUtil.getClientPlayer();
                var pos = [Math.floor(entity.field_145851_c), Math.floor(entity.field_145848_d), Math.floor(entity.field_145849_e)];
                NGTLog.sendChatMessage(player, "§b[EditableDepSign] §c錯誤發生");
                NGTLog.sendChatMessage(player, "Pos:" + pos.join(", "));
                NGTLog.sendChatMessage(player, "Info:§e" + cache.errorInfo);
                cache.errorDrawn = true;
            }
        } else {
            cache.errorDrawn = false;
        }

        if (cache.settings === null) return;

        try {
            var PlatSelect = cache.platform;

            updateTimeRenderCache(cache, currentTime);
            var digits = cache.digits;
            var showDigits = cache.isRealTime ? cache.digits : cache.digitsMC;
            var totalSec = cache.totalSec;

            GL11.glPushMatrix();
            if (platforms[PlatSelect]) platforms[PlatSelect].render(renderer);
            
            // 渲染時間
            hour_tens[showDigits.hourTens].render(renderer);
            hour_units[showDigits.hourOnes].render(renderer);
            minute_tens[showDigits.minuteTens].render(renderer);
            minute_units[showDigits.minuteOnes].render(renderer);

            if (digits.secondOnes % 2 == 0) timeColon.render(renderer);
            
            if (cache.isApproaching) {
                var flash = (digits.milliseconds > 500) ? 1 : 0;
                if (cache.approachingState[0]) approachingLight[0][flash].render(renderer);
                if (cache.approachingState[1]) approachingLight[1][flash].render(renderer);
                if (cache.approachingState[2]) approachingLight[2][flash].render(renderer);
            }
            GL11.glPopMatrix();

            // 渲染時刻表列表
            GL11.glPushMatrix();
            mat2.render(renderer);
            
            var matId = renderer.currentMatId;

            if (matId == 1) {
                updateVisibleRowsCache(cache, totalSec);
                for (var rowCount = 0; rowCount < cache.visibleRows.length; rowCount++) {
                    var row = cache.visibleRows[rowCount];
                    panelRender(cache.settings, row.entry, row.isDelayed, row.delayMin, row.trainTime, totalSec, rowCount, matId, renderer, digits);
                }
            }
            GL11.glPopMatrix();

        } catch (e) {
            cache.isError = true;
            cache.errorInfo = "Render Error: " + e;
        }
    }
}

// --- 邏輯更新函數 ---
function toCommandString(value) {
    if (value === null || value === undefined) return null;
    var command = String(value);
    return command === "" ? null : command;
}

function decodeSyncedCommand(value) {
    if (value === null || value === undefined || value === "") return null;
    try {
        var bytes = Base64.getDecoder().decode(String(value));
        return toCommandString(new java.lang.String(bytes, StandardCharsets.UTF_8));
    } catch (e) {
        return null;
    }
}

function readSyncedCommand(entity) {
    try {
        var dataMap = entity.getResourceState().getDataMap();
        return decodeSyncedCommand(dataMap.getString("trapidsCommandB64"));
    } catch (e) {
        return null;
    }
}

function readCommandFromNBT(nbt) {
    var command = null;
    try { command = toCommandString(nbt.func_74779_i("Command")); } catch (e) {}
    if (command !== null) return command;
    try { command = toCommandString(nbt.getString("Command")); } catch (e) {}
    return command;
}

function readCommandBlockCommand(commandBlock) {
    if (commandBlock === null) return null;

    var logic = null;
    try { logic = commandBlock.func_145993_a(); } catch (e) {}
    if (logic === null) return null;

    var command = null;
    try { command = toCommandString(logic.func_145753_i()); } catch (e) {}
    if (command !== null) return command;

    try {
        var version = RTMCore.VERSION;
        if (version.indexOf("1.7.10") != -1){
            command = NGTUtil.getField(CommandBlockLogic.class, logic, ["Command", "field_145763_e"]);
        }else{
            command = NGTUtil.getField(CommandBlockBaseLogic.class, logic, ["Command", "field_145763_e"]);
        }
        command = toCommandString(command);
    } catch (e) {
        command = null;
    }
    if (command !== null) return command;

    try {
        var nbt = new NBTTagCompound();
        try {
            logic.func_145759_b(nbt);
        } catch (e1) {
            commandBlock.func_145841_b(nbt);
        }
        command = readCommandFromNBT(nbt);
    } catch (e) {
        command = null;
    }
    return command;
}

function updateEntityLogic(entity, cache) {
    try {
        // 1. 獲取指令方塊並解析設定
        var commandStr = readSyncedCommand(entity);
        if (commandStr === null || commandStr === "") {
            var commandBlock = searchTileEntity(entity, TileEntityCommandBlock);
            commandStr = readCommandBlockCommand(commandBlock);
        }

        if (commandStr === null || commandStr === "") {
            cache.settings = null;
            return;
        }

        if (commandStr !== cache.commandStr) {
            cache.commandStr = commandStr;
            try {
                cache.settings = JSON.parse(commandStr);
                cache.isError = false;
                
                if (cache.settings.setting) {
                    cache.timeZone = cache.settings.setting.timeZone !== undefined ? parseInt(cache.settings.setting.timeZone) : 0;
                    cache.isRealTime = !!cache.settings.setting.realTime;
                    var p = cache.settings.setting.platform || "1";
                    cache.platform = (typeof(p) === "string") ? p.toLowerCase() : "1";
                    cache.lookupPath = getLookupPath(cache.settings.setting);
                    cache.lastTimeRenderUpdate = 0;
                    cache.visibleRowsLastSec = -1;
                }
            } catch (e) {
                cache.isError = true;
                cache.errorInfo = "JSON Parse Error";
                return;
            }
        }

        if (!cache.settings || !cache.settings.setting) {
            cache.isError = true;
            cache.errorInfo = "No 'setting' in JSON";
            return;
        }

        cache.lookupPath = getLookupPath(cache.settings.setting);
        if (!ensureLookupConfig(cache.lookupPath)) {
            cache.isError = true;
            cache.errorInfo = "Lookup Load Error";
            return;
        }

        // 2. 檔案讀取
        var filePath = cache.settings.setting.filePath;
        if (filePath) {
            try {
                var path = Paths.get(filePath);
                if (Files.exists(path)) {
                    var currentModified = Files.getLastModifiedTime(path);
                    
                    if (cache.lastFilePath !== filePath || 
                        cache.lastFileModified === null || 
                        currentModified.compareTo(cache.lastFileModified) > 0) {
                        
                        var jsonString = new java.lang.String(Files.readAllBytes(path), StandardCharsets.UTF_8);
                        var jsonObject = JSON.parse(jsonString);
                        setTimetable(cache, jsonObject.list || []);
                        
                        cache.lastFilePath = filePath;
                        cache.lastFileModified = currentModified;
                        cache.isError = false;
                    }
                } else {
                    clearTimetable(cache);
                    cache.lastFilePath = filePath;
                    cache.lastFileModified = null;
                    cache.isError = true;
                    cache.errorInfo = "File Not Found";
                }
            } catch (e) {
                clearTimetable(cache);
                cache.isError = true;
                cache.errorInfo = "File Load Error";
            }
        } else {
            clearTimetable(cache);
            cache.lastFilePath = null;
            cache.lastFileModified = null;
            cache.isError = false;
        }

        // 3. 搜尋周圍方塊
        var BlockData = Blocks.field_150399_cn;
        var appRes = searchBlockAndMetaTriple(entity, BlockData, null);
        cache.approachingState = [
            (appRes[0][0] !== null && appRes[0][1] >= 1),
            (appRes[1][0] !== null && appRes[1][1] >= 1),
            (appRes[2][0] !== null && appRes[2][1] >= 1)
        ];
        cache.isApproaching = cache.approachingState[0] || cache.approachingState[1] || cache.approachingState[2];

        var delayBlockData = Blocks.field_150325_L;
        var delayRes = searchBlockAndMeta(entity, delayBlockData, null);
        var delayMeta = (delayRes[0] !== null) ? delayRes[1] : 0;
        cache.isDelaying = (delayRes[0] !== null && delayMeta > 0);
        
        if (cache.isDelaying) {
            cache.delayTime = delayMeta * 5 * 60;
        } else {
            cache.delayTime = 0;
        }

    } catch (e) {
        cache.isError = true;
        cache.errorInfo = "Update Logic Error: " + e;
    }
}

function decordTime(time) {
    var t = String(time).split(":");
    if (t.length !== 3) return 0;
    var hh = parseInt(t[0], 10);
    var mm = parseInt(t[1], 10);
    var ss = parseInt(t[2], 10);
    if (isNaN(hh) || isNaN(mm) || isNaN(ss)) return 0;
    return (hh * 3600 + mm * 60 + ss) % 86400;
}

function isValidTimetableEntry(entry) {
    if (!entry || entry.length < 6) return false;
    if (entry[0] === null || entry[1] === null || entry[2] === null || entry[3] === null || entry[4] === null || entry[5] === null) return false;

    var t = String(entry[4]).split(":");
    if (t.length !== 3) return false;
    var hh = parseInt(t[0], 10);
    var mm = parseInt(t[1], 10);
    var ss = parseInt(t[2], 10);
    return !(isNaN(hh) || isNaN(mm) || isNaN(ss));
}

// 注意：這裡最後一個參數改為 digits (時間物件)，以避免在內部重複計算
function panelRender(cb, Timetable, isDelaying, delayTime, trainTime, totalSec, rowCount, matId, renderer, digits) {
    if (matId !== 1) return;
}

function searchBlockAndMeta(entity, blockType, metadata) {
    var version = RTMCore.VERSION;
    var searchMaxCount = 32;
    var world = entity.func_145831_w();

    if (version.indexOf("1.7.10") != -1) {
        var x = Math.floor(entity.field_145851_c);
        var y = Math.floor(entity.field_145848_d);
        var z = Math.floor(entity.field_145849_e);

        for (var i = 1; i <= searchMaxCount; i++) {
            var block = world.func_147439_a(x, y - i, z);
            if (block === blockType) {
                if (metadata !== null) {
                    if (world.func_72805_g(x, y - i, z) === metadata) return [block, world.func_72805_g(x, y - i, z), i];
                }
                else return [block, world.func_72805_g(x, y - i, z), i];
            }
        }
    } else {
        var blockPos = entity.func_174877_v();
        var x = Math.floor(blockPos.func_177958_n());
        var y = Math.floor(blockPos.func_177956_o());
        var z = Math.floor(blockPos.func_177952_p());

        for (var i = 1; i <= searchMaxCount; i++) {
            var searchBlockPos = new BlockPos(x, y - i, z);
            var iBlockState = world.func_180495_p(searchBlockPos);
            var block = iBlockState.func_177230_c();
            
            if (block === blockType) {
                if (blockType == Blocks.field_150359_w || blockType == Blocks.field_150410_aZ || blockType == Blocks.field_150405_ch){
                    return [block, 0, i];
                }else{
                    var meta = block.func_176201_c(iBlockState);
                    if (metadata !== null) {
                        if (meta === metadata) return [block, meta, i];
                    }
                    else return [block, meta, i];
                }
            }
        }
    }
    return [null, 0, 35];
}

function searchBlockAndMetaTriple(entity, blockType, metadata)
{
    var version = RTMCore.VERSION;
    var searchMaxCount = 32;
    var world = entity.func_145831_w();
    var Return1 = null, Return2 = null, Return3 = null;

    if (version.indexOf("1.7.10") != -1) {
        var x = Math.floor(entity.field_145851_c);
        var y = Math.floor(entity.field_145848_d);
        var z = Math.floor(entity.field_145849_e);

        for (var i = 1; i <= searchMaxCount; i++){
            var block = world.func_147439_a(x, y - i, z);

            if (block === blockType) {
                if (metadata !== null) {
                    if (world.func_72805_g(x, y - i, z) === metadata)
                    {
                        if(Return1 && Return2){
                            Return3 = [block, world.func_72805_g(x, y - i, z), i];
                            break;
                        }else if (Return1){
                            Return2 = [block, world.func_72805_g(x, y - i, z), i];
                        }else {
                            Return1 = [block, world.func_72805_g(x, y - i, z), i];
                        }
                    }
                }else{
                    if(Return1 && Return2){
                        Return3 = [block, world.func_72805_g(x, y - i, z), i];
                        break;
                    }else if (Return1){
                        Return2 = [block, world.func_72805_g(x, y - i, z), i];
                    }else {
                        Return1 = [block, world.func_72805_g(x, y - i, z), i];
                    }
                }
            }
        }
    }else{
        var blockPos = entity.func_174877_v();
        var x = Math.floor(blockPos.func_177958_n());
        var y = Math.floor(blockPos.func_177956_o());
        var z = Math.floor(blockPos.func_177952_p());

        for (var i = 1; i <= searchMaxCount; i++){
            var searchBlockPos = new BlockPos(x, y - i, z);
            var iBlockState = world.func_180495_p(searchBlockPos);
            var block = iBlockState.func_177230_c();

            if (block === blockType){
                if (blockType == Blocks.field_150359_w || blockType == Blocks.field_150410_aZ || blockType == Blocks.field_150405_ch){
                    if(Return1 && Return2){
                        Return3 = [block, 0, i];
                        break;
                    }else if (Return1){
                        Return2 = [block, 0, i];
                    }else {
                        Return1 = [block, 0, i];
                    }
                }else{
                    var meta = block.func_176201_c(iBlockState);
                    if (metadata !== null) {
                        if (meta === metadata){
                            if(Return1 && Return2){
                                Return3 = [block, meta, i];
                                break;
                            }else if (Return1){
                                Return2 = [block, meta, i];
                            }else {
                                Return1 = [block, meta, i];
                            }
                        }
                    }else{
                        if(Return1 && Return2){
                            Return3 = [block, meta, i];
                            break;
                        }else if (Return1){
                            Return2 = [block, meta, i];
                        }else {
                            Return1 = [block, meta, i];
                        }
                    }
                }
            }
        }
    }
    if (!Return3){Return3 = [null, 0, 35];}
    if (!Return2){Return2 = [null, 0, 35];}
    if (!Return1){Return1 = [null, 0, 35];}
    return [Return1, Return2, Return3];
}

function MCVersionChecker() {
    var varsion = RTMCore.VERSION;
    if (varsion.indexOf("1.7.10") >= 0) return "1.7.10";
    else if (varsion.indexOf("2.0") >= 0) return "1.8.9";
    else if (varsion.indexOf("2.1") >= 0) return "1.9.4";
    else if (varsion.indexOf("2.2") >= 0) return "1.10.2";
    else if (varsion.indexOf("2.4") >= 0) return "1.12.2";
    else return "unknown";
}

function searchTileEntity(entity, typeName) {
    if(entity === null) return null;
    var searchMaxCount = 32;
    var world = entity.func_145831_w();
    if(world === null) return null;
    for (var i = 1; i <= searchMaxCount; i++) {
        var version = MCVersionChecker();
        if (version === "1.7.10"){
            var x = Math.floor(entity.field_145851_c);
            var y = Math.floor(entity.field_145848_d);
            var z = Math.floor(entity.field_145849_e);
            var block = world.func_147438_o(x, y - i, z);
            if (block instanceof typeName) return block;
        }else{
            var cmdPos = entity.func_174877_v();
            var x = Math.floor(cmdPos.func_177958_n());
            var y = Math.floor(cmdPos.func_177956_o());
            var z = Math.floor(cmdPos.func_177952_p());
            var cmdPosA = new BlockPos(x, y - i, z);
            var block = world.func_175625_s(cmdPosA);
            if (block instanceof typeName) return block;
        }
        
    }
    return null;
}

function getTimeDigits(timezoneOffset) {
    var now = new Date();
    var systemOffsetInMs = now.getTimezoneOffset() * 60 * 1000;
    var utcTimeInMs = now.getTime() + systemOffsetInMs;
    var targetTimeInMs = utcTimeInMs + (timezoneOffset * 60 * 60 * 1000);
    var targetDate = new Date(targetTimeInMs);

    var hours   = targetDate.getHours();
    var minutes = targetDate.getMinutes();
    var seconds = targetDate.getSeconds();
    var milliseconds = targetDate.getMilliseconds();
    var totalSec = hours * 3600 + minutes * 60 + seconds;

    var hourTens = Math.floor(hours / 10);
    var hourOnes = hours % 10;
    var minuteTens = Math.floor(minutes / 10);
    var minuteOnes = minutes % 10;
    var secondTens = Math.floor(seconds / 10);
    var secondOnes = seconds % 10;

    return {
        hourTens:      hourTens,
        hourOnes:      hourOnes,
        minuteTens:    minuteTens,
        minuteOnes:    minuteOnes,
        secondTens:    secondTens,
        secondOnes:    secondOnes,
        milliseconds:  milliseconds,
        totalSec: totalSec
    };
}

function convertSecondsToHMS(totalSeconds) {
    var hours = Math.floor(totalSeconds / 3600);
    var remainSecAfterHours = totalSeconds % 3600;
    var minutes = Math.floor(remainSecAfterHours / 60);
    var seconds = remainSecAfterHours % 60;

    var hourTens = Math.floor(hours / 10);
    var hourOnes = hours % 10;
    var minuteTens = Math.floor(minutes / 10);
    var minuteOnes = minutes % 10;
    var secondTens = Math.floor(seconds / 10);
    var secondOnes = seconds % 10;

    return {
        hourTens:   hourTens,
        hourOnes:   hourOnes,
        minuteTens: minuteTens,
        minuteOnes: minuteOnes,
        secondTens: secondTens,
        secondOnes: secondOnes
    };
}


function getLookupPath(setting) {
    if (setting && setting.lookupPath) return String(setting.lookupPath);
    return DEFAULT_LOOKUP_FILE;
}

function AddingQuads(signPos, signL, u1, u2, signLength, signStartv, signEndv) {
    var v1 = signStartv + ((signEndv - signStartv) / (signLength) * (1 + signPos));
    var v2 = signStartv + ((signEndv - signStartv) / (signLength) * signPos);

    tessellator.addVertexWithUV(signL[0], signL[1], signL[2], u2, v1);
    tessellator.addVertexWithUV(signL[3], signL[4], signL[5], u2, v2);
    tessellator.addVertexWithUV(signL[6], signL[7], signL[8], u1, v2);
    tessellator.addVertexWithUV(signL[9], signL[10], signL[11], u1, v1);
}

function validateLookupConfig(config) {
    return config !== null && config !== undefined &&
        config.Line !== undefined &&
        config.Destination !== undefined &&
        config.Type !== undefined;
}

function ensureLookupConfig(lookupPath) {
    if (!lookupPath) return false;
    try {
        var path = Paths.get(lookupPath);
        if (!Files.exists(path)) {
            var fallbackPath = DEFAULT_LOOKUP_FILE;
            if (lookupPath !== fallbackPath && Files.exists(Paths.get(fallbackPath))) {
                lookupPath = fallbackPath;
                path = Paths.get(lookupPath);
            } else {
                LookupCache.path = lookupPath;
                LookupCache.lastModified = null;
                LookupCache.data = null;
                return false;
            }
        }

        var currentModified = Files.getLastModifiedTime(path);
        if (LookupCache.data !== null &&
            LookupCache.path === lookupPath &&
            LookupCache.lastModified !== null &&
            currentModified.compareTo(LookupCache.lastModified) <= 0) {
            return true;
        }

        var jsonString = new java.lang.String(Files.readAllBytes(path), StandardCharsets.UTF_8);
        var jsonObject = JSON.parse(jsonString);
        if (!validateLookupConfig(jsonObject)) {
            LookupCache.path = lookupPath;
            LookupCache.lastModified = currentModified;
            LookupCache.data = null;
            return false;
        }

        LookupCache.path = lookupPath;
        LookupCache.lastModified = currentModified;
        LookupCache.data = jsonObject;
        return true;
    } catch (e) {
        LookupCache.path = lookupPath;
        LookupCache.lastModified = null;
        LookupCache.data = null;
        return false;
    }
}

function returnNumber(str, type) {
    if (!str) {
      return null;
    }

    var mapConfig = LookupCache.data;
    if (mapConfig === null || mapConfig === undefined) {
      return null;
    }

    if (mapConfig[type] && mapConfig[type].hasOwnProperty(str)) {
      return mapConfig[type][str];
    } else {
      return null;
    }
}

function parseAndRoundTime(timeStr) {
    if (!timeStr) return null; // 防止空值報錯
    var parts = String(timeStr).split(":");
    if (parts.length !== 3) {
        return null;
    }
    var hh = parseInt(parts[0], 10);
    var mm = parseInt(parts[1], 10);
    var ss = parseInt(parts[2], 10);
    if (isNaN(hh) || isNaN(mm) || isNaN(ss)) {
        return null;
    }
    if (ss >= 30) {
        mm++;
        if (mm >= 60) {
            mm = 0;
            hh++;
            if (hh >= 24) {
                hh = 0;
            }
        }
    }
    var hhStr = (hh < 10 ? "0" : "") + hh;
    var mmStr = (mm < 10 ? "0" : "") + mm;
    var hhDigits = hhStr.split("").map(function (d) { return parseInt(d, 10); });
    var mmDigits = mmStr.split("").map(function (d) { return parseInt(d, 10); });
    var result = hhDigits.concat([10], mmDigits);
    return result;
}

function parseAndRoundSeconds(totalSeconds) {
    if (totalSeconds === null || totalSeconds === undefined) return null;
    var secondsInDay = 24 * 3600;
    var seconds = Math.floor(totalSeconds % secondsInDay);
    if (seconds < 0) seconds += secondsInDay;

    var hh = Math.floor(seconds / 3600);
    var mm = Math.floor((seconds % 3600) / 60);
    var ss = seconds % 60;

    if (ss >= 30) {
        mm++;
        if (mm >= 60) {
            mm = 0;
            hh++;
            if (hh >= 24) hh = 0;
        }
    }

    var hhStr = (hh < 10 ? "0" : "") + hh;
    var mmStr = (mm < 10 ? "0" : "") + mm;
    var hhDigits = hhStr.split("").map(function (d) { return parseInt(d, 10); });
    var mmDigits = mmStr.split("").map(function (d) { return parseInt(d, 10); });
    return hhDigits.concat([10], mmDigits);
}

function loadTimetable(filePath) {
    return true; 
}
