/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */

import { Injectable } from '@angular/core';
import { FieldProvider } from '../models/dataview-dashboard.model';
import {
    DataExplorerField,
    EventProperty,
    EventPropertyPrimitive,
    EventPropertyUnion,
    FieldConfig,
    SourceConfig,
} from '@streampipes/platform-services';

@Injectable()
export class DataExplorerFieldProviderService {
    public generateFieldLists(sourceConfigs: SourceConfig[]): FieldProvider {
        const provider: FieldProvider = {
            allFields: [],
            numericFields: [],
            booleanFields: [],
            dimensionFields: [],
            nonNumericFields: [],
        };

        sourceConfigs.forEach((sourceConfig, sourceIndex) => {
            sourceConfig.queryConfig.fields
                .filter(field => field.selected)
                .forEach(field => {
                    this.addField(
                        sourceConfig.measureName,
                        sourceConfig.measure.eventSchema.eventProperties,
                        sourceIndex,
                        field,
                        provider,
                        sourceConfig.queryType === 'aggregated' ||
                            sourceConfig.queryType === 'single',
                    );
                });
        });

        return provider;
    }

    private addField(
        measure: string,
        eventProperties: EventPropertyUnion[],
        sourceIndex: number,
        fieldConfig: FieldConfig,
        provider: FieldProvider,
        useAggregations: boolean,
    ) {
        const property: EventPropertyUnion = eventProperties.find(
            p => p.runtimeName === fieldConfig.runtimeName,
        );

        if (!useAggregations) {
            this.addSingleField(
                measure,
                property,
                sourceIndex,
                fieldConfig,
                provider,
            );
        } else {
            fieldConfig.aggregations.forEach(agg =>
                this.addSingleField(
                    measure,
                    property,
                    sourceIndex,
                    fieldConfig,
                    provider,
                    agg,
                ),
            );
        }
    }

    private addSingleField(
        measure: string,
        property: EventPropertyUnion,
        sourceIndex: number,
        fieldConfig: FieldConfig,
        provider: FieldProvider,
        aggregation?: string,
    ) {
        const field: DataExplorerField = {
            runtimeName: fieldConfig.runtimeName,
            measure,
            aggregation,
            fullDbName: this.makeFullDbName(fieldConfig, aggregation),
            sourceIndex,
            fieldCharacteristics: {
                dimension: this.isDimensionProperty(property),
                numeric: this.isNumber(property) || aggregation === 'COUNT',
                binary: this.isBoolean(property),
                semanticTypes: property.domainProperties,
            },
        };
        provider.allFields.push(field);

        if (field.fieldCharacteristics.numeric) {
            provider.numericFields.push(field);
        } else {
            provider.nonNumericFields.push(field);
        }

        if (field.fieldCharacteristics.binary) {
            provider.booleanFields.push(field);
        }

        if (this.isTimestamp(property)) {
            provider.primaryTimestampField = field;
        }

        if (field.fieldCharacteristics.dimension) {
            provider.dimensionFields.push(field);
        }
    }

    public isDimensionProperty(p: EventProperty): boolean {
        return p.propertyScope === 'DIMENSION_PROPERTY';
    }

    public isBoolean(p: EventPropertyUnion): boolean {
        return (
            this.isPrimitive(p) &&
            (p as EventPropertyPrimitive).runtimeType ===
                'http://www.w3.org/2001/XMLSchema#boolean'
        );
    }

    public isString(p: EventPropertyUnion): boolean {
        return (
            this.isPrimitive(p) &&
            (p as EventPropertyPrimitive).runtimeType ===
                'http://www.w3.org/2001/XMLSchema#string'
        );
    }

    public isTimestamp(p: EventProperty) {
        return p.domainProperties.some(
            dp => dp === 'http://schema.org/DateTime',
        );
    }

    public getAddedFields(
        currentFields: DataExplorerField[],
        newFields: DataExplorerField[],
    ): DataExplorerField[] {
        return this.getMissingFields(newFields, currentFields);
    }

    public getRemovedFields(
        currentFields: DataExplorerField[],
        newFields: DataExplorerField[],
    ): DataExplorerField[] {
        return this.getMissingFields(currentFields, newFields);
    }

    public getMissingFields(
        origin: DataExplorerField[],
        target: DataExplorerField[],
    ): DataExplorerField[] {
        return origin.filter(
            field =>
                !target.find(
                    newField => newField.fullDbName === field.fullDbName,
                ),
        );
    }

    isNumber(p: EventPropertyUnion): boolean {
        if (this.isPrimitive(p)) {
            const runtimeType = (p as EventPropertyPrimitive).runtimeType;

            return (
                runtimeType === 'http://schema.org/Number' ||
                runtimeType === 'http://www.w3.org/2001/XMLSchema#float' ||
                runtimeType === 'http://www.w3.org/2001/XMLSchema#double' ||
                runtimeType === 'http://www.w3.org/2001/XMLSchema#integer' ||
                runtimeType === 'http://www.w3.org/2001/XMLSchema#long'
            );
        } else {
            return false;
        }
    }

    private isPrimitive(property: any): boolean {
        return (
            property instanceof EventPropertyPrimitive ||
            property['@class'] ===
                'org.apache.streampipes.model.schema.EventPropertyPrimitive'
        );
    }

    private makeFullDbName(
        fieldConfig: FieldConfig,
        aggregation?: string,
    ): string {
        const prefix = aggregation ? aggregation.toLowerCase() + '_' : '';
        return prefix + fieldConfig.runtimeName;
    }
}
