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

/*
 * 1024x768 / 4:3 車内LCD
 * - platform取得は元の正常系に合わせ getThisRoutePlatforms() / getThisRoutePlatformsNextIndex() のみ使用
 * - 前面/側面LEDとは独立。ikisaki.js / ikisaki_head.js には触らない
 */

var LCD_W = 1024;
var LCD_H = 768;
var HEADER_H = 250;

var __LCD_BASE_FONT = null;
var __LCD_BASE_FONT_JP = null;
var __LCD_FONT_CACHE = {};
var __LCD_FONT_JP_CACHE = {};

function _font(size, style) {
    if (style == null) style = Font.PLAIN;
    var key = String(size) + ":" + String(style);
    if (__LCD_FONT_CACHE[key]) return __LCD_FONT_CACHE[key];
    if (__LCD_BASE_FONT == null) __LCD_BASE_FONT = Resources.getSystemFont("Noto Sans");
    __LCD_FONT_CACHE[key] = __LCD_BASE_FONT.deriveFont(style, size);
    return __LCD_FONT_CACHE[key];
}

function _fontJP(size, style) {
    if (style == null) style = Font.PLAIN;
    var key = String(size) + ":" + String(style);
    if (__LCD_FONT_JP_CACHE[key]) return __LCD_FONT_JP_CACHE[key];
    if (__LCD_BASE_FONT_JP == null) {
        try { __LCD_BASE_FONT_JP = Resources.readFont(Resources.id("mtr:fonts/notosanscjkjp-medium.otf")); }
        catch (e) { __LCD_BASE_FONT_JP = Resources.getSystemFont("Noto Sans"); }
    }
    __LCD_FONT_JP_CACHE[key] = __LCD_BASE_FONT_JP.deriveFont(style, size);
    return __LCD_FONT_JP_CACHE[key];
}

function _platformSize(list) {
    if (list == null) return 0;
    try { return list.size(); } catch (e) {}
    try { return list.length; } catch (e2) {}
    return 0;
}

function _platformGet(list, index) {
    try { return list.get(index); } catch (e) {}
    return list[index];
}

function _safeText(v) { return v == null ? "" : String(v); }
function _cjk(v) { try { return _safeText(TextUtil.getCjkParts(_safeText(v))); } catch (e) { return _safeText(v); } }
function _nonCjk(v) { try { return _safeText(TextUtil.getNonCjkParts(_safeText(v))); } catch (e) { return ""; } }
function _isEmpty(s) { return s == null || String(s).length == 0; }
function _routeColorOf(p) { try { return new Color(p.route.color); } catch (e) { return new Color(0x3157A7); } }
function _darkBg() { return new Color(0x171C1C); }
function _lightGray() { return new Color(0xC8C8C8); }

