import { Component, OnInit, ChangeDetectorRef, ChangeDetectionStrategy,
    Output, EventEmitter } from '@angular/core'
import { Injectable } from '@angular/core'
import { ActivatedRoute, Params, Router } from '@angular/router'

import { SelectionModel } from '@angular/cdk/collections'
import { NestedTreeControl } from '@angular/cdk/tree'
import { MatTreeNestedDataSource } from '@angular/material/tree'
import { MatSnackBar } from '@angular/material/snack-bar'

import { BehaviorSubject, Observable, of as observableOf } from 'rxjs'

import { ConfigService } from '../../_services/config.service'
import { Node, PrimaryGroupNode, LayerNode, TreeData } from '../models'
import { MapControlService } from '../map-control.service'
import { MapDataService } from '../map-data.service'
import { Feature, FeatureWithField } from '../../info/info-data.service'
import { CategoryGroup } from '../../openapi'


@Injectable()
export class LayerDatabase {
    dataChange: BehaviorSubject<PrimaryGroupNode[]> = new BehaviorSubject<PrimaryGroupNode[]>([])

    setData(data: PrimaryGroupNode[]) {
        // Notify the change.
        this.dataChange.next(data)
    }

    getLayers(group: Node): Node[] {
        //return this.dataChange.getValue().find(l => l.name == group).children
        return []
    }
}

@Component({
    selector: 'gisaf-tree-layer',
    templateUrl: './tree-layer.component.html',
    styleUrls: ['./tree-layer.component.css'],
    providers: [LayerDatabase],
    changeDetection: ChangeDetectionStrategy.OnPush,
})
export class TreeLayerComponent implements OnInit {
    treeControl: NestedTreeControl<Node>
    checklistSelection = new SelectionModel<Node>(true /* multiple */)
    nestedDataSource: MatTreeNestedDataSource<Node>
    @Output() showLayer: EventEmitter<Event> = new EventEmitter<Event>()
    @Output() hideLayer: EventEmitter<Event> = new EventEmitter<Event>()
    loading = true
    error = false
    data$: any  // XXX: should be Observable<PrimaryGroupNode[]>, but ts compile error

    constructor(
        public configService: ConfigService,
        protected route: ActivatedRoute,
        protected router: Router,
        private database: LayerDatabase,
        protected mapControlService: MapControlService,
        public mapDataService: MapDataService,
        public snackBar: MatSnackBar,
        private cdr: ChangeDetectorRef,
    ) {
        this.treeControl = new NestedTreeControl<Node>(this.getChildren)
        this.treeControl.isExpandable = (node => false)
        this.nestedDataSource = new MatTreeNestedDataSource()
        database.dataChange.subscribe(data => {
            this.nestedDataSource.data = data
        })
    }

    ngOnInit() {
        this.route.queryParams.subscribe((params: Params) => {
            if ('stores' in params) {
                let stores = params['stores'].split(',')
                for (let store in stores) {
                    this.mapControlService.addBaseMapLayerById(store)
                }
            }
        })

        this.mapControlService.layerAddById$.subscribe(
            (store: string) => {
                let layerNode = this._getNodeByStore(store)
                if (!layerNode) {
                    console.log('No such layer, cannot add it', store)
                    return
                }
                this.checklistSelection.select(layerNode)
                this.mapControlService.addLayer(<LayerNode>layerNode)
                this.cdr.markForCheck()
            }
        )

        this.mapControlService.updateSelection$.subscribe(
            _ => {
                this.mapControlService.selectedLayers = <LayerNode[]>this.checklistSelection.selected
            }
        )

        this.mapControlService.layerRemoveById$.subscribe(
            (store: string) => {
                let layerNode = this._getNodeByStore(store)
                if (!layerNode) {
                    console.log('No such layer, cannot remove it', store)
                    return
                }
                this.checklistSelection.deselect(layerNode)
                this.mapControlService.removeLayer(layerNode.treeElement.node)
                this.cdr.markForCheck()
                // Bug in checklistSelection? Need to unset checked manually:
                layerNode.treeElement.checked = false
                layerNode.treeElement.cdr.markForCheck()
            }
        )
        this.mapControlService.showFeature$.subscribe(
            (feature: FeatureWithField) => {
                if (!feature) {
                    return
                }
                let layerNode: LayerNode = this._getNodeByStore(feature.store)
                if (!layerNode) {
                    this.snackBar.open('Unknown layer ' + feature.store, 'OK', {
                        duration: 3000
                    })
                    return
                }
                this.checklistSelection.select(layerNode)
                this.mapControlService.addLayer(layerNode)
                this.cdr.markForCheck()
            }
        )

        this.mapDataService.mapInitDataLoaded$.subscribe(
            _ => {
                _ && this.makeData()
            }
        )

        // XXX: code snippets:
        // Get names of selected layers: this.checklistSelection.selected.map(s => s.name)
    }

