importPackage(java.awt);
importPackage(java.awt.geom);
importPackage(java.awt.image);

importPackage(java.lang);
importPackage(java.util.concurrent);
importPackage(java.util.concurrent.locks);
importPackage(java.io);
importPackage(java.util);
importPackage(javax.imageio);
importPackage(javax.imageio.stream);

include("ikisaki.js");

try { javax.imageio.ImageIO.setUseCache(false); } catch (e) { }

var GLOBAL = GLOBAL || {};
function loadAssetsOnce() {
    if (GLOBAL.loaded) return;
    GLOBAL.font = Resources.readFont(Resources.id("mtr:font/notosanscjkjp-medium.otf"));
    GLOBAL.loaded = true;
}

loadAssetsOnce();

// =====================================================
// create
// =====================================================
function create(ctx, state, train) {
    state.dh = [];
    state.dhLocks = [];
    for (let i = 0; i < train.trainCars(); i++) {
        state.dh[i] = dhBase.create();
        state.dhLocks[i] = new ReentrantLock();
    }

    state.lastPathInfo = null;
    state.lastNextIndex = -1;
    state.lastDoorOpen = false;
    state.lastStationCenters = { 'left': [], 'right': [] };

    state.cachedAllStations = null;
    state.cachedRestStations = null;
    state.cachedPathSize = -1;
    state.cachedRailIndex = -1;

    state.modelRate = new RateLimit(0.1);

    state.ikisakiRate = new RateLimit(2.5);

    state.font = GLOBAL.font;

    // 3000形 独自要素 (3Dモデルロード)
    state.baseModel = ModelManager.loadPartedRawModel(Resources.manager(), Resources.id(modelPath), null);
    state.masconModel = state.baseModel.get("mascon");
    state.exlightOffModel = state.baseModel.get("front_exlight_off");
    state.exlightModel = state.baseModel.get("front_exlight");
    state.doorLLampModel = state.baseModel.get("doorL_lamp_in");
    state.doorRLampModel = state.baseModel.get("doorR_lamp_in");
    state.doorLLampOffModel = state.baseModel.get("doorL_lamp_in_off");
    state.doorRLampOffModel = state.baseModel.get("doorR_lamp_in_off");

    state.masconModel.setAllRenderType("interior");
    if (state.exlightModel) {
        state.exlightModel.setAllRenderType("light");
    }
    state.doorLLampModel.setAllRenderType("light");
    state.doorRLampModel.setAllRenderType("light");
    state.doorLLampOffModel.setAllRenderType("interior");
    state.doorRLampOffModel.setAllRenderType("interior");

    state.dmhMascon = new DynamicModelHolder();
    state.dmhMascon1 = new DynamicModelHolder();
    if (state.exlightModel) {
        state.dmhExlight = new DynamicModelHolder();
        state.dmhExlight1 = new DynamicModelHolder();
    }
    state.dmhDoorLLamp = [];
    state.dmhDoorRLamp = [];
    for (let i = 0; i < train.trainCars(); i++) {
        state.dmhDoorLLamp[i] = new DynamicModelHolder();
        state.dmhDoorRLamp[i] = new DynamicModelHolder();
        state.dmhDoorLLamp[i].uploadLater(state.doorLLampOffModel);
        state.dmhDoorRLamp[i].uploadLater(state.doorRLampOffModel);
    }
    state.dmhMascon.uploadLater(state.masconModel);
    state.dmhMascon1.uploadLater(state.masconModel);
    if (state.exlightModel) {
        state.dmhExlight.uploadLater(state.exlightOffModel);
        state.dmhExlight1.uploadLater(state.exlightOffModel);
    }

    state.lastNotch = null;
    state.lastRouteTypeJpn = null;
    state.lastIsReversed = null;
    state.lastDoorLState = [];
    state.lastDoorRState = [];
    for (let i = 0; i < train.trainCars(); i++) {
        state.lastDoorLState[i] = false;
        state.lastDoorRState[i] = false;
    }

    if (state.exlightModel) {
        state.exlightModelRotated = state.exlightModel.copy();
        state.exlightModelRotated.applyRotation(new Vector3f(0, 1, 0), 180);
        state.exlightOffModelRotated = state.exlightOffModel.copy();
        state.exlightOffModelRotated.applyRotation(new Vector3f(0, 1, 0), 180);
    }
    state.text_image = new BufferedImage(160, 32, BufferedImage.TYPE_INT_ARGB);
    state.text_g = state.text_image.createGraphics();
    state.text_front_image = new BufferedImage(160, 32, BufferedImage.TYPE_INT_ARGB);
    state.text_front_g = state.text_front_image.createGraphics();

    if (Resources.ANTE_FLAG) {
        state.lcdPool = Executors.newScheduledThreadPool(1);

        state.lcdPool.scheduleAtFixedRate(new java.lang.Runnable({
            run: () => {
                try {
                    renderIkisaki(ctx, state, train);
                } catch (e) { print("LCD static Error: " + e); }
            }
        }), 0, 3000, TimeUnit.MILLISECONDS);
    }
}