// ===== route_type_colors.json 色取得 =====
var __LCD_ROUTE_TYPE_COLOR_DB = null;
function _loadRouteTypeColorDbOnce() {
    if (__LCD_ROUTE_TYPE_COLOR_DB != null) return;
    var path = "mtr:botapinu_lcd/mctn_lcd/route_type_colors.json";
    try {
        var txt = Resources.readString(Resources.id(path));
        __LCD_ROUTE_TYPE_COLOR_DB = JSON.parse(String(txt));
        if (__LCD_ROUTE_TYPE_COLOR_DB == null || typeof __LCD_ROUTE_TYPE_COLOR_DB !== "object") __LCD_ROUTE_TYPE_COLOR_DB = {};
        print("route_type_colors.json loaded: " + path);
    } catch (e) {
        __LCD_ROUTE_TYPE_COLOR_DB = {};
        print("FAILED route_type_colors.json: " + e);
    }
}
function _normalizeRouteTypeKey(s) {
    if (s == null) return "";
    return String(s).replace(/[\s　|｜]+/g, "").trim();
}
function _parseAnyColor(v) {
    if (v == null) return null;
    if (typeof v === "string" && v.indexOf("Color.") === 0) {
        var name = v.substring(6).trim();
        try { if (Color[name] != null) return Color[name]; } catch (e0) {}
    }
    if (typeof v === "number") {
        try { return new Color((v | 0)); } catch (en) {}
    }
    var s = String(v).trim();
    if (s.length == 0) return null;
    try {
        if (s.indexOf("#") === 0) {
            var hex = s.substring(1);
            if (hex.length === 6) return new Color((java.lang.Integer.parseInt(hex, 16)) | 0);
            if (hex.length === 8) return new Color((java.lang.Long.parseLong(hex, 16)) | 0, true);
        }
        if (s.indexOf("0x") === 0 || s.indexOf("0X") === 0) {
            var hex2 = s.substring(2);
            if (hex2.length <= 6) return new Color((java.lang.Integer.parseInt(hex2, 16)) | 0);
            return new Color((java.lang.Long.parseLong(hex2, 16)) | 0, true);
        }
        var n = java.lang.Long.parseLong(s);
        if (n >= 0 && n <= 0xFFFFFF) return new Color((n | 0));
        return new Color((n | 0), true);
    } catch (e) {}
    return null;
}
function _getRouteTypeColorObj(routeTypeJpn) {
    _loadRouteTypeColorDbOnce();
    if (!routeTypeJpn || !__LCD_ROUTE_TYPE_COLOR_DB) return null;

    var key = String(routeTypeJpn).trim();
    var obj = __LCD_ROUTE_TYPE_COLOR_DB[key];
    if (obj && typeof obj === "object") return obj;

    var normalized = _normalizeRouteTypeKey(key);
    for (var k in __LCD_ROUTE_TYPE_COLOR_DB) {
        if (!__LCD_ROUTE_TYPE_COLOR_DB.hasOwnProperty(k)) continue;
        if (_normalizeRouteTypeKey(k) === normalized) {
            obj = __LCD_ROUTE_TYPE_COLOR_DB[k];
            if (obj && typeof obj === "object") return obj;
        }
    }
    try { print("lcd route_type_colors miss: " + routeTypeJpn); } catch (em) {}
    return null;
}
function _getRouteTypeBg(routeTypeJpn, fallbackColor) {
    var obj = _getRouteTypeColorObj(routeTypeJpn);
    if (!obj) return fallbackColor;
    var bg = _parseAnyColor(obj.bg);
    return bg == null ? fallbackColor : bg;
}
function _getRouteTypeFg(routeTypeJpn, fallbackColor) {
    var obj = _getRouteTypeColorObj(routeTypeJpn);
    if (!obj) return fallbackColor;
    var fg = _parseAnyColor(obj.fg);
    return fg == null ? fallbackColor : fg;
}

function _getRouteTypeOutline(routeTypeJpn, fallbackValue) {
    var obj = _getRouteTypeColorObj(routeTypeJpn);
    if (!obj || obj.outline == null) return fallbackValue;
    if (obj.outline === true) return true;
    if (obj.outline === false) return false;
    var s = String(obj.outline).trim().toLowerCase();
    if (s === "true" || s === "1" || s === "yes" || s === "on") return true;
    if (s === "false" || s === "0" || s === "no" || s === "off") return false;
    return fallbackValue;
}


function _fitDraw(g, text, font, x, baseline, w, color, alignCenter) {
    text = _safeText(text);
    g.setFont(font);
    var fm = g.getFontMetrics(font);
    var tw = fm.stringWidth(text);
    var sx = 1.0;
    if (tw > w && tw > 0) sx = w / tw;
    g.setColor(color);

    if (sx < 1.0) {
        var at = new AffineTransform();
        at.scale(sx, 1.0);
        var scaledFont = font.deriveFont(at);
        g.setFont(scaledFont);
        var drawX = alignCenter ? x + (w - tw * sx) / 2 : x;
        g.drawString(text, drawX / sx, baseline);
    } else {
        var drawX2 = alignCenter ? x + (w - tw) / 2 : x;
        g.drawString(text, drawX2, baseline);
    }
}