    protected makeData(): void {
        // Recompose the hierarchical data

        // Build a mapping object for faster assignment
        // XXX: Typescript doesn't provide Object.fromEntries, otherwise it should be:
        //let pgs = Object.fromEntries(treeData.primaryGroups.map(pg=>[pg.name, pg]))
        let pgs: Record<string, PrimaryGroupNode> = {}

        this.mapDataService.mapInitData.groups.forEach(
            pg => pgs[pg.name] = new PrimaryGroupNode(
                pg.name,
                pg.long_name,
                pg.long_name)
        )

        this.mapDataService.mapInitData.stores.forEach(
            store => {
                let group: PrimaryGroupNode = pgs[store.group]
                if (!group) {
                    console.warn('No group (' + store.group + ') for layer node ' + store.name)
                    return
                }
                group.children.push(new LayerNode(
                    store.name,
                    store.long_name,
                    store.group,
                    store.symbol, //store.icon,
                    store.symbol, //|| gisTypeSymbolMap[layer['gisType']],
                    '', //store.rawSurveyStore,
                    store.type,
                    store.gis_type,
                    {}, //JSON.parse(store.style),
                    store.z_index,
                    store.count,
                    store.description,
                    store.is_live,
                    store.custom,
                    [], //store.tagPlugins,
                    store.viewable_role,
                    store.category
                ))
                // console.log(this.mapDataService, group.children)
            }
        )
        this.database.setData(Object.values(pgs)) //this.mapDataService.mapInitData.groups)
        this.loading = false
        this.mapControlService.layerTreeLoaded.complete()
        this.cdr.markForCheck()
    }

    protected _getNodeByStore(store: string): LayerNode {
        // Get the LayerNode with the store
        // Assumes that the tree's depth is 2
        return <LayerNode>(this.nestedDataSource.data.map(
            pg => pg.children.find(node => node.store==store
        )).find(node => !!node))
    }

    /*
    protected _getNodeById(store: Store): LayerNode {
        console.log('FIXME: _getNodeById', store)
        return new LayerNode()
        const group = this.nestedDataSource.data.find(l => l.name == layer.group)
        const descendents = this.treeControl.getDescendants(group)
        const layerNode = descendents.find(l => l.name == layer.name)
        return layerNode
    }
    */

    isNodeActive(node): boolean {
        // Used in the initialization, from the route - otherwise,
        // the status of the checkbox is managed by Angular
        // FIXME: LayerNode in the activeLayerIds don't have group set
        //        This should be:
        //return !!this.activeLayerIds.find(l=>l.group == node.group && l.name == node.name)
        /*
        if (!!this.activeLayerIds.find(l=>l.name == node.name)) {
            console.log(node)
        }
        return !!this.activeLayerIds.find(l=>l.name == node.name)
        */
       return false
    }

    getChildren = (node: Node): Observable<Node[]> => {
        if (node.hasChildren()) {
            if ((<Node[]>node.children).length > 0) {
                // Already loaded: always the case with proper graphql query
                return observableOf(node.children)
            }
            else {
                // Dynamically load, unused with proper graphql query
                return observableOf(this.database.getLayers(node).map(
                    children => {
                        //node.children.next(children)
                        return children
                    }
                ))
            }
        }
        else {
            return observableOf(<Node[]>[])
        }
    }

    downloadLayer(node: LayerNode): void {
        node.downloadShapefile()
    }

    downloadDXF(node: LayerNode, reproject: Boolean=false): void {
        node.downloadDXF(reproject)
    }

    downloadGpkg(node: LayerNode) {
        node.downloadGpkg()
    }

    downloadRawData(node: LayerNode): void {
        node.downloadRawData()
    }

    /** Whether all the descendants of the node are selected */
    descendantsAllSelected(node: Node): boolean {
        const descendants = this.treeControl.getDescendants(node)
        if (!descendants.length) {
            return this.checklistSelection.isSelected(node)
        }
        const selected = this.checklistSelection.isSelected(node)
        const allSelected = descendants.every(child => this.checklistSelection.isSelected(child))
        if (!selected && allSelected) {
            this.checklistSelection.select(node)
            this.cdr.markForCheck()
        }
        return allSelected
    }

    /** Whether part of the descendants are selected */
    descendantsPartiallySelected(node: Node): boolean {
        const descendants = this.treeControl.getDescendants(node)
        if (!descendants.length) {
            return false
        }
        const result = descendants.some(child => this.checklistSelection.isSelected(child))
        return result && !this.descendantsAllSelected(node)
    }

    /** Toggle the to-do item selection. Select/deselect all the descendants node */
    layerItemSelectionToggle(node: Node): void {
        this.checklistSelection.toggle(node)
        const descendants = this.treeControl.getDescendants(node)
        this.checklistSelection.isSelected(node)
        ? this.checklistSelection.select(...descendants, node)
        : this.checklistSelection.deselect(...descendants, node)
        this.cdr.markForCheck()
    }

    goToAdmin(node: LayerNode) {
        this.router.navigate(['/admin', node.name])
    }
}
