"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
    return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __importStar = (this && this.__importStar) || (function () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
var __metadata = (this && this.__metadata) || function (k, v) {
    if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
var __param = (this && this.__param) || function (paramIndex, decorator) {
    return function (target, key) { decorator(target, key, paramIndex); }
};
var RegionalBusPositionsManager_1;
Object.defineProperty(exports, "__esModule", { value: true });
exports.RegionalBusPositionsManager = void 0;
const DateTimeUtils_1 = require("../../../../../../helpers/DateTimeUtils");
const VPContainerToken_1 = require("../../../../ioc/VPContainerToken");
const tsyringe_1 = require("@golemio/core/dist/shared/tsyringe");
const turf = __importStar(require("@turf/turf"));
const const_1 = require("../../../../../../const");
const PositionHandlerEnum_1 = require("../PositionHandlerEnum");
const ValidToCalculator_1 = require("../ValidToCalculator");
const PositionStateDelayManager_1 = require("../state-position/PositionStateDelayManager");
const RegionalBusComputeDelayHelper_1 = require("./compute-positions/RegionalBusComputeDelayHelper");
const BasePositionsManager_1 = require("../BasePositionsManager");
const CoreToken_1 = require("@golemio/core/dist/helpers/ioc/CoreToken");
let RegionalBusPositionsManager = RegionalBusPositionsManager_1 = class RegionalBusPositionsManager extends BasePositionsManager_1.BasePositionsManager {
    constructor(validToCalculator, logger) {
        super({ useEndTimestamp: false, allowCancelState: false });
        this.validToCalculator = validToCalculator;
        this.logger = logger;
        /**
         * Takes position one by one, set proper handler for type of position, and do the process of position
         */
        this.updatePositions = (options, _schedule) => {
            const { tripPositions, startDayTimestamp, startTimestamp, context, computedPositions, gtfsRouteType } = options;
            let isBacktrackingDetected = false;
            const positionsSource = tripPositions.positions;
            positionsSource.sort(RegionalBusPositionsManager_1.sortByOriginTimestamp);
            for (const position of positionsSource) {
                let positionToUpdate = null;
                // situations
                try {
                    switch (this.setPositionUpdateHandler(position)) {
                        case PositionHandlerEnum_1.PositionHandlerEnum.TRACKING:
                            const currentPosition = turf.point([position.lng, position.lat], {
                                id: position.id,
                                origin_time: position.origin_time,
                                origin_timestamp: position.origin_timestamp,
                                scheduled_timestamp: position.scheduled_timestamp,
                                this_stop_id: position.this_stop_id,
                                tcp_event: position.tcp_event,
                            });
                            positionToUpdate = this.getEstimatedPoint(tripPositions.gtfsData, currentPosition, context, startDayTimestamp);
                            positionToUpdate = RegionalBusComputeDelayHelper_1.RegionalBusComputeDelayHelper.updatePositionToUpdate(context, positionToUpdate, position, gtfsRouteType);
                            positionToUpdate.bearing = position.bearing ?? positionToUpdate.bearing;
                            break;
                        case PositionHandlerEnum_1.PositionHandlerEnum.NOT_TRACKING:
                            // if there is no previous positions with tracking status, set position as before_track
                            if (this.isFreshNewVehicleInSystem(context, position, null)) {
                                const firstStopTime = tripPositions.gtfsData.stop_times[0];
                                positionToUpdate = this.defaultNonTrackingPositionToUpdate(position, firstStopTime, startDayTimestamp, false, 0);
                            }
                            else {
                                // if there is tracking 2 position with same origin_timestamp then this position is duplicate
                                const statePosition = this.detectDuplicatePosition(position, context.lastPositionOriginTimestamp, positionsSource);
                                const lastShapesAnchorPoint = tripPositions.gtfsData.shapes_anchor_points[tripPositions.gtfsData.shapes_anchor_points.length - 1];
                                const lastStopTime = tripPositions.gtfsData.stop_times[tripPositions.gtfsData.stop_times.length - 1];
                                const lastShapesAnchorPointTimeScheduled = DateTimeUtils_1.DateTimeUtils.getStopDateTimeForDayStart(lastShapesAnchorPoint.time_scheduled_seconds, startDayTimestamp);
                                positionToUpdate = {
                                    id: position.id,
                                    last_stop_arrival_time: lastShapesAnchorPointTimeScheduled,
                                    last_stop_departure_time: lastShapesAnchorPointTimeScheduled,
                                    last_stop_id: lastStopTime.stop_id,
                                    last_stop_sequence: lastShapesAnchorPoint.last_stop_sequence,
                                    shape_dist_traveled: lastShapesAnchorPoint.shape_dist_traveled,
                                    state_position: statePosition,
                                    state_process: const_1.StateProcessEnum.PROCESSED,
                                    ...(lastStopTime.stop_headsign && {
                                        last_stop_headsign: lastStopTime.stop_headsign,
                                    }),
                                };
                                // if state was changed in last 30 seconds, then it is AFTER_TRACK_DELAYED
                                // with delay computation (copied from delay at stop)
                                if (positionToUpdate.state_position === const_1.StatePositionEnum.AFTER_TRACK &&
                                    PositionStateDelayManager_1.PositionStateDelayManager.shouldPropagateAfterTrackDelay(context)) {
                                    positionToUpdate.state_position = const_1.StatePositionEnum.AFTER_TRACK_DELAYED;
                                }
                                positionToUpdate = RegionalBusComputeDelayHelper_1.RegionalBusComputeDelayHelper.updatePositionToUpdate(context, positionToUpdate, position, gtfsRouteType);
                                if (positionToUpdate.state_position === const_1.StatePositionEnum.AFTER_TRACK_DELAYED) {
                                    positionToUpdate.delay = positionToUpdate.delay_stop_arrival ?? null;
                                }
                            }
                            break;
                        case PositionHandlerEnum_1.PositionHandlerEnum.DO_NOTHING:
                        default:
                            break;
                    }
                    // if not null push to update
                    if (positionToUpdate) {
                        positionToUpdate.is_tracked ??= position.is_tracked;
                        positionToUpdate.valid_to = this.validToCalculator.getValidToAttribute(positionToUpdate, position, gtfsRouteType, tripPositions.gtfsData, startTimestamp);
                        // backtracking detection so we can later invalidate old positions
                        if (!isBacktrackingDetected && positionToUpdate.last_stop_sequence && context.lastStopSequence) {
                            isBacktrackingDetected = positionToUpdate.last_stop_sequence < context.lastStopSequence;
                        }
                        computedPositions.push(positionToUpdate);
                    }
                    // set last known BEFORE_TRACK_DELAYED position
                    // set last position tracking (only for at_stop and on_track)
                    this.setLastPositionTracking(position, context, positionToUpdate);
                    // set new first position at stop streak if this stop seqence is set and it is not same as before
                    this.setFirstUniqueStopSequence(positionToUpdate, context, position);
                    // IF currently valid updated position / position was processed before
                    // and it is NOT AT_STOP
                    // then disrupt atStopStreak
                    if (this.disruptAtStopStreak(position, positionToUpdate)) {
                        context.atStopStreak.stop_sequence = null;
                    }
                    // duplicated position should not be considered at all
                    if (positionToUpdate && positionToUpdate.state_position !== const_1.StatePositionEnum.DUPLICATE) {
                        context.lastPositionId = position.id;
                        context.lastPositionLat = position.lat;
                        context.lastPositionLng = position.lng;
                        context.lastPositionCanceled = position.is_canceled;
                        context.lastPositionOriginTimestamp = position.origin_timestamp.getTime();
                        context.lastStopSequence = positionToUpdate.last_stop_sequence ?? context.lastStopSequence ?? null;
                        if (context.lastPositionState !== positionToUpdate.state_position) {
                            context.lastPositionState = positionToUpdate.state_position;
                            context.lastPositionStateChange = new Date().toISOString();
                        }
                        RegionalBusComputeDelayHelper_1.RegionalBusComputeDelayHelper.updateContext(context, positionToUpdate, position, gtfsRouteType);
                    }
                }
                catch (error) {
                    this.logger.error({
                        error,
                        context,
                        positionId: position.id,
                        lat: position.lat,
                        lng: position.lng,
                    }, `${this.constructor.name}: Error while processing position`);
                    continue;
                }
            }
            return {
                context,
                positions: computedPositions,
                isBacktrackingDetected: isBacktrackingDetected,
            };
        };
        /**
         * Picks only one closest point for multiple possible points based on delay of last position and stop times
         *
         * @param {Feature<Point, ICurrentPositionProperties>} currentPosition - Feature Point of current position
         * @param {IVPTripsLastPositionContext | null} context - Context state, holds information about previous positions
         * @param {number} startDayTimestamp - Unix timestamp of start of the day
         * @param {IShapeAnchorPoint[]} closestPts - All closest points of possible segments
         * @param {IComputationTrip} tripGtfsData - GTFS data and all set of known positions
         * @returns {IPositionToUpdate} - Result point as position to update in DB
         */
        this.getClosestPoint = (currentPosition, context, startDayTimestamp, thisClosestPoint, tripGtfsData, _gtfsRouteType) => {
            // want to find minimum difference of our prediction, where the bus should be
            const tripShapePoints = tripGtfsData.shapes_anchor_points;
            const tripStopTimes = tripGtfsData.stop_times;
            const firstTripShapePoint = tripShapePoints[0];
            const lastTripShapePoint = tripShapePoints[tripShapePoints.length - 1];
            const timeScheduledTimestamp = DateTimeUtils_1.DateTimeUtils.getStopDateTimeForDayStart(
            // take stop arrival time if is point at stop
            thisClosestPoint.this_stop_sequence
                ? tripStopTimes[thisClosestPoint.this_stop_sequence - 1].arrival_time_seconds
                : thisClosestPoint.time_scheduled_seconds, startDayTimestamp);
            let timeDelayInSeconds = Math.round((currentPosition.properties.origin_timestamp.getTime() - timeScheduledTimestamp.getTime()) / 1000);
            // lets correct delay if it is at stop
            if (thisClosestPoint.this_stop_sequence) {
                const thisStopSequence = thisClosestPoint.this_stop_sequence;
                timeDelayInSeconds = this.getCorrectedTimeDelay(timeDelayInSeconds, context, currentPosition, thisStopSequence, {
                    departureTime: tripStopTimes[thisStopSequence - 1].departure_time_seconds,
                    arrivalTime: tripStopTimes[thisStopSequence - 1].arrival_time_seconds,
                });
                // delay can not be negative if the vehicle is at the first stop
                if (thisStopSequence === firstTripShapePoint.this_stop_sequence) {
                    timeDelayInSeconds = Math.max(timeDelayInSeconds, 0);
                }
            }
            let thisStopSequence = thisClosestPoint.this_stop_sequence;
            let lastStopSequence = thisClosestPoint.last_stop_sequence;
            let nextStopSequence = thisClosestPoint.next_stop_sequence;
            const statePosition = this.determineStatePosition(thisClosestPoint, lastTripShapePoint, context);
            const isTracked = statePosition === const_1.StatePositionEnum.AT_STOP || statePosition === const_1.StatePositionEnum.ON_TRACK;
            if (!isTracked) {
                if (statePosition === const_1.StatePositionEnum.AFTER_TRACK) {
                    // normal after track positions (without propagation) should always have delay null
                    timeDelayInSeconds = null;
                }
                thisStopSequence = lastTripShapePoint.this_stop_sequence;
                lastStopSequence = lastTripShapePoint.last_stop_sequence;
                nextStopSequence = null;
            }
            // save it for result
            const estimatedPoint = {
                id: currentPosition.properties.id,
                bearing: thisClosestPoint.bearing,
                shape_dist_traveled: thisClosestPoint.shape_dist_traveled,
                next_stop_id: nextStopSequence ? tripStopTimes[nextStopSequence - 1].stop_id : null,
                last_stop_id: tripStopTimes[lastStopSequence - 1].stop_id,
                next_stop_name: nextStopSequence ? tripStopTimes[nextStopSequence - 1].stop.stop_name : null,
                last_stop_name: tripStopTimes[lastStopSequence - 1].stop.stop_name,
                next_stop_sequence: nextStopSequence,
                last_stop_sequence: lastStopSequence,
                next_stop_arrival_time: nextStopSequence
                    ? DateTimeUtils_1.DateTimeUtils.getStopDateTimeForDayStart(tripStopTimes[nextStopSequence - 1].arrival_time_seconds, startDayTimestamp)
                    : null,
                last_stop_arrival_time: DateTimeUtils_1.DateTimeUtils.getStopDateTimeForDayStart(tripStopTimes[lastStopSequence - 1].arrival_time_seconds, startDayTimestamp),
                next_stop_departure_time: nextStopSequence
                    ? DateTimeUtils_1.DateTimeUtils.getStopDateTimeForDayStart(tripStopTimes[nextStopSequence - 1].departure_time_seconds, startDayTimestamp)
                    : null,
                last_stop_departure_time: DateTimeUtils_1.DateTimeUtils.getStopDateTimeForDayStart(tripStopTimes[lastStopSequence - 1].departure_time_seconds, startDayTimestamp),
                delay: timeDelayInSeconds,
                this_stop_id: (statePosition === const_1.StatePositionEnum.AT_STOP &&
                    thisStopSequence &&
                    tripStopTimes[thisStopSequence - 1].stop_id) ||
                    undefined,
                this_stop_name: (statePosition === const_1.StatePositionEnum.AT_STOP &&
                    thisStopSequence &&
                    tripStopTimes[thisStopSequence - 1].stop.stop_name) ||
                    undefined,
                this_stop_sequence: (statePosition === const_1.StatePositionEnum.AT_STOP && thisStopSequence) || undefined,
                last_stop_headsign: tripStopTimes[lastStopSequence - 1].stop_headsign || undefined,
                state_position: statePosition,
                state_process: const_1.StateProcessEnum.PROCESSED,
                valid_to: this.validToCalculator.getDefaultValidToAttribute(currentPosition.properties.origin_timestamp),
                is_tracked: isTracked,
            };
            return estimatedPoint;
        };
        this.determineStatePosition = (thisClosestPoint, lastTripPoint, context) => {
            if (thisClosestPoint.last_stop_sequence >= lastTripPoint.last_stop_sequence) {
                if (PositionStateDelayManager_1.PositionStateDelayManager.shouldPropagateAfterTrackDelay(context)) {
                    return const_1.StatePositionEnum.AFTER_TRACK_DELAYED;
                }
                return const_1.StatePositionEnum.AFTER_TRACK;
            }
            else if (thisClosestPoint.this_stop_sequence) {
                return const_1.StatePositionEnum.AT_STOP;
            }
            return const_1.StatePositionEnum.ON_TRACK;
        };
    }
};
exports.RegionalBusPositionsManager = RegionalBusPositionsManager;
exports.RegionalBusPositionsManager = RegionalBusPositionsManager = RegionalBusPositionsManager_1 = __decorate([
    (0, tsyringe_1.injectable)(),
    __param(0, (0, tsyringe_1.inject)(VPContainerToken_1.VPContainerToken.ValidToCalculator)),
    __param(1, (0, tsyringe_1.inject)(CoreToken_1.CoreToken.Logger)),
    __metadata("design:paramtypes", [ValidToCalculator_1.ValidToCalculator, Object])
], RegionalBusPositionsManager);
//# sourceMappingURL=RegionalBusPositionsManager.js.map