function _fitDrawOutline(g, text, font, x, baseline, w, mainColor, outlineColor, alignCenter) {
    var o = 2;
    _fitDraw(g, text, font, x-o, baseline, w, outlineColor, alignCenter);
    _fitDraw(g, text, font, x+o, baseline, w, outlineColor, alignCenter);
    _fitDraw(g, text, font, x, baseline-o, w, outlineColor, alignCenter);
    _fitDraw(g, text, font, x, baseline+o, w, outlineColor, alignCenter);
    _fitDraw(g, text, font, x, baseline, w, mainColor, alignCenter);
}

function _drawCoverImage(g, img, x, y, w, h) {
    if (img == null) return false;
    var iw = img.getWidth();
    var ih = img.getHeight();
    if (iw <= 0 || ih <= 0) return false;
    var scale = Math.max(w / iw, h / ih);
    var dw = Math.ceil(iw * scale);
    var dh = Math.ceil(ih * scale);
    var dx = x + Math.floor((w - dw) / 2);
    var dy = y + Math.floor((h - dh) / 2);
    g.drawImage(img, dx, dy, dw, dh, null);
    return true;
}

function _routeData(train) {
    var data = {};
    data.invalid = false;
    data.next = 0;
    data.size = 0;
    data.platformList = null;
    data.nextStaJpn = "";
    data.nextStaEng = "";
    data.finalStaJpn = "";
    data.finalStaEng = "";
    data.routeTypeJpn = "";
    data.routeTypeEng = "";
    data.routeTypeRaw = "";
    data.routeColor = new Color(0x3157A7);
    data.prefixJpn = "つぎは";
    data.prefixEng = "Next";

    try {
        if (!train.isOnRoute()) { data.invalid = true; return data; }

        var list = train.getThisRoutePlatforms();
        var next = train.getThisRoutePlatformsNextIndex();
        var size = _platformSize(list);
        if (list == null || size <= 0 || next < 0 || next >= size) {
            data.invalid = true;
            return data;
        }

        var p = _platformGet(list, next);
        if (p == null || p.route == null || p.station == null) {
            data.invalid = true;
            return data;
        }

        data.platformList = list;
        data.next = next;
        data.size = size;
        data.nextStaJpn = _cjk(p.station.name);
        data.nextStaEng = _nonCjk(p.station.name);
        data.finalStaJpn = _cjk(p.destinationName);
        data.finalStaEng = _nonCjk(p.destinationName);
        data.routeTypeRaw = _safeText(p.route.lightRailRouteNumber).trim();
        var rtParts = data.routeTypeRaw.split(/[|｜]/);
        if (rtParts.length >= 2) {
            data.routeTypeJpn = _safeText(rtParts[0]).trim();
            data.routeTypeEng = _safeText(rtParts[1]).trim();
        } else {
            data.routeTypeJpn = _cjk(data.routeTypeRaw);
            data.routeTypeEng = _nonCjk(data.routeTypeRaw);
        }
        if (_isEmpty(data.routeTypeJpn)) data.routeTypeJpn = data.routeTypeRaw;
        if (_isEmpty(data.routeTypeEng)) data.routeTypeEng = data.routeTypeJpn;
        data.routeColor = _routeColorOf(p);

        var remain = 9999;
        try { remain = p.distance - train.getRailProgress(0); } catch (e2) {}
        var isLast = (next >= size - 1);
        if (train.doorValue() > 0) {
            data.prefixJpn = isLast ? "終点" : "ただいま";
            data.prefixEng = isLast ? "Last Stop" : "Now stopping";
        } else if (remain < 500) {
            data.prefixJpn = isLast ? "まもなく終点" : "まもなく";
            data.prefixEng = isLast ? "Soon Last Stop" : "Soon";
        } else {
            data.prefixJpn = isLast ? "つぎは終点" : "つぎは";
            data.prefixEng = isLast ? "Next Last Stop" : "Next";
        }
    } catch (e) {
        data.invalid = true;
    }
    return data;
}

