"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.LocationService = void 0;
const tslib_1 = require("tslib");
const core_1 = require("@loopback/core");
const lodash_1 = require("lodash");
const helper_1 = require("../../helper");
const models_1 = require("../../models");
const services_1 = require("../../services");
const action_entity_service_1 = require("../../services/action-entity/action-entity.service");
const transient_entity_service_1 = require("../../services/transient-entity/transient-entity.service");
const uri_complete_service_1 = require("../../services/uri-complete/uri-complete.service");
const location_const_1 = require("./location.const");
let LocationService = class LocationService {
    constructor(actionEntityService, objectTreeService, objectNodeService, objectTypeService, transientEntityService, insideRestService, uriCompleteService) {
        this.actionEntityService = actionEntityService;
        this.objectTreeService = objectTreeService;
        this.objectNodeService = objectNodeService;
        this.objectTypeService = objectTypeService;
        this.transientEntityService = transientEntityService;
        this.insideRestService = insideRestService;
        this.uriCompleteService = uriCompleteService;
        this.locatableItems = [];
        this.locationReferences = [];
        this.actionEntityService.registerNewMethodFunction(location_const_1.LOCATION_PROVIDER, LocationService.name, 'Propagate location to resources', models_1.EntityName.objectNode, 'propagateLocation', location_const_1.LOCATABLE_ITEMS_HOLDER_TYPE.name, this.propagateLocation.bind(this), 'update');
        this.actionEntityService.registerNewMethodFunction(location_const_1.LOCATION_PROVIDER, LocationService.name, 'Set resources location', models_1.EntityName.objectNode, 'setResourcesLocation', location_const_1.LOCATABLE_ITEMS_HOLDER_TYPE.name, this.setResourcesLocation.bind(this), 'update');
        this.transientEntityService.registerTransientEntityTypeFunction(location_const_1.LOCATION_PROVIDER, LocationService.name, 
        //      'Complete paragraphsTemplateChoice json schema definition with paragraphsTemplateChoice from referenced template and add its conditional paragraph template configuration',
        'Complete locatable items holder with propagate location method', models_1.EntityName.objectNode, location_const_1.LOCATABLE_ITEMS_HOLDER_TYPE.name, this.completeLocatableItemsHolderNode.bind(this));
        this.actionEntityService.registerNewMethodFunction(location_const_1.LOCATION_PROVIDER, LocationService.name, 'Update location from resources location', models_1.EntityName.objectNode, 'setLocationReference', location_const_1.LOCATABLE_ITEMS_HOLDER_TYPE.name, this.setLocationReference.bind(this), 'update');
        this.transientEntityService.registerTransientEntityTypeFunction(location_const_1.LOCATION_PROVIDER, LocationService.name, 
        //      'Complete paragraphsTemplateChoice json schema definition with paragraphsTemplateChoice from referenced template and add its conditional paragraph template configuration',
        'Complete location reference holder with set location reference method', models_1.EntityName.objectNode, location_const_1.LOCATION_REFERENCE_TYPE.name, this.completeLocationReferenceNode.bind(this));
    }
    findMedian(inputArray) {
        inputArray.sort();
        const inputLength = inputArray.length;
        const middleIndex = Math.floor(inputLength / 2);
        const oddLength = inputLength % 2 !== 0;
        let median;
        if (oddLength) {
            // if array length is odd -> return element at middleIndex
            median = inputArray[middleIndex];
        }
        else {
            median = (inputArray[middleIndex] + inputArray[middleIndex - 1]) / 2;
        }
        return median;
    }
    async setLocationReference(objectNode, args, ctx) {
        const locationReferenceFields = await this.getLocationReferenceFields(objectNode, ctx);
        if (0 === locationReferenceFields.length) {
            throw helper_1.ApplicationError.incompatible({
                method: 'setLocationReference',
                objectType: objectNode.objectTypeId,
            });
        }
        const objectTree = await this.objectTreeService.loadTree(objectNode.id, ctx);
        const foundLocations = await this.getExistingLocations(objectTree, ctx);
        if (0 === foundLocations.length) {
            throw helper_1.ApplicationError.notFound({ [objectNode.title]: 'localized items' });
        }
        const locations = foundLocations
            .map((location) => location.split(','))
            .filter((locationParts) => 2 === locationParts.length)
            .map((locationParts) => [
            parseFloat(locationParts[0]),
            parseFloat(locationParts[1]),
        ]);
        if (0 === locations.length) {
            throw helper_1.ApplicationError.notFound({ [objectNode.title]: 'localized items' });
        }
        const newLocation = '' +
            this.findMedian(locations.map((parts) => parts[0])) +
            ',' +
            this.findMedian(locations.map((parts) => parts[1]));
        const nodeUpdate = {};
        for (const locationReferenceField of locationReferenceFields) {
            nodeUpdate[locationReferenceField.locationField] = newLocation;
            if (locationReferenceField.locationConditionField &&
                !nodeUpdate[locationReferenceField.locationConditionField]) {
                nodeUpdate[locationReferenceField.locationConditionField] =
                    objectNode.title;
            }
        }
        return await this.objectNodeService.modifyById(objectNode.id, nodeUpdate, ctx, true);
    }
    async getExistingLocations(objectTree, ctx, foundLocations = []) {
        const locatableItems = await this.getLocatableItems(objectTree.treeNode, ctx);
        for (const localizedItem of locatableItems.fillPositions) {
            foundLocations.push(localizedItem.objectNode[localizedItem.locationField]);
        }
        if (lodash_1.isArray(objectTree.children)) {
            for (const subTree of objectTree.children) {
                const subTreePosition = await this.getPositionFromNode(subTree.treeNode, ctx);
                if (!subTreePosition) {
                    await this.getExistingLocations(subTree, ctx, foundLocations);
                }
            }
        }
        return foundLocations;
    }
    async completeLocationReferenceNode(objectNode, ctx) {
        var _a;
        const locationReferenceFields = await this.getLocationReferenceFields(objectNode, ctx);
        if (0 < locationReferenceFields.length) {
            if (!objectNode.entityCtx) {
                objectNode.entityCtx = { entityType: models_1.EntityName.objectNode };
            }
            if (!((_a = objectNode.entityCtx) === null || _a === void 0 ? void 0 : _a.actions)) {
                objectNode.entityCtx.actions = {};
            }
            if (!objectNode.entityCtx.actions.methods) {
                objectNode.entityCtx.actions.methods = [];
            }
            objectNode.entityCtx.actions.methods.push({
                methodId: 'setLocationReference',
                methodName: 'Localize this event from images geolocation',
                actionName: 'Set location',
                parameters: {
                    properties: {},
                },
                icon: 'far fa-map',
            });
        }
    }
    registerLocationProvider(contributor) {
        if (contributor.locatableItems) {
            this.locatableItems.push(...contributor.locatableItems);
        }
        if (contributor.locationReferences) {
            this.locationReferences.push(...contributor.locationReferences);
        }
    }
    async completeLocatableItemsHolderNode(objectNode, ctx) {
        var _a;
        if (!objectNode.entityCtx) {
            objectNode.entityCtx = { entityType: models_1.EntityName.objectNode };
        }
        if (!((_a = objectNode.entityCtx) === null || _a === void 0 ? void 0 : _a.actions)) {
            objectNode.entityCtx.actions = {};
        }
        if (!objectNode.entityCtx.actions.methods) {
            objectNode.entityCtx.actions.methods = [];
        }
        const hasLocation = !!(await this.getPositionFromNode(objectNode, ctx));
        if (hasLocation) {
            objectNode.entityCtx.actions.methods.push({
                methodId: 'propagateLocation',
                methodName: 'Propagate location to images',
                actionName: 'Propagate location',
                parameters: {
                    properties: {
                        forced: {
                            title: 'replace any existing position',
                            type: 'boolean',
                        },
                        recursively: {
                            title: 'propagate recursively any known event position',
                            type: 'boolean',
                        },
                    },
                },
                icon: 'far fa-map',
            });
        }
        else {
            objectNode.entityCtx.actions.methods.push({
                methodId: 'setResourcesLocation',
                methodName: 'Set images location',
                actionName: 'Set location',
                parameters: {
                    properties: {
                        locationPosition: {
                            title: 'Location position',
                            type: 'string',
                            'x-schema-form': {
                                type: 'position',
                            },
                        },
                        forced: {
                            title: 'replace any existing position',
                            type: 'boolean',
                        },
                        recursively: {
                            title: 'propagate recursively any known event position',
                            type: 'boolean',
                        },
                    },
                },
                icon: 'far fa-map',
            });
        }
    }
    async setPosition(objectNode, position, forced, recursively, ctx) {
        const objectTree = await this.objectTreeService.loadTree(objectNode.id, ctx);
        await this.setPositions(objectTree, position, forced, recursively, ctx);
    }
    async getLocatableItems(objectNode, ctx) {
        const result = { emptyPositions: [], fillPositions: [] };
        const implementedTypes = await this.objectTypeService.getImplementedTypes(objectNode.objectTypeId);
        for (const locatableItem of this.locatableItems) {
            if (implementedTypes.includes(locatableItem.objectType)) {
                if (locatableItem.treeItemsConditionField) {
                    if (!lodash_1.isArray(objectNode[locatableItem.treeItemsConditionField]) ||
                        0 === objectNode[locatableItem.treeItemsConditionField].length) {
                        break;
                    }
                }
                if (locatableItem.treeItemsField) {
                    const tree = await this.insideRestService.read(this.uriCompleteService.getUri(locatableItem.entityType, objectNode.id, ctx), ctx);
                    if (lodash_1.isArray(tree[locatableItem.treeItemsField])) {
                        for (const resourceTree of tree[locatableItem.treeItemsField]) {
                            if (resourceTree.treeNode &&
                                !resourceTree.treeNode[locatableItem.locationField]) {
                                result.emptyPositions.push({
                                    locationField: locatableItem.locationField,
                                    objectNode: resourceTree.treeNode,
                                });
                            }
                            else if (resourceTree.treeNode) {
                                result.fillPositions.push({
                                    locationField: locatableItem.locationField,
                                    objectNode: resourceTree.treeNode,
                                });
                            }
                        }
                    }
                }
            }
        }
        return result;
    }
    async setPositions(objectTree, position, forced, recursively, ctx) {
        if (position) {
            const locatableItems = await this.getLocatableItems(objectTree.treeNode, ctx);
            for (const locatableItem of locatableItems.emptyPositions) {
                await this.objectNodeService.modifyById(locatableItem.objectNode.id, {
                    [locatableItem.locationField]: position,
                }, services_1.CurrentContext.get(ctx), true);
            }
            if (forced) {
                for (const locatableItem of locatableItems.fillPositions) {
                    await this.objectNodeService.modifyById(locatableItem.objectNode.id, {
                        [locatableItem.locationField]: position,
                    }, services_1.CurrentContext.get(ctx), true);
                }
            }
        }
        if (lodash_1.isArray(objectTree.children)) {
            for (const subTree of objectTree.children) {
                const subTreePosition = await this.getPositionFromNode(subTree.treeNode, ctx);
                if (!subTreePosition) {
                    await this.setPositions(subTree, position, forced, recursively, ctx);
                }
                else if (recursively) {
                    await this.setPositions(subTree, subTreePosition, forced, recursively, ctx);
                }
            }
        }
    }
    async setResourcesLocation(objectNode, args, ctx) {
        if (!args.locationPosition && !args.recursively) {
            throw helper_1.ApplicationError.missingParameter('locationPosition');
        }
        await this.setPosition(objectNode, args.locationPosition, !!args.forced, !!args.recursively, ctx);
    }
    async getLocationReferenceFields(objectNode, ctx) {
        const result = [];
        const implementedTypes = await this.objectTypeService.getImplementedTypes(objectNode.objectTypeId);
        for (const locationReference of this.locationReferences) {
            if (implementedTypes.includes(locationReference.objectType)) {
                result.push({
                    locationField: locationReference.locationField,
                    locationConditionField: locationReference.locationConditionField,
                });
            }
        }
        return result;
    }
    async getPositionFromNode(objectNode, ctx) {
        const locationReferenceFields = await this.getLocationReferenceFields(objectNode, ctx);
        let locationPosition = undefined;
        for (const locationreference of locationReferenceFields) {
            if (locationreference.locationField in objectNode &&
                !!objectNode[locationreference.locationField] &&
                (!locationreference.locationConditionField ||
                    !!objectNode[locationreference.locationConditionField])) {
                locationPosition = objectNode[locationreference.locationField];
                break;
            }
        }
        return locationPosition;
    }
    async propagateLocation(objectNode, args, ctx) {
        const locationPosition = await this.getPositionFromNode(objectNode, ctx);
        if (!locationPosition && !args.recursively) {
            throw helper_1.ApplicationError.missingParameter('locationPosition');
        }
        await this.setPosition(objectNode, locationPosition, !!args.forced, !!args.recursively, ctx);
    }
};
LocationService = tslib_1.__decorate([
    tslib_1.__param(0, core_1.service(action_entity_service_1.ActionEntityService)),
    tslib_1.__param(1, core_1.service(services_1.ObjectTreeService)),
    tslib_1.__param(2, core_1.service(services_1.ObjectNodeService)),
    tslib_1.__param(3, core_1.service(services_1.ObjectTypeService)),
    tslib_1.__param(4, core_1.service(transient_entity_service_1.TransientEntityService)),
    tslib_1.__param(5, core_1.service(services_1.InsideRestService)),
    tslib_1.__param(6, core_1.service(uri_complete_service_1.UriCompleteService)),
    tslib_1.__metadata("design:paramtypes", [action_entity_service_1.ActionEntityService,
        services_1.ObjectTreeService,
        services_1.ObjectNodeService,
        services_1.ObjectTypeService,
        transient_entity_service_1.TransientEntityService,
        services_1.InsideRestService,
        uri_complete_service_1.UriCompleteService])
], LocationService);
exports.LocationService = LocationService;
//# sourceMappingURL=location.service.js.map