modules/shared/vehicle.mjs

// window.B2_top.Circle(Widget.GREEN, true, 20, 10, 50);
// window.B2_top.category = Widget.CATEGORY_ENTITY;
// CATEGORY_UNSEL_ALL
// CATEGORY_SEL_ALL// - green
// CATEGORY_UPDATE
// GETEGORY_GENERIC_2 //latent

import { OccupantPhysiology, OccupantProduct } from "./occupant.mjs";
import { OccupantVersion } from "./occupant_version.mjs";
import { WorkflowOccupant } from "./workflow_occupant.mjs";

export { ProtocolVehicle, VehicleOccupant };

class ProtocolVehicle {
    /**
     * Class to define a Vehicle with Occupants that can occupy each seat
     * @param {VehicleOccupant} driver array of OccupantProduct names in driver seat
     * @param {VehicleOccupant} front array of OccupantProduct names in front seat
     * @param {VehicleOccupant} behind_driver array of OccupantProduct names in behind_driver seat
     * @param {VehicleOccupant} behind_front array of OccupantProduct names in behind_front seat
     * @param {VehicleOccupant} middle array of OccupantProduct names in middle seat
     * @example
     * let vehicle = new ProtocolVehicle(driver, front, behind_driver, behind_front, middle);
     */
    constructor(driver, front, behind_driver, behind_front, middle) {
        this.driver = driver;
        this.front = front;
        this.behind_driver = behind_driver;
        this.behind_front = behind_front;
        this.middle = middle;
    }

    /** driver occupant names array
     * @type {VehicleOccupant} */
    get driver() {
        return this._driver;
    }
    set driver(new_driver) {
        this._driver = new_driver;
    }

    /** front occupant names array
     * @type {VehicleOccupant} */
    get front() {
        return this._front;
    }
    set front(new_front) {
        this._front = new_front;
    }

    /** behind_driver occupant names array
     * @type {VehicleOccupant} */
    get behind_driver() {
        return this._behind_driver;
    }
    set behind_driver(new_behind_driver) {
        this._behind_driver = new_behind_driver;
    }

    /** behind_front occupant names array
     * @type {VehicleOccupant} */
    get behind_front() {
        return this._behind_front;
    }
    set behind_front(new_behind_front) {
        this._behind_front = new_behind_front;
    }

    /** middle occupant names array
     * @type {VehicleOccupant} */
    get middle() {
        return this._middle;
    }
    set middle(new_middle) {
        this._middle = new_middle;
    }

    /**
     * @typedef {Object} ProductPhysiology
     * @property {string} product Occupant product type
     * @property {string} physiology Occupant physiology
     */

    /**
     * @typedef {Object} ProtocolVehicleJSON
     * @property {ProductPhysiology} driver Occupant product and physiology in driver seat
     * @property {ProductPhysiology} front Occupant product and physiology in front seat
     * @property {ProductPhysiology} behind_driver Occupant product and physiology in behind_driver seat
     * @property {ProductPhysiology} behind_front Occupant product and physiology in behind_front seat
     * @property {ProductPhysiology} middle Occupant product and physiology in middle seat
     */

    /**
     * constrcut a ProtocolVehicle from JSON
     * @param {ProtocolVehicleJSON} json
     * @return {ProtocolVehicle}
     */
    static FromJSON(json) {
        let vehicle = new ProtocolVehicle(
            new VehicleOccupant(
                WorkflowOccupant.DRIVER,
                WorkflowOccupant.LEFT,
                WorkflowOccupant.FRONT,
                json.driver.product,
                json.driver.physiology
            ),
            new VehicleOccupant(
                WorkflowOccupant.PASSENGER,
                WorkflowOccupant.RIGHT,
                WorkflowOccupant.FRONT,
                json.front.product,
                json.front.physiology
            ),
            new VehicleOccupant(
                WorkflowOccupant.PASSENGER,
                WorkflowOccupant.LEFT,
                WorkflowOccupant.REAR,
                json.behind_driver.product,
                json.behind_driver.physiology
            ),
            new VehicleOccupant(
                WorkflowOccupant.PASSENGER,
                WorkflowOccupant.RIGHT,
                WorkflowOccupant.REAR,
                json.behind_front.product,
                json.behind_front.physiology
            ),
            new VehicleOccupant(
                WorkflowOccupant.PASSENGER,
                WorkflowOccupant.MIDDLE,
                WorkflowOccupant.REAR,
                json.middle.product,
                json.middle.physiology
            )
        );

        return vehicle;
    }

    /**
     * get array of all the VehicleOccupants in the ProtocolVehicle
     * @returns {VehicleOccupant[]}
     * @example
     * let occupants = this.Occupants();
     */
    Occupants() {
        let vehicle = this;

        return [vehicle.driver, vehicle.behind_driver, vehicle.front, vehicle.behind_front, vehicle.middle];
    }

    /**
     * get array of all the VehicleOccupants in the ProtocolVehicle

     */