function _isKoukokuMode(data) {
    var rt = _safeText(data.routeTypeJpn);
    var dest = _safeText(data.finalStaJpn);
    return data.invalid
        || rt == "回送" || dest == "回送"
        || rt == "試運転" || dest == "試運転"
        || rt == "臨時" || dest == "臨時"
        || rt.indexOf("車庫") >= 0 || dest.indexOf("車庫") >= 0
        || rt.indexOf("入庫") >= 0 || dest.indexOf("入庫") >= 0;
}

function _drawKoukoku1OrFallback(g) {
    var paths = [
        "mtr:botapinu_lcd/mctn_lcd/images/home.png"
    ];
    for (var i = 0; i < paths.length; i++) {
        try {
            var img = Resources.readBufferedImage(Resources.id(paths[i]));
            if (_drawCoverImage(g, img, 0, 0, LCD_W, LCD_H)) return;
        } catch (e) {}
    }
    g.setColor(Color.white);
    g.fillRect(0, 0, LCD_W, LCD_H);
    g.setColor(_darkBg());
    g.fillRect(0, 0, LCD_W, HEADER_H);
    _fitDraw(g, "回　送", _fontJP(96, Font.BOLD), 0, 135, LCD_W, Color.white, true);
    _fitDraw(g, "Not In Service", _font(46, Font.PLAIN), 0, 200, LCD_W, Color.white, true);
}

function _drawServiceBadge(g, data) {
    var bg = _getRouteTypeBg(data.routeTypeJpn, data.routeColor);
    var fg = _getRouteTypeFg(data.routeTypeJpn, Color.white);
    var outline = _getRouteTypeOutline(data.routeTypeJpn, true);
    var badgeX = 22;
    var badgeY = 22;
    var badgeW = 200;
    var badgeH = 64;
    g.setColor(bg);
    g.fillRect(badgeX, badgeY, badgeW, badgeH);
    var text = !_isEmpty(data.routeTypeJpn) ? data.routeTypeJpn : data.routeTypeEng;
    if (_isEmpty(text)) text = "普通";
    var f = _fontJP(44, Font.BOLD);
    if (outline === false) {
        _fitDraw(g, text, f, badgeX + 10, badgeY + 48, badgeW - 20, fg, true);
    } else {
        _fitDrawOutline(g, text, f, badgeX + 10, badgeY + 48, badgeW - 20, fg, Color.black, true);
    }
}
function _drawHeader(g, data, carNumber) {
    g.setColor(_darkBg());
    g.fillRect(0, 0, LCD_W, HEADER_H);

    _drawServiceBadge(g, data);
    _fitDraw(g, data.finalStaJpn, _fontJP(60, Font.BOLD), 22, 160, 200, Color.white, true);
    _fitDraw(g, _isEmpty(data.finalStaJpn) ? "" : "ゆき", _fontJP(28, Font.PLAIN), 65, 200, 120, Color.white, true);

    g.setColor(data.routeColor);
    g.fillRect(245, 14, 44, HEADER_H - 28);
    g.setColor(new Color(0x4A4F4F));
    g.fillRect(291, 14, 3, HEADER_H - 28);

    _fitDraw(g, data.prefixJpn, _fontJP(42, Font.PLAIN), 325, 70, 245, Color.white, false);
    _fitDraw(g, data.nextStaJpn, _fontJP(108, Font.BOLD), 325, 190, 480, Color.white, true);

    var d = new Date();
    var hh = ("0" + d.getHours()).slice(-2);
    var mm = ("0" + d.getMinutes()).slice(-2);
    g.setColor(Color.white);
    g.setFont(_font(40, Font.PLAIN));
    g.drawString(hh + ":" + mm, 795, 63);

    var carText = (carNumber == null ? "10" : String(carNumber));
    _fitDraw(g, carText, _fontJP(42, Font.PLAIN), 900, 62, 80, Color.white, true);
    g.setFont(_fontJP(18, Font.PLAIN));
    g.setColor(Color.white);
    g.drawString("号車", 930, 90);

    g.setColor(new Color(0x4B4B4B));
    g.fillRect(0, HEADER_H - 3, LCD_W, 3);
}

function lcdmain(g, train, state, carNumber) {
    if (g instanceof java.awt.Graphics2D) {
        g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF);
        g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_OFF);
        g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR);
        g.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_SPEED);
    }

    var data = _routeData(train);
    if (_isKoukokuMode(data)) {
        _drawKoukoku1OrFallback(g);
        return;
    }

    g.setColor(Color.white);
    g.fillRect(0, 0, LCD_W, LCD_H);
    _drawHeader(g, data, carNumber);

    g.setColor(new Color(0xF7F8FA));
    g.fillRect(0, HEADER_H, LCD_W, LCD_H - HEADER_H);

}

function _drawVerticalStationNameFit(g, text, xCenter, yLine, color) {
    text = _safeText(text);
    if (_isEmpty(text)) return;
    var topLimit = HEADER_H + 28;
    var bottomLimit = yLine - 42;
    var size = 34;
    while (size > 15) {
        var f = _fontJP(size, Font.BOLD);
        var fm = g.getFontMetrics(f);
        var totalH = text.length * (fm.getHeight() - 4);
        if (bottomLimit - totalH >= topLimit) break;
        size -= 1;
    }
    var font = _fontJP(size, Font.BOLD);
    var fm2 = g.getFontMetrics(font);
    var step = fm2.getHeight() - 4;
    var totalH2 = text.length * step;
    var y = Math.max(topLimit + fm2.getAscent(), bottomLimit - totalH2 + fm2.getAscent());
    g.setFont(font);
    g.setColor(color);
    for (var i = 0; i < text.length; i++) {
        var ch = text.substring(i, i + 1);
        var cw = fm2.stringWidth(ch);
        g.drawString(ch, xCenter - cw / 2, y);
        y += step;
    }
}

function _drawAngledStationNameFit(g, text, x, yLine, color, angleDeg) {
    text = _safeText(text);
    if (_isEmpty(text)) return;
    var y = yLine - 118;
    var size = 30;
    var maxW = 170;
    if (text.length >= 13) maxW = 150;
    if (text.length >= 18) maxW = 135;
    while (size > 15) {
        var f0 = _font(size, Font.BOLD);
        var fm0 = g.getFontMetrics(f0);
        if (fm0.stringWidth(text) <= maxW || size <= 17) break;
        size -= 1;
    }
    var font = _font(size, Font.BOLD);
    g.rotate(Math.PI / 180 * angleDeg, x, y);
    try { _fitDraw(g, text, font, x - maxW / 2, y, maxW, color, true); }
    finally { g.rotate(Math.PI / 180 * -angleDeg, x, y); }
}

