try {
    importPackage(java.awt);
    importPackage(java.awt.geom);

    include("lcd.js");
    include("ikisaki.js");
    include("ikisaki_head.js");
    include("koukoku.js");

    let g;

    function _loadImageSafe(path) {
        try {
            return Resources.readBufferedImage(Resources.id(path));
        } catch (e) {
            print("image load skipped: " + path + " / " + e);
            return null;
        }
    }

    function _loadAdImage(n) {
        return _loadImageSafe("mtr:botapinu_lcd/mctn_lcd/koukoku/koukoku_" + n + ".png");
    }

    function create(ctx, state, train) {
        // 号車ごとにLCDへ別テクスチャを書けるよう、dhを車両数ぶん作る
        state.dh = [];
        for (let i = 0; i < train.trainCars(); i++) {
            state.dh[i] = dhBase.create();
        }

        // 更新頻度を分離
        // LCD本体・路線図は重いので低頻度化（号車は固定、時刻は1分単位なので高頻度不要）
        // 広告・行先表示はさらに低頻度化して負荷を下げる
        state.lcdRate = new RateLimit(3.0);
        state.koukokuRate = new RateLimit(5.0);
        state.ikisakiRate = new RateLimit(5.0);

        state.koukoku_1 = _loadImageSafe("mtr:botapinu_lcd/mctn_lcd/images/home.png");
        state.koukoku_2 = _loadAdImage(2);
        state.koukoku_3 = _loadAdImage(3);
        state.koukoku_4 = _loadAdImage(4);
        state.koukoku_5 = _loadAdImage(5);
        state.koukoku_6 = _loadAdImage(6);
        state.koukoku_7 = _loadAdImage(7);
        state.home1 = _loadImageSafe("mtr:botapinu_lcd/mctn_lcd/images/home.png");
    }

    function dispose(ctx, state, train) {
        if (state.dh != null) {
            for (let i = 0; i < state.dh.length; i++) {
                try { if (state.dh[i] != null) state.dh[i].close(); } catch (e) {}
            }
        }
    }

    function _lcdNames(train) {
        if (train.isReversed()) {
            return { left: "lcd_door_right", right: "lcd_door_left" };
        }
        return { left: "lcd_door_left", right: "lcd_door_right" };
    }

    function _renderLcdCar(state, train, carIndex) {
        let dh = state.dh[carIndex];
        let carNumber = carIndex + 1;
        let names = _lcdNames(train);

        g = dh.graphicsFor(names.left);
        lcdmain(g, train, state, carNumber);
        lcdleft(g, train);

        g = dh.graphicsFor(names.right);
        lcdmain(g, train, state, carNumber);
        lcdright(g, train);

        dh.upload();
    }

    function _renderKoukokuCar(state, train, carIndex) {
        let dh = state.dh[carIndex];

        g = dh.graphicsFor("koukoku_left");
        koukokuMain(g, state, train);

        g = dh.graphicsFor("koukoku_right");
        koukokuMain(g, state, train);

        dh.upload();
    }

    function _renderIkisakiCar(state, train, carIndex) {
        let dh = state.dh[carIndex];

        g = dh.graphicsFor("out_left");
        ikisakiMain(g, state, train);

        g = dh.graphicsFor("out_right");
        ikisakiMain(g, state, train);

        // 前面/後面は全車両に描く必要がないため、先頭車と最後尾車だけ更新する
        if (carIndex == 0) {
            g = dh.graphicsFor("out_front");
            ikisakiHead(g, state, train);
        }
        if (carIndex == train.trainCars() - 1) {
            g = dh.graphicsFor("out_back");
            ikisakiHead(g, state, train);
        }

        dh.upload();
    }

    function render(ctx, state, train) {
        if (train.shouldRender()) {
            if (state.lcdRate.shouldUpdate()) {
                for (let i = 0; i < train.trainCars(); i++) {
                    _renderLcdCar(state, train, i);
                }
            }

            if (state.koukokuRate.shouldUpdate()) {
                for (let i = 0; i < train.trainCars(); i++) {
                    _renderKoukokuCar(state, train, i);
                }
            }

            if (state.ikisakiRate.shouldUpdate()) {
                for (let i = 0; i < train.trainCars(); i++) {
                    _renderIkisakiCar(state, train, i);
                }
            }
        }

        for (let i = 0; i < train.trainCars(); i++) {
            ctx.drawCarModel(state.dh[i].model, i, null);
        }
    }
} catch (error) {
    print("main.js error: " + error);
}
