import { Component, OnInit, Input,
    ChangeDetectionStrategy, ChangeDetectorRef } from '@angular/core'
import { UntypedFormGroup, UntypedFormControl } from '@angular/forms'

import { map } from 'rxjs/operators'
import { forkJoin, combineLatest } from 'rxjs'

import { MapControlService } from '../../map/map-control.service'
import { FullInfo, InfoDataService, TaggedLayer } from '../info-data.service'
import { Tag, TagsPluginsService } from './tags.service'
import { AuthenticationService } from '../../_services/authentication.service'

@Component({
    selector: 'gisaf-tag-form',
    templateUrl: './tag-form.component.html',
    styleUrls: ['./tag-form.component.css'],
    changeDetection: ChangeDetectionStrategy.OnPush,
})
export class TagFormComponent implements OnInit {
    @Input() source: FullInfo | TaggedLayer[]
    formGroup: UntypedFormGroup
    availableKeys: string[] = []
    plugins: Object = {}
    pluginsFormGroup: UntypedFormGroup

    constructor(
        protected infoDataService: InfoDataService,
        protected mapControlService: MapControlService,
        public tagsPluginsService: TagsPluginsService,
        private authenticationService: AuthenticationService,
        private cdr: ChangeDetectorRef,
    ) {}

    ngOnInit() {
        // Set an empty formGroup
        this.formGroup = new UntypedFormGroup({
            key: new UntypedFormControl(''),
            value: new UntypedFormControl(''),
        })

        if (this.source) {
            if (this.source instanceof FullInfo) {
                this.fetchSingle()
            }
            else {
                this.fetchSelection()
            }
        }

        this.infoDataService.getTagKeys().subscribe(
            tags => {
                this.availableKeys = tags
                this.cdr.markForCheck()
            }
        )
    }

    get storeNames(): string[] {
        if (this.source instanceof FullInfo) {
            let source = <FullInfo>this.source
            return [source.modelInfo.store]
        }
        else {
            return Object.keys(this.mapControlService.selection.value)
        }
    }

    submit() {
        if (this.source instanceof FullInfo) {
            this.submitSingle()
        }
        else {  // source: TaggedLayer[]
            this.submitSelection()
        }
    }

    submitPlugins() {
        let newTags = Object.entries(this.pluginsFormGroup.value).filter(
            ([key, value]) => !!value
        )
        let keys: string[] = newTags.map(kv => kv[0])
        let values: string[] = newTags.map(kv => <string>kv[1])
        if (this.source instanceof FullInfo) {
            this.submitPluginsSingle(keys, values)
        }
        else {  // source: TaggedLayer[]
            this.submitPluginsSelection(keys, values)
        }
    }

    /* Below, implementations for fetch, submit simple tag and plugin tags, for single feature selection */

    protected fetchSingle() {
        combineLatest([
            this.infoDataService.dataProviderService$,
            this.tagsPluginsService.getTagsPlugins()
        ]).pipe(map(
            ([fullInfo, stores]) => {
                this.pluginsFormGroup = new UntypedFormGroup({})
                for (let store of stores.stores.filter(
                    store => store.store == fullInfo.modelInfo.store
                )) {
                    for (let tagKey of store.tagActions) {
                        if (tagKey.actions.map(
                            // FIXME: isAuthorized is async!!
                            action => this.authenticationService.isAuthorized(action.roles)
                        ).filter(i => i).length > 0) {
                            this.pluginsFormGroup.addControl(
                                tagKey.domain ? tagKey.domain + ':' + tagKey.key : tagKey.key,
                                new UntypedFormControl()
                            )
                        }
                    }
                }
                this.cdr.markForCheck()
            }
        )).subscribe()
    }

    submitSingle() {
        let source = <FullInfo>this.source
        this.infoDataService.createTag(source, this.formGroup.value).subscribe(
            (tags: Tag[]) => {
                source.featureInfo.tags = tags
                this.infoDataService.dataProviderService.next(source)
                // Update the search, forcing refreshing the tags
                this.mapControlService.search.next(true)
                this.cdr.markForCheck()
            }
        )
    }

    submitPluginsSingle(keys: string[], values: string[]) {
        let source = <FullInfo>this.source
        this.tagsPluginsService.submit(
            {
                [source.modelInfo.store]: [source.featureInfo.id]
            },
            keys,
            values
        ).pipe(map(
            (tags: Tag[]) => {
                for (let tag of tags) {
                    let existing_tags = source.featureInfo.tags.filter(
                        existing_tag => existing_tag.key == tag.key
                    )
                    for (let existing_tag of existing_tags) {
                        // Remove any existing tag with the same key
                        source.featureInfo.tags.splice(source.featureInfo.tags.indexOf(existing_tag), 1)
                    }
                    source.featureInfo.tags.push(tag) //new Tag(key, value))
                }
            }
        )).subscribe(
            resp => {
                this.infoDataService.dataProviderService.next(source)
                // Update the search, forcing refreshing the tags
                this.mapControlService.search.next(true)
                // Trick to refresh the tags on the map
                if (this.mapControlService.hasTags.value) {
                    this.mapControlService.hasTags.next(true)
                }
                this.cdr.markForCheck()
            }
        )
    }

    /* Below, implementations for fetch, submit simple tag and plugin tags, for selection */

    protected fetchSelection() {
        combineLatest([
            this.mapControlService.selection$,
            this.tagsPluginsService.getTagsPlugins()
        ]).pipe(map(
            ([selection, stores]) => {
                for (let store of stores.stores.filter(
                    store => selection.hasOwnProperty(store.store)
                )) {
                    for (let tagKey of store.tagActions) {
                        this.pluginsFormGroup.addControl(tagKey.key, new UntypedFormControl())
                    }
                }
                this.cdr.markForCheck()
            }
        )).subscribe()
    }

    private updateTags(tags: Tag[]) {
        // Update infoDataService.taggedFeaturesSelectionService
        let taggedFeatures = this.infoDataService.taggedFeaturesSelectionService.value
        tags.forEach(
            tag => taggedFeatures.forEach(
                taggedLayer => taggedLayer.features.forEach(
                    taggedFeature => {
                        // Update existing tags
                        taggedFeature.tags.filter(
                            ftag => tag.key == ftag.key
                        ).map(
                            ftag => ftag.value = tag.value
                        )
                        // Add new tag
                        if (taggedFeature.tags.findIndex(ftag => ftag.key == tag.key) == -1) {
                            taggedFeature.tags.push(tag)
                        }
                    }
                )
            )
        )
        this.infoDataService.taggedFeaturesSelectionService.next(taggedFeatures)
        // Update the search, forcing refreshing the tags
        this.mapControlService.search.next(true)
        this.cdr.markForCheck()
    }

    submitSelection() {
        let source = this.mapControlService.selection.value
        this.infoDataService.createTags(
            [this.formGroup.value['key']],
            [this.formGroup.value['value']],
            source,
        ).subscribe(
            (tags: Tag[]) => this.updateTags(tags)
        )
    }

    submitPluginsSelection(keys: string[], values: string[]) {
        let source = this.mapControlService.selection.value
        //features[source.modelInfo.store] = [source.featureInfo.id]
        this.tagsPluginsService.submit(
            source,
            keys,
            values,
        ).subscribe(
            (tags: Tag[]) => this.updateTags(tags)
        )
    }
}
