"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 __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 __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.PositionsRepository = void 0;
const const_1 = require("../../../../../schema-definitions/const");
const vehicle_positions_1 = require("../../../../../schema-definitions/vehicle-positions");
const models_1 = require("../../../../../schema-definitions/vehicle-positions/models");
const PositionDto_1 = require("../../../../../schema-definitions/vehicle-positions/models/PositionDto");
const connectors_1 = require("@golemio/core/dist/integration-engine/connectors");
const models_2 = require("@golemio/core/dist/integration-engine/models");
const golemio_errors_1 = require("@golemio/core/dist/shared/golemio-errors");
const golemio_validator_1 = require("@golemio/core/dist/shared/golemio-validator");
const sequelize_1 = __importStar(require("@golemio/core/dist/shared/sequelize"));
const const_2 = require("../../../../../const");
const PositionsMapper_1 = __importDefault(require("./helpers/PositionsMapper"));
class PositionsRepository extends models_2.PostgresModel {
    constructor() {
        super("VPPositionsRepository", {
            outputSequelizeAttributes: PositionDto_1.PositionDto.attributeModel,
            pgTableName: vehicle_positions_1.VehiclePositions.positions.pgTableName,
            pgSchema: const_1.PG_SCHEMA,
            savingType: "insertOrUpdate",
            sequelizeAdditionalSettings: {
                indexes: [
                    {
                        fields: ["trips_id"],
                        name: "vehiclepositions_positions_trips_id",
                    },
                    {
                        fields: ["origin_time"],
                        name: "vehiclepositions_positions_origin_time",
                    },
                ],
            },
        }, new golemio_validator_1.JSONSchemaValidator(vehicle_positions_1.VehiclePositions.positions.name + "ModelValidator", PositionDto_1.PositionDto.jsonSchema));
        /**
         * Get all trips with positions for given trip ids. Positions are ordered by created_at instead of origin_timestamp
         *   to ensure they are processed by the same order they are created
         *   (sometimes TCP common positions are received out of order)
         *
         * Example of processing of mismatched TCP common positions:
         *   Batch 1 - TCP position (event T) with origin_timestamp 10:00:01, created_at 10:00:04, not last position
         *     (position is at_stop, last stop sequence is 2, delay at last stop is null, is not served to the output API)
         *   Batch 1 - TCP position (event O) with origin_timestamp 10:00:00, created_at 10:00:05, last position
         *     (position is on_track, last stop sequence is 2, delay at last stop is 10 and is served to the output API)
         *   Batch 2 - TCP position (event T) with origin_timestamp 10:01:00, created_at 10:01:04, last position
         *     (position is on_track, last stop sequence is 2, delay at last stop is 10 and is served to the output API)
         *   Batch 3 - TCP position (event 0) with origin_timestamp 10:01:40, created_at 10:01:44, last position
         *     (position is on_track, last stop sequence is 3, delay at last stop is 20 and is served to the output API)
         */
        this.getPositionsForUpdateDelay = async (tripIds) => {
            // TODO - check that origin_time is not duplicate for tracking == 2.
            // const originTimeColumn = `"vehiclepositions_positions"."origin_time"`;
            // make sure to not run with empty array
            if (tripIds.length === 0) {
                return [];
            }
            const results = await this.tripsModel.findAll({
                attributes: [
                    // Sequelize.literal(`DISTINCT ON (${originTimeColumn}) ${originTimeColumn}`),
                    "id",
                    "gtfs_trip_id",
                    "gtfs_route_type",
                    "start_timestamp",
                    "end_timestamp",
                    "agency_name_scheduled",
                    "start_cis_stop_id",
                    "last_position_context",
                ],
                include: [
                    {
                        attributes: [
                            "bearing",
                            "lat",
                            "lng",
                            "origin_time",
                            "origin_timestamp",
                            "scheduled_timestamp",
                            "delay",
                            "is_tracked",
                            "id",
                            "shape_dist_traveled",
                            "is_canceled",
                            "state_position",
                            "state_process",
                            "tcp_event",
                            "this_stop_sequence",
                            "this_stop_id",
                            "last_stop_sequence",
                            "last_stop_id",
                            "last_stop_arrival_time",
                            "last_stop_departure_time",
                            "last_stop_headsign",
                            "next_stop_arrival_time",
                            "next_stop_departure_time",
                            "next_stop_id",
                            "next_stop_sequence",
                            "next_stop_name",
                        ],
                        model: this.sequelizeModel,
                        where: {
                            [sequelize_1.default.Op.or]: [
                                {
                                    lat: { [sequelize_1.default.Op.not]: null },
                                    lng: { [sequelize_1.default.Op.not]: null },
                                    state_process: {
                                        [sequelize_1.default.Op.in]: [
                                            const_2.StateProcessEnum.INPUT,
                                            const_2.StateProcessEnum.TCP_INPUT,
                                            const_2.StateProcessEnum.UDP_INPUT,
                                        ],
                                    },
                                },
                                {
                                    is_canceled: true,
                                    state_process: {
                                        [sequelize_1.default.Op.in]: [
                                            const_2.StateProcessEnum.INPUT,
                                            const_2.StateProcessEnum.TCP_INPUT,
                                            const_2.StateProcessEnum.UDP_INPUT,
                                        ],
                                    },
                                },
                            ],
                        },
                    },
                ],
                order: [
                    [{ model: this.sequelizeModel, as: "position" }, "created_at", "ASC"],
                    [{ model: this.sequelizeModel, as: "position" }, "is_tracked", "DESC"],
                ],
                raw: true,
                where: {
                    gtfs_trip_id: { [sequelize_1.default.Op.ne]: null },
                    id: { [sequelize_1.default.Op.any]: tripIds },
                },
            });
            // Sequlize return with raw==true flatten array of results, nest==true is available for Sequelize ver >5 only
            // We return objects of positions grouped by trips_id
            try {
                const positionsToReturn = results.reduce((p, c) => {
                    let pIndex = p.findIndex((e) => e.id === c.id);
                    if (pIndex === -1) {
                        p.push({
                            agency_name_scheduled: c.agency_name_scheduled,
                            gtfs_trip_id: c.gtfs_trip_id,
                            gtfs_route_type: c.gtfs_route_type,
                            positions: [],
                            last_position_context: c.last_position_context,
                            start_cis_stop_id: c.start_cis_stop_id,
                            start_timestamp: c.start_timestamp,
                            end_timestamp: c.end_timestamp,
                            id: c.id,
                        });
                        pIndex = p.findIndex((e) => e.id === c.id);
                    }
                    p[pIndex].positions.push({
                        bearing: c["vehiclepositions_positions.bearing"],
                        delay: c["vehiclepositions_positions.delay"],
                        id: c["vehiclepositions_positions.id"],
                        lat: parseFloat(c["vehiclepositions_positions.lat"]) || null,
                        lng: parseFloat(c["vehiclepositions_positions.lng"]) || null,
                        origin_time: c["vehiclepositions_positions.origin_time"],
                        origin_timestamp: c["vehiclepositions_positions.origin_timestamp"],
                        scheduled_timestamp: c["vehiclepositions_positions.scheduled_timestamp"] ?? null,
                        shape_dist_traveled: parseFloat(c["vehiclepositions_positions.shape_dist_traveled"]) || null,
                        is_tracked: c["vehiclepositions_positions.is_tracked"],
                        is_canceled: c["vehiclepositions_positions.is_canceled"],
                        state_position: c["vehiclepositions_positions.state_position"],
                        state_process: c["vehiclepositions_positions.state_process"],
                        tcp_event: c["vehiclepositions_positions.tcp_event"],
                        this_stop_sequence: c["vehiclepositions_positions.this_stop_sequence"],
                        this_stop_id: c["vehiclepositions_positions.this_stop_id"],
                        last_stop_sequence: c["vehiclepositions_positions.last_stop_sequence"],
                        last_stop_id: c["vehiclepositions_positions.last_stop_id"],
                        last_stop_arrival_time: c["vehiclepositions_positions.last_stop_arrival_time"],
                        last_stop_departure_time: c["vehiclepositions_positions.last_stop_departure_time"],
                        last_stop_headsign: c["vehiclepositions_positions.last_stop_headsign"],
                        next_stop_arrival_time: c["vehiclepositions_positions.next_stop_arrival_time"],
                        next_stop_departure_time: c["vehiclepositions_positions.next_stop_departure_time"],
                        next_stop_id: c["vehiclepositions_positions.next_stop_id"],
                        next_stop_sequence: c["vehiclepositions_positions.next_stop_sequence"],
                        next_stop_name: c["vehiclepositions_positions.next_stop_name"],
                    });
                    return p;
                }, []);
                return positionsToReturn;
            }
            catch (err) {
                throw new golemio_errors_1.GeneralError(`Error while transforming data from db for batch. Trip count ${results.length}.`, this.name, err);
            }
        };
        this.upsertCommonRunPosition = async ({ run, run_message }, gtfsTrip, isCurrent = true) => {
            try {
                const [record] = await this.sequelizeModel.upsert(PositionsMapper_1.default.mapCommonRunToDto(run_message, isCurrent, gtfsTrip, run), {
                    returning: true,
                });
                return record.get({ plain: true });
            }
            catch (err) {
                throw new golemio_errors_1.GeneralError("upsertCommonRunPosition: Error while saving to database.", this.name, err);
            }
        };
        this.upsertMetroRunPosition = async (runInput, gtfsTrip, isCurrent = true) => {
            try {
                const [record] = await this.sequelizeModel.upsert(PositionsMapper_1.default.mapMetroRunToDto(isCurrent, gtfsTrip, runInput), {
                    returning: true,
                });
                return record.get({ plain: true });
            }
            catch (err) {
                throw new golemio_errors_1.GeneralError("upsertMetroRunPosition: Error while saving to database.", this.name, err);
            }
        };
        this.deleteNHoursOldData = async (hours) => {
            try {
                return await this.sequelizeModel.destroy({
                    where: {
                        updated_at: {
                            [sequelize_1.Op.lt]: sequelize_1.default.literal(`NOW() - INTERVAL '${hours} HOURS'`),
                        },
                    },
                });
            }
            catch (err) {
                throw new golemio_errors_1.GeneralError("Error while purging old data", this.constructor.name, err);
            }
        };
        this.createEntity = async (positionDto) => {
            try {
                return await this.sequelizeModel.create(positionDto, {
                    returning: true,
                });
            }
            catch (err) {
                throw new golemio_errors_1.GeneralError("createEntity: error while saving data", this.name, err);
            }
        };
        /**
         * Invalidate positions for trips with stop sequence greater than given stop sequence
         * Used when a vehicle repeats some part of the trip
         * @param tripWithStopSequenceTuples - array of tuples with trip id and stop sequence
         * @returns number of invalidated positions
         */
        this.invalidatePositionsAfterStopSequence = async (tripWithStopSequenceTuples) => {
            if (tripWithStopSequenceTuples.length === 0) {
                return 0;
            }
            const whereOptions = {
                state_process: const_2.StateProcessEnum.PROCESSED,
                [sequelize_1.Op.or]: tripWithStopSequenceTuples.map(([tripId, stopSequence]) => ({
                    trips_id: tripId,
                    last_stop_sequence: {
                        [sequelize_1.Op.gt]: stopSequence,
                    },
                })),
            };
            try {
                const [count] = await this.sequelizeModel.update({
                    state_process: const_2.StateProcessEnum.INVALIDATED,
                    state_position: const_2.StatePositionEnum.UNKNOWN,
                }, {
                    where: whereOptions,
                });
                return count;
            }
            catch (err) {
                throw new golemio_errors_1.GeneralError("invalidatePositions: error while updating data", this.name, err);
            }
        };
        this.tripsModel = connectors_1.PostgresConnector.getConnection().define(models_1.VPTripsModel.TABLE_NAME, models_1.VPTripsModel.attributeModel, {
            schema: const_1.PG_SCHEMA,
        });
        this.tripsModel.hasMany(this.sequelizeModel, { foreignKey: "trips_id", sourceKey: "id" });
    }
}
exports.PositionsRepository = PositionsRepository;
//# sourceMappingURL=PositionsRepository.js.map