function withLock(state, i, fn) {
    let lock = state.dhLocks[i];
    lock.lock();
    try {
        fn();
    } finally {
        lock.unlock();
    }
}

function renderIkisaki(ctx, state, train) {
    if (!state.ikisakiRate.shouldUpdate()) return;
    for (let i = 0; i < train.trainCars(); i++) {
        withLock(state, i, () => {
            ["out_left", "out_right"].forEach(pos => {
                let g = state.dh[i].graphicsFor(pos);
                ikisakiMain(g, train, state, i + 1);
            });
        });
    }
    withLock(state, 0, () => {
        let g = state.dh[0].graphicsFor("out_front");
        ikisakiFront(g, train, state);
    });
    let last = train.trainCars() - 1;
    withLock(state, last, () => {
        let g = state.dh[last].graphicsFor("out_back");
        ikisakiFront(g, train, state);
    });
}

function render(ctx, state, train) {
    if (state.modelRate.shouldUpdate()) {
        let notch = ctx.train.manualNotch;
        let angle;
        switch (notch) {
            case 5: angle = -22.5; break;
            case 4: angle = -17.5; break;
            case 3: angle = -12.5; break;
            case 2: angle = -7.5; break;
            case 1: angle = -2.5; break;
            case 0: angle = 2.5; break;
            case -1: angle = 7.5; break;
            case -2: angle = 12.5; break;
            case -3: angle = 17.5; break;
            case -4: angle = 22.5; break;
            case -5: angle = 27.5; break;
            case -6: angle = 32.5; break;
            case -7: angle = 37.5; break;
            case -8: angle = 42.5; break;
            case -9: angle = 45; break;
            default: angle = 2.5;
        }

        if (state.lastNotch != notch) {
            state.lastNotch = notch;

            let rotatedModel = state.masconModel.copy();
            const centerX = 0.921591;
            const centerY = 1.3;
            const centerZ = 9.62319;
            rotatedModel.applyTranslation(-centerX, -centerY, -centerZ);
            rotatedModel.applyRotation(new Vector3f(1, 0, 0), angle);
            rotatedModel.applyTranslation(centerX, centerY, centerZ);
            state.dmhMascon.uploadLater(rotatedModel);

            let rotatedModel1 = state.masconModel.copy();
            rotatedModel1.applyTranslation(-centerX, -centerY, -centerZ);
            rotatedModel1.applyRotation(new Vector3f(1, 0, 0), angle);
            rotatedModel1.applyTranslation(centerX, centerY, centerZ);
            rotatedModel1.applyRotation(new Vector3f(0, 1, 0), 180);
            state.dmhMascon1.uploadLater(rotatedModel1);
        }

        let routeTypeJpn = "";
        if (train.isOnRoute()) {
            let next = train.getThisRoutePlatformsNextIndex();
            let platformList = train.getThisRoutePlatforms();
            if (next < platformList.length) {
                let routeType = platformList[next].route.lightRailRouteNumber;
                routeTypeJpn = TextUtil.getCjkParts(routeType);
            }
        }

        if (state.exlightModel) {
            let isReversed = train.isReversed();
            if (state.lastRouteTypeJpn != routeTypeJpn || state.lastIsReversed != isReversed) {
                state.lastRouteTypeJpn = routeTypeJpn;
                state.lastIsReversed = isReversed;

                let isExpress = (routeTypeJpn != "普通" && routeTypeJpn != "各駅停車" && routeTypeJpn != "各停");
                if (isExpress) {
                    if (isReversed) {
                        state.dmhExlight.uploadLater(state.exlightOffModel);
                        state.dmhExlight1.uploadLater(state.exlightModelRotated);
                    } else {
                        state.dmhExlight.uploadLater(state.exlightModel);
                        state.dmhExlight1.uploadLater(state.exlightOffModelRotated);
                    }
                } else {
                    state.dmhExlight.uploadLater(state.exlightOffModel);
                    state.dmhExlight1.uploadLater(state.exlightOffModelRotated);
                }
            }
        }

        let blinkOn = (Date.now() % 500 < 250);
        for (let i = 0; i < train.trainCars(); i++) {
            let doorLOn = train.doorLeftOpen[i] && (train.doorValue() != 0) && (train.doorValue() != 1) && blinkOn;
            let doorROn = train.doorRightOpen[i] && (train.doorValue() != 0) && (train.doorValue() != 1) && blinkOn;

            if (state.lastDoorLState[i] != doorLOn) {
                state.lastDoorLState[i] = doorLOn;
                state.dmhDoorLLamp[i].uploadLater(doorLOn ? state.doorLLampModel : state.doorLLampOffModel);
            }
            if (state.lastDoorRState[i] != doorROn) {
                state.lastDoorRState[i] = doorROn;
                state.dmhDoorRLamp[i].uploadLater(doorROn ? state.doorRLampModel : state.doorRLampOffModel);
            }
        }
    }

    ctx.drawCarModel(state.dmhMascon.getUploadedModel(), 0, null);
    ctx.drawCarModel(state.dmhMascon1.getUploadedModel(), train.trainCars() - 1, null);
    if (state.exlightModel) {
        ctx.drawCarModel(state.dmhExlight.getUploadedModel(), 0, null);
        ctx.drawCarModel(state.dmhExlight1.getUploadedModel(), train.trainCars() - 1, null);
    }

    for (let i = 0; i < train.trainCars(); i++) {
        ctx.drawCarModel(state.dmhDoorLLamp[i].getUploadedModel(), i, null);
        ctx.drawCarModel(state.dmhDoorRLamp[i].getUploadedModel(), i, null);
    }

    // 非ANTE環境用（こちらも順序を固定化）
    if (!Resources.ANTE_FLAG) {
        try {
            renderIkisaki(ctx, state, train);

            for (let i = 0; i < train.trainCars(); i++) {
                state.dh[i].upload();
                ctx.drawCarModel(state.dh[i].model, i, null);
            }
        } catch (e) {
            // エラーログ間引き（5秒に1回だけ出力）
            let now = Date.now();
            if (!state.lastLcdErrLogMs) state.lastLcdErrLogMs = 0;
            if (now - state.lastLcdErrLogMs > 5000) {
                print("LCD Error (throttled): " + e);
                state.lastLcdErrLogMs = now;
            }
        }
    }
}

function dispose(ctx, state, train) {
    if (Resources.ANTE_FLAG) {
        state.lcdPool.shutdown();
    }
    for (let i = 0; i < train.trainCars(); i++) {
        state.dh[i].close();
        state.dmhDoorLLamp[i].close();
        state.dmhDoorRLamp[i].close();
    }
    state.text_g.dispose();
    state.text_front_g.dispose();
    state.dmhMascon.close();
    state.dmhMascon1.close();
    if (state.exlightModel) {
        state.dmhExlight.close();
        state.dmhExlight1.close();
    }
}