"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.MpvMessageTransformation = void 0;
const vehicle_positions_1 = require("../../../../../schema-definitions/vehicle-positions");
const CoreToken_1 = require("@golemio/core/dist/helpers/ioc/CoreToken");
const Di_1 = require("@golemio/core/dist/integration-engine/ioc/Di");
const transformations_1 = require("@golemio/core/dist/integration-engine/transformations");
const moment_timezone_1 = __importDefault(require("@golemio/core/dist/shared/moment-timezone"));
const const_1 = require("../../../../../const");
const DPPUtils_1 = __importDefault(require("../helpers/DPPUtils"));
const ProviderSourceTypeEnum_1 = require("../helpers/ProviderSourceTypeEnum");
const MpvStopParser_1 = require("../helpers/mpv-net/MpvStopParser");
const RouteTypeEnums_1 = require("../../../../../helpers/RouteTypeEnums");
var PositionTrackingEnum;
(function (PositionTrackingEnum) {
    PositionTrackingEnum["NOT_TRACKING"] = "0";
    PositionTrackingEnum["TRACKING"] = "2";
})(PositionTrackingEnum || (PositionTrackingEnum = {}));
class MpvMessageTransformation extends transformations_1.BaseTransformation {
    constructor() {
        super();
        this.skipAgencyNames = [];
        /**
         * Overrides BaseTransformation::transform
         */
        this.transform = async (data) => {
            let trips = new Map();
            let stops = new Map();
            let res = {
                positions: [],
                stops: [],
                trips: [],
            };
            if (!Array.isArray(data))
                data = [data];
            for (const element of data) {
                const elemRes = await this.transformElement(element);
                if (elemRes) {
                    res.positions.push(elemRes.position);
                    trips.set(elemRes.trip.id, elemRes.trip);
                    for (const [key, value] of elemRes.stops) {
                        stops.set(key, value);
                    }
                }
            }
            res.trips = Array.from(trips.values());
            res.stops = Array.from(stops.values());
            return res;
        };
        this.transformElement = async (element) => {
            const attributes = element.$;
            // Trips with null cpoz (origin time) and falsy or "false" zrus (cancellation) attributes are excluded
            let hasInvalidAttributes = !attributes.cpoz && (!attributes.zrus || attributes.zrus === "false");
            // Trips without the CIS trip number are excluded
            hasInvalidAttributes ||= typeof attributes.spoj === "undefined" || attributes.spoj === "";
            // Canceled trips with no lat or lng are accepted because we try to later get the coords from the context if possible
            hasInvalidAttributes ||= attributes.zrus !== "true" && (!attributes.lat || !attributes.lng);
            if (this.isBlacklistedAgency(attributes.dopr) || hasInvalidAttributes) {
                return null;
            }
            attributes.lin = attributes.lin || "none";
            attributes.alias = attributes.alias || "none";
            attributes.cpoz = attributes.cpoz || moment_timezone_1.default.tz("Europe/Prague").format(MpvMessageTransformation.ORIGIN_TIME_FORMAT);
            const cisTripNumber = parseInt(attributes.spoj, 10);
            if (isNaN(cisTripNumber)) {
                return null;
            }
            const stops = element.zast;
            const { startTimestamp, positionTimestamp } = await this.deduceTimestamps(stops[0], attributes.cpoz);
            // filter out position with future time (set to be far than 5 minutes from now)
            if ((0, moment_timezone_1.default)(positionTimestamp).isAfter((0, moment_timezone_1.default)().add(5, "minutes"))) {
                return null;
            }
            // primary key for t = 0 (vehicle type is train) -> start_timestamp, cis_number
            // primary key for t != 0 -> start_timestamp, cis_id, cis_short_name, cis_number
            const primaryKeySuffix = attributes.t === "0" ? `_${attributes.spoj}` : `_${attributes.lin}` + `_${attributes.alias}` + `_${attributes.spoj}`;
            const primaryKey = startTimestamp.utc().format() + primaryKeySuffix;
            /*
                MPV route types:
    
                Metro:                           1
                Tramvaj:                         2
                Bus městský:                     3
                Bus regionální:                  4
                Bus noční:                       5
                Noční tramvaj:                   6
                Náhradní autobusová doprava:     7
                Lanovka:                         8
                Školní:                          9
                Invalidní:                      10
                Smluvní:                        11
                Loď:                            12
                Vlak:                           13
                Náhradní doprava za vlak:       14
                Náhradní tramvajová doprava:    15
                Bus noční regionální:           16
                Ostatní:                        17
                Trolejbus:                      18
            */
            const bearing = attributes.azimut;
            const speed = attributes.rychl;
            const aswLastStopId = null;
            const cisLastStopId = attributes.zast;
            let res = {
                position: {
                    asw_last_stop_id: aswLastStopId ? this.formatASWStopId(aswLastStopId) : null,
                    bearing: bearing ? this.fixSourceNegativeBearing(parseInt(bearing, 10)) : null,
                    cis_last_stop_id: cisLastStopId ? parseInt(cisLastStopId, 10) : null,
                    cis_last_stop_sequence: null,
                    delay_stop_arrival: attributes.zpoz_prij ? parseInt(attributes.zpoz_prij, 10) : null,
                    delay_stop_departure: attributes.zpoz_odj ? parseInt(attributes.zpoz_odj, 10) : null,
                    is_canceled: attributes.zrus === "true",
                    lat: attributes.lat ? parseFloat(attributes.lat) : null,
                    lng: attributes.lng ? parseFloat(attributes.lng) : null,
                    origin_time: attributes.cpoz,
                    origin_timestamp: positionTimestamp.toDate(),
                    speed: speed ? parseInt(speed, 10) : null,
                    state_position: const_1.StatePositionEnum.UNKNOWN,
                    state_process: const_1.StateProcessEnum.INPUT,
                    is_tracked: attributes.sled === PositionTrackingEnum.TRACKING && !!attributes.lat && !!attributes.lng,
                    trips_id: primaryKey,
                },
                stops: new Map(),
                trip: {
                    agency_name_real: attributes.doprSkut ? attributes.doprSkut : null,
                    agency_name_scheduled: attributes.dopr ? attributes.dopr : null,
                    cis_line_id: attributes.lin,
                    cis_line_short_name: attributes.alias,
                    cis_trip_number: cisTripNumber,
                    id: primaryKey,
                    is_canceled: attributes.zrus === "true",
                    origin_route_name: attributes.kmenl ?? null,
                    run_number: attributes.po !== undefined ? parseInt(attributes.po, 10) : null,
                    start_asw_stop_id: stops[0].$.asw ?? null,
                    start_cis_stop_id: stops[0].$.zast ? parseInt(stops[0].$.zast, 10) : null,
                    start_cis_stop_platform_code: stops[0].$.stan ?? null,
                    start_time: startTimestamp.tz("Europe/Prague").format("HH:mm"),
                    start_timestamp: startTimestamp.toDate(),
                    vehicle_registration_number: attributes.vuzevc ? parseInt(attributes.vuzevc, 10) : null,
                    vehicle_type_id: attributes.t ? parseInt(attributes.t, 10) : null,
                    wheelchair_accessible: attributes.np === "true",
                    provider_source_type: ProviderSourceTypeEnum_1.ProviderSourceTypeEnum.Http,
                },
            };
            for (let i = 0; i < stops.length; i++) {
                const stop = stops[i];
                if (!stop.$.zast) {
                    continue;
                }
                const delayDepartureType = stop.$.zpoz_typ_odj;
                const cisStopSequence = i + 1;
                if (res.position.cis_last_stop_id === parseInt(stop.$.zast, 10) &&
                    ((delayDepartureType && parseInt(delayDepartureType, 10) === 3) ||
                        (stop.$.zpoz_typ_prij && parseInt(stop.$.zpoz_typ_prij, 10) === 3))) {
                    res.position.cis_last_stop_sequence = cisStopSequence;
                }
                if (res.trip.vehicle_type_id === RouteTypeEnums_1.MPVRouteTypesEnum.UNKNOWN || res.trip.vehicle_type_id === RouteTypeEnums_1.MPVRouteTypesEnum.TRAIN) {
                    res.stops.set(`${primaryKey}_${stop.$.zast}`, {
                        rt_trip_id: primaryKey,
                        cis_stop_group_id: parseInt(stop.$.zast, 10),
                        cis_stop_platform_code: MpvStopParser_1.MpvStopParser.parsePlatformCode(stop),
                    });
                }
            }
            return res;
        };
        /**
         * Deduce UTC timestamp of start of trip and UTC timestamp of current position
         *
         * @param {any} firstStopXML - parsed object of first stop
         * @param {string} positionTimestampXML - XML attribute of local timestamp of position (HH:mm:ss)
         * @returns {{ startTimestamp: Moment; positionTimestamp: Moment }}
         */
        this.deduceTimestamps = async (firstStopXML, positionTimestampXML) => {
            const now = moment_timezone_1.default.tz("Europe/Prague");
            let isOverMidnight = 0;
            // creating startDate and timestamp from zast[0].prij and cpoz
            const startTimestamp = now.clone();
            let startDatePlain = firstStopXML.$.prij || firstStopXML.$.odj;
            startDatePlain = startDatePlain.split(":");
            // eslint-disable-next-line prettier/prettier
            startTimestamp.hour(parseInt(startDatePlain[0], 10)).minute(parseInt(startDatePlain[1], 10)).second(0).millisecond(0);
            // midnight checking
            isOverMidnight = this.checkMidnight(now, startTimestamp); // returns -1, 1 or 0
            startTimestamp.add(isOverMidnight, "d");
            const positionTimestamp = now.clone();
            const timestampPlain = positionTimestampXML.split(":");
            positionTimestamp
                .hour(parseInt(timestampPlain[0], 10))
                .minute(parseInt(timestampPlain[1], 10))
                .second(parseInt(timestampPlain[2], 10))
                .millisecond(0);
            // midnight checking
            isOverMidnight = this.checkMidnight(now, positionTimestamp); // returns -1, 1 or 0
            positionTimestamp.add(isOverMidnight, "d");
            return {
                startTimestamp,
                positionTimestamp,
            };
        };
        /**
         * Returns -1 if start hour is 12-23 and now is 0-12, 1 if start hour is 18-23 and now 0-6, else 0
         *
         * @param {Moment} now - Moment of position
         * @param {Moment} start - Moment of start of trip
         * @returns {number}
         */
        this.checkMidnight = (now, start) => {
            // i.e. 0 - 22 = -22
            // -22 <= -12
            // trip starting in previous day, but never starts before 12:00
            if (now.hour() - start.hour() <= -(24 - 12)) {
                // "backwards" 12 hours
                return -1;
            }
            // i.e. 23 - 1 = +22
            // +22 >= +18
            // trip starting next day, sending positions early, not eariler than 6 hours before start
            else if (now.hour() - start.hour() >= 24 - 6) {
                // "forwards" 6 hours
                return 1;
            }
            return 0; // same day
        };
        this.name = vehicle_positions_1.VehiclePositions.name;
        this.config = Di_1.IntegrationEngineContainer.resolve(CoreToken_1.CoreToken.SimpleConfig);
        this.skipAgencyNames = this.config
            .getValue("module.pid.vehicle-positions.mpvIntegration.skipAgencyNames", DPPUtils_1.default.DPP_AGENCY_NAME)
            .split(",");
    }
    isBlacklistedAgency(agency) {
        return agency && this.skipAgencyNames.includes(agency);
    }
    /**
     * Fix source negative bearing value due to overflow by adding 256
     *
     * @param {number} bearing
     * @returns {number}
     */
    fixSourceNegativeBearing(bearing) {
        return bearing < 0 ? bearing + 256 : bearing;
    }
    /**
     * Format input stop id from DPP agency (XXX000Y to XXX/Y) to ASW id
     *
     * @param {string} stopId
     * @returns {stringList}
     */
    formatASWStopId(stopId) {
        const fixedRightPadFactor = 10000;
        const aswParsedStopNodeId = Math.floor(parseInt(stopId, 10) / fixedRightPadFactor);
        const aswParsedStopPostId = parseInt(stopId, 10) - aswParsedStopNodeId * fixedRightPadFactor;
        return aswParsedStopNodeId + "/" + aswParsedStopPostId;
    }
}
exports.MpvMessageTransformation = MpvMessageTransformation;
MpvMessageTransformation.ORIGIN_TIME_FORMAT = "HH:mm:ss";
//# sourceMappingURL=MpvMessageTransformation.js.map