    /**
     * returns an array of valid (non-empty) VehicleOccupants that match all the passed criteria for position
     * side and front_rear.
     * Note that null can be passed for any of these criteria and it will not filter on that property
     * Note also that zero length array is returned if no matching occupants found
     * @param {?string} [position = null] Workflowoccupant.DRIVER or WorkflowOccupant.PASSENGER
     * @param {?string} [front_rear = null] front or rear row seat
     * @param {?string} [side = null] WorkflowOccupant.LEFT, WorkflowOccupant.RIGHT or WorkflowOccupant.MIDDLE
     * @param {string} [drive_side = VehicleOccupant.LHD] VehicleOccupant.LHD by default, but can set to VehicleOccupant.RHD. This is only used if side is not null
     * @returns {VehicleOccupant[]}
     * @example
     * let occupants = this.OnlyOccupants(WorkflowOccupant.PASSENGER, WorkflowOccupant.REAR);
     */
    OnlyOccupants(position = null, front_rear = null, side = null, drive_side = VehicleOccupant.LHD) {
        let occupants = this.Occupants();

        let output_occupants = [];

        for (let occupant of occupants) {
            if (!occupant.Empty()) {
                if (position != null) {
                    if (occupant.position != position) {
                        continue;
                    }
                }

                if (front_rear != null) {
                    if (occupant.front_rear != front_rear) {
                        continue;
                    }
                }

                if (side != null) {
                    if (occupant.GetSide(drive_side) != side) {
                        continue;
                    }
                }

                output_occupants.push(occupant);
            }
        }

        return output_occupants;
    }
}

class VehicleOccupant {
    /**
     * this class holds vehicle occupant data from the protocol json file
     * it also adds meta data e.g. side and position. Positions assume
     * left-hand drive (LHD) by default, but GetPosition method can be used
     * to return the correct positions if vehicle is right-hand drive (RHD).
     */

    /**
     *
     * @param {string} position
     * @param {string} side assumed WorkflowOccupant.LEFT
     * @param {string} front_rear
     * @param {?string} product taken from JSON
     * @param {?string} physiology taken from JSON
     * @example let driver = new VehicleOccupant(
                WorkflowOccupant.DRIVER,
                WorkflowOccupant.LEFT,
                WorkflowOccupant.FRONT,
                json.driver.product,
                json.driver.physiology
            )
     */
    constructor(position, side, front_rear, product, physiology) {
        this.position = position;
        this.side = side;
        this.front_rear = front_rear;
        this.product = product;
        this.physiology = physiology;
    }

    /** Left-hand drive (LHD) constant
     * @type {string} */
    static get LHD() {
        return "LHD";
    }

    /** Right-hand drive (RHD) constant
     * @type {string} */
    static get RHD() {
        return "RHD";
    }

    /**
     * if the hand drive is valid returns the value passed, otherwise prints warning and return VehicleOccupant.LHD
     * @param {string} drive_side
     * @returns {string}
     */
    static GetValidHandDrive(drive_side) {
        if (drive_side == VehicleOccupant.LHD || drive_side == VehicleOccupant.RHD) return drive_side;

        WarningMessage(
            `Vehicle hand drive ${drive_side} was not valid so cannot be set. Defaulting to ${VehicleOccupant.LHD}`
        );

        return VehicleOccupant.LHD;
    }

    /**
     * occupant may be empty if either product or physiology are null
     * @returns {boolean} empty
     */
    Empty() {
        if (this.position && this.physiology) return false;
        return true;
    }

    /**
     * Occupant position constant
     * @type {string} */
    get position() {
        return this._position;
    }
    set position(new_position) {
        if (WorkflowOccupant.Positions().indexOf(new_position) == -1) {
            throw new Error(`Invalid position: ${new_position} in VehicleOccupant constructor`);
        }

        this._position = new_position;
    }

    /**
     * Occupant side setter (no getter as need to use member function GetSide(<drive_side>))
     * @param {string} new_side */
    set side(new_side) {
        if (WorkflowOccupant.Sides().indexOf(new_side) == -1) {
            throw new Error(`Invalid side: ${new_side} in VehicleOccupant constructor`);
        }

        this._side = new_side;
    }

    /**
     * Occupant front/Rear constant
     * @type {string} */
    get front_rear() {
        return this._front_rear;
    }
    set front_rear(new_front_rear) {
        if (WorkflowOccupant.FrontRear().indexOf(new_front_rear) == -1) {
            throw new Error(`Invalid front_rear: ${new_front_rear} in VehicleOccupant constructor`);
        }

        this._front_rear = new_front_rear;
    }

    /** Occupant product
     * @type {?string|undefined} */
    get product() {
        return this._product;
    }
    set product(new_product) {
        if (!new_product) new_product = null;
        else if (!OccupantProduct.Valid(new_product)) {
            throw new Error(`Invalid product ${new_product} in VehicleOccupant constructor`);
        }
        this._product = new_product;
    }

    /** Occupant physiology
     * @type {?string|undefined} */
    get physiology() {
        return this._physiology;
    }
    set physiology(new_physiology) {
        if (!new_physiology) new_physiology = null;
        else if (!OccupantPhysiology.Valid(new_physiology)) {
            throw new Error(`Invalid physiology ${new_physiology} in VehicleOccupant constructor`);
        }
        this._physiology = new_physiology;
    }

    /**
     * Get the side of an occupant for a given drive side (LHD or RHD)
     * @param {string} [drive_side = VehicleOccupant.LHD] VehicleOccupant.LHD (default) or VehicleOccupant.RHD
     * @returns {string}
     */
    GetSide(drive_side = VehicleOccupant.LHD) {
        // Middle is always middle regardless of drive_side
        if (this._side == WorkflowOccupant.MIDDLE) return this._side;
        // If LHD, return occupant side unaltered
        if (drive_side == VehicleOccupant.LHD) return this._side;
        // If RHD, return the opposite
        if (this._side == WorkflowOccupant.LEFT) return WorkflowOccupant.RIGHT;
        return WorkflowOccupant.LEFT;
    }

    /**
     * returns an array of valid (supported) occupant names corresponding to
     * the occupant product and physiology properties
     * @returns {string[]} occupant names array
     */
    GetOccupantNames() {
        return OccupantVersion.GetOnly("ALL", this.product, this.physiology);
    }
}