import { Component, Input, ViewChild } from '@angular/core';

import { NestedTreeControl } from '@angular/cdk/tree'
import { MatTreeNestedDataSource } from '@angular/material/tree'

import { Tag } from '../info-tags/tags.service'
import { TagFormComponent } from '../info-tags/tag-form.component'
import { MapControlService } from '../../map/map-control.service'
import { InfoDataService, TaggedLayer, TaggedFeature, Feature } from '../info-data.service'

export class Node {
    expanded: Boolean = true
    hasDelete: Boolean = false
    hasInfo: Boolean = false
    getChildren() {
        return []
    }
}

export class FeatureTree extends Node {
    constructor(
        public id: Number,
        public storeTree: StoreTree,
    ) {
        super()
        this.hasDelete = true
        this.hasInfo = true
    }
    label = "Feature"
    getLabel(): string {
        return this.id.toString()
    }
    getStore(): string {
        return this.storeTree.store
    }
    getValue(): string {
        return this.storeTree.tagValueTree.value
    }
    getKey(): string {
        return this.storeTree.tagValueTree.tagKeyTree.key
    }
}

export class StoreTree extends Node {
    constructor(
        public store: string,
        public tagValueTree: TagValueTree,
        public features: FeatureTree[] = []
    ) {
        super()
        this.expanded = false
    }
    label = 'Store (layer)'
    getLabel(): string {
        return this.store + ' (' + this.features.length + ')'
    }
    addNewFeature(id: Number): void {
        this.features.push(new FeatureTree(id, this))
    }
    getChildren() {
        return this.features
    }
}

export class TagValueTree extends Node {
    constructor(
        public value: string,
        public tagKeyTree: TagKeyTree,
        public stores: StoreTree[] = []
    ) {
        super()
    }
    label = 'Tag value'
    addNewStore(store: string): StoreTree {
        if (!this.stores.map(storeTree => storeTree.store).includes(store)) {
            let storeTree = new StoreTree(store, this)
            this.stores.push(storeTree)
            return storeTree
        }
        else {
            return this.stores.find(storeTree => storeTree.store == store)
        }
    }
    getChildren() {
        return this.stores
    }
    getLabel(): string {
        return this.value
    }
}

export class TagKeyTree extends Node {
    constructor(
        public key: string,
        public tagTree: TagTree,
        public values: TagValueTree[] = []
    ) {
        super()
    }
    label = 'Tag key'
    addNewValue(value: string): TagValueTree {
        if (!this.values.map(tagKeyValue => tagKeyValue.value).includes(value)) {
            let tagKeyValue = new TagValueTree(value, this)
            this.values.push(tagKeyValue)
            return tagKeyValue
        }
        else {
            return this.values.find(tagKeyValues => tagKeyValues.value == value)
        }
    }
    getChildren() {
        return this.values
    }
    getLabel(): string {
        return this.key
    }
}

export class TagTree extends Node {
    constructor(
        public keys: TagKeyTree[] = [],
    ) {
        super()
    }
    addNewKey(key: string): TagKeyTree {
        if (!this.keys.map(tagKeyTree => tagKeyTree.key).includes(key)) {
            let tagKeyTree = new TagKeyTree(key, this)
            this.keys.push(tagKeyTree)
            return tagKeyTree
        }
        else {
            return this.keys.find(tagKeyTree => tagKeyTree.key == key)
        }
    }
    getChildren() {
        return this.keys
    }
}


@Component({
    selector: 'gisaf-info-selection-tags',
    templateUrl: './info-selection-tags.component.html',
    styleUrls: ['./info-selection-tags.component.css']
})
export class InfoSelectionTagsComponent {
    /* Component showing information for the selection */
    @ViewChild(TagFormComponent, {static: true}) form: TagFormComponent
    @Input() selection: TaggedLayer[]
    treeControl: NestedTreeControl<Node>
    nestedDataSource: MatTreeNestedDataSource<Node>

    constructor(
        private infoDataService: InfoDataService,
        private mapControlService: MapControlService,
    ) {
        this.treeControl = new NestedTreeControl<TagTree>(this.getChildren)
        this.treeControl.isExpandable = (node => {
            if (node.expanded) {this.treeControl.expand(node)}
            return false
        })
        this.nestedDataSource = new MatTreeNestedDataSource()
    }

    ngOnInit() {
        this.mapControlService.selection$.subscribe(
            selection => {
                this.infoDataService.getTaggedFeatures(selection).subscribe()
            }
        )

        this.infoDataService.taggedFeaturesSelectionService$.subscribe(
            (taggedLayers: TaggedLayer[]) => {
                let tagTree: TagTree = this._convertSelectionToTagTree(taggedLayers)
                this.nestedDataSource.data = tagTree.keys
            }
        )
    }

    getChildren(node: Node) {
        return node.getChildren()
    }

    hasChild = (_: number, node: Node) => node.getChildren().length > 0

    private _convertSelectionToTagTree(taggedLayers: TaggedLayer[]): TagTree {
        let tagTree = new TagTree()
        let tagKeyTree: TagKeyTree
        let tagValueTree: TagValueTree
        let storeTree: StoreTree
        taggedLayers.forEach(
            taggedLayer => {
                taggedLayer.features.forEach(
                    (feature: TaggedFeature) => {
                        if (feature.tags.length == 0) {
                            tagKeyTree = tagTree.addNewKey('[No tag]')
                            tagValueTree = tagKeyTree.addNewValue('-')
                            storeTree = tagValueTree.addNewStore(taggedLayer.store)
                            storeTree.addNewFeature(feature.id)
                        }
                        else {
                            feature.tags.forEach(
                                (tag: Tag) => {
                                    tagKeyTree = tagTree.addNewKey(tag.key)
                                    tagValueTree = tagKeyTree.addNewValue(tag.value)
                                    storeTree = tagValueTree.addNewStore(taggedLayer.store)
                                    storeTree.addNewFeature(feature.id)
                                }
                            )
                        }
                    }
                )
            }
        )
        return tagTree
    }

    delete(node: FeatureTree) {
        this.infoDataService.deleteTag(node).subscribe(
            _ => {
                let features = node.storeTree.features
                features.splice(features.indexOf(node), 1)
                this._refreshTree()
            }
        )
    }

    showInfo(node: FeatureTree) {
        this.mapControlService.featureClicked.next(
            new Feature(<string>node.storeTree.store, node.id.toString())
        )
    }

    private _refreshTree() {
        // Angular bug, see https://github.com/angular/components/issues/11381
        let _data = this.nestedDataSource.data
        this.nestedDataSource.data = null
        this.nestedDataSource.data = _data
        this.mapControlService.search.next(/* refresh */true)
    }
}