function _drawRouteMap(g, train, reverse) {
    var data = _routeData(train);
    if (_isKoukokuMode(data)) return;

    var list = data.platformList;
    var size = data.size;
    var next = data.next;
    var count = 8;
    var start = next - 3;
    if (train.doorValue() <= 0) start = next - 2;
    if (start < 0) start = 0;
    if (start + count > size) start = Math.max(0, size - count);
    count = Math.min(count, size - start);
    if (count <= 0) return;

    var yLine = 585;
    var x0 = 112;
    var gap = 114;
    var barH = 54;

    var xs = [];
    for (var i = 0; i < count; i++) {
        xs[i] = reverse ? (LCD_W - x0 - i * gap) : (x0 + i * gap);
    }

    var showJapanese = (Date.now() % 10000 > 5000);
    for (var i2 = 0; i2 < count; i2++) {
        var idx = start + i2;
        var p = _platformGet(list, idx);
        if (p == null || p.station == null) continue;
        var nameJ = _cjk(p.station.name);
        var nameE = _nonCjk(p.station.name);
        var xName = xs[i2];
        if (showJapanese || _isEmpty(nameE)) {
            _drawVerticalStationNameFit(g, !_isEmpty(nameJ) ? nameJ : nameE, xName, yLine, Color.black);
        } else {
            _drawAngledStationNameFit(g, nameE, xName, yLine, Color.black, -62);
        }
    }

    var minX = xs[0], maxX = xs[0];
    for (var m = 1; m < xs.length; m++) {
        if (xs[m] < minX) minX = xs[m];
        if (xs[m] > maxX) maxX = xs[m];
    }
    minX -= 44;
    maxX += 44;

    var routeColor = data.routeColor;
    var passedColor = new Color(0x9A9A9A);
    var barTop = yLine - barH / 2;
    var barBot = yLine + barH / 2;

    // 次駅のX座標を確定
    var currentMarkerX = xs[0];
    for (var findCur = 0; findCur < count; findCur++) {
        if (start + findCur == next) currentMarkerX = xs[findCur];
    }

    // バー本体：過ぎた側をグレー、これから先を路線色
    // 注意：reverse は画面左右の反転なので、Xの大小が逆になる。
    if (!reverse) {
        g.setColor(passedColor);
        g.fillRect(minX, barTop, Math.max(0, currentMarkerX - minX), barH);
        g.setColor(routeColor);
        g.fillRect(currentMarkerX, barTop, Math.max(0, maxX - currentMarkerX), barH);
        g.fillPolygon([maxX + 48, maxX, maxX], [yLine, barTop, barBot], 3);
    } else {
        g.setColor(routeColor);
        g.fillRect(minX, barTop, Math.max(0, currentMarkerX - minX), barH);
        g.fillPolygon([minX - 48, minX, minX], [yLine, barTop, barBot], 3);
        g.setColor(passedColor);
        g.fillRect(currentMarkerX, barTop, Math.max(0, maxX - currentMarkerX), barH);
    }

    var doorOpen = false;
    try { doorOpen = train.doorValue() > 0; } catch (eDoor) { doorOpen = false; }

    var nextDotColor = new Color(0xE5C100); // 次駅丸：黄色
    var markerColor = new Color(0x8B1535);

    // 駅丸。停車中は次駅を赤ブロックで描くのでスキップ。
    // 走行中は次駅も黄色丸として残す。
    for (var k = 0; k < count; k++) {
        var stationIndex = start + k;
        var x = xs[k];
        if (stationIndex == next) currentMarkerX = x;

        if (doorOpen && stationIndex == next) continue;

        var r = 14;
        var isPassed = stationIndex < next;
        if (!doorOpen && stationIndex == next) {
            g.setColor(nextDotColor);
        } else {
            g.setColor(isPassed ? new Color(0xD0D0D0) : Color.white);
        }
        g.fillOval(x - r, yLine - r, r * 2, r * 2);
        g.setColor(isPassed ? new Color(0x777777) : new Color(0x444444));
        g.setStroke(new BasicStroke(2));
        g.drawOval(x - r, yLine - r, r * 2, r * 2);
    }

    // ===== 赤い位置表示 =====
    // 停車中：駅位置に赤ブロック＋白丸
    // 走行中：前駅〜次駅の間に赤い矢印ブロックだけを置く。中に丸は描かない。
    var markerH = barH;
    var notch = 18;
    var markerW = doorOpen ? 96 : 72;
    var mx = currentMarkerX;

    if (!doorOpen) {
        // 走行中は「次駅」ではなく、次駅の手前の駅間に置く。
        // !reverse は右向き表示なので次駅の左側、reverse は左向き表示なので次駅の右側。
        mx = reverse ? currentMarkerX + gap * 0.42 : currentMarkerX - gap * 0.42;
    }

    var left = mx - markerW / 2;
    var right = mx + markerW / 2;
    var top = yLine - markerH / 2;
    var mid = yLine;
    var bot = yLine + markerH / 2;

    g.setColor(markerColor);

    // 進行方向：画面上で次駅が右なら +1、左なら -1。
    // 尖っている方向は必ずこの方向に合わせる。
    var dir = reverse ? -1 : 1;

    if (doorOpen) {
        // 停車中：赤い停車駅ブロック＋白丸。
        // 尖り方向 = 次駅方向。
        if (dir > 0) {
            // 次駅が右側：右向きに尖る
            g.fillPolygon([left, right - notch, right, right - notch, left],
                          [top,  top,           mid,   bot,           bot], 5);
        } else {
            // 次駅が左側：左向きに尖る
            g.fillPolygon([left, left + notch, right, right, left + notch],
                          [mid,  top,          top,   bot,  bot], 5);
        }
    } else {
        // 走行中：赤い現在位置マーカー。
        // 形は「/──/ < < \──\」のように、両端が同じ方向を向く山形。
        // 白丸は描かず、次駅の黄色丸は駅位置に残す。
        var slant = notch;
        if (dir > 0) {
            // 次駅が右側：両端が右向き（ >  > ）になる鏡像
            g.fillPolygon([left, right - slant, right, right - slant, left, left + slant],
                          [top,  top,           mid,   bot,           bot,  mid], 6);
        } else {
            // 次駅が左側：両端が左向き（ <  < ）
            g.fillPolygon([left + slant, right, right - slant, right, left + slant, left],
                          [top,          top,   mid,           bot,  bot,          mid], 6);
        }
    }

    // 白い境界線。停車中は尖り側1か所、走行中は両端に同じ向きの山形境界を入れる。
    g.setColor(Color.white);
    g.setStroke(new BasicStroke(5));
    if (doorOpen) {
        if (dir > 0) {
            // 右向き停車マーカーの白い境界
            g.drawLine(Math.floor(right - notch), Math.floor(top + 4), Math.floor(right - 4), Math.floor(mid));
            g.drawLine(Math.floor(right - 4), Math.floor(mid), Math.floor(right - notch), Math.floor(bot - 4));
        } else {
            // 左向き停車マーカーの白い境界
            g.drawLine(Math.floor(left + notch), Math.floor(top + 4), Math.floor(left + 4), Math.floor(mid));
            g.drawLine(Math.floor(left + 4), Math.floor(mid), Math.floor(left + notch), Math.floor(bot - 4));
        }
    } else {
        if (dir > 0) {
            // 右向き走行マーカー：両端に > > 型の白境界
            g.drawLine(Math.floor(left), Math.floor(top + 4), Math.floor(left + slant), Math.floor(mid));
            g.drawLine(Math.floor(left + slant), Math.floor(mid), Math.floor(left), Math.floor(bot - 4));
            g.drawLine(Math.floor(right - slant), Math.floor(top + 4), Math.floor(right - 4), Math.floor(mid));
            g.drawLine(Math.floor(right - 4), Math.floor(mid), Math.floor(right - slant), Math.floor(bot - 4));
        } else {
            // 左向き走行マーカー：両端に < < 型の白境界
            g.drawLine(Math.floor(left + slant), Math.floor(top + 4), Math.floor(left + 4), Math.floor(mid));
            g.drawLine(Math.floor(left + 4), Math.floor(mid), Math.floor(left + slant), Math.floor(bot - 4));
            g.drawLine(Math.floor(right), Math.floor(top + 4), Math.floor(right - slant), Math.floor(mid));
            g.drawLine(Math.floor(right - slant), Math.floor(mid), Math.floor(right), Math.floor(bot - 4));
        }
    }

    // 停車中だけ赤ブロック内に白丸を描く。走行中は赤内の丸は描かない。
    if (doorOpen) {
        var dotR = 11;
        g.setColor(new Color(0xE6E6E6));
        g.fillOval(Math.floor(mx - dotR), Math.floor(yLine - dotR), dotR * 2, dotR * 2);
        g.setColor(new Color(0xEEEEEE));
        g.setStroke(new BasicStroke(1));
        g.drawOval(Math.floor(mx - dotR), Math.floor(yLine - dotR), dotR * 2, dotR * 2);
    }

}

function lcdleft(g, train) { _drawRouteMap(g, train, false); }
function lcdright(g, train) { _drawRouteMap(g, train, true); }
