import {
  Component, Input, OnChanges, SimpleChanges, OnInit, ViewChild
} from '@angular/core';
import { InfraService, InfraHost, InfraZone, Infra } from '@overflow/commons-typescript/model/infra';
import { Observable, of } from 'rxjs';
import { catchError, map, tap, take } from 'rxjs/operators';
import { TreeNode, MenuItem, ContextMenu } from 'primeng/primeng';
import { ProbeHost, MetaInfraTypeEnum, toMetaInfraType } from '@overflow/commons-typescript';
import { InfraService as InfraManageService } from '../service/infra.service';

@Component({
  selector: 'of-infra-tree',
  templateUrl: './infra-tree.component.html',
})
export class InfraTreeComponent implements OnInit, OnChanges {

  @Input() probeHost: ProbeHost;

  infraZones: InfraZone[];
  infraHosts: InfraHost[];
  infraServices: InfraService[];

  pending$: Observable<boolean>;
  error$: Observable<any>;

  zoneNode: TreeNode[];
  hostNode: TreeNode[];

  contextMenuZone: MenuItem[];
  contextMenuHost: MenuItem[];
  contextMenuService: MenuItem[];
  selectedNode: TreeNode;

  @ViewChild('cmZone') cmZone: ContextMenu;
  @ViewChild('cmHost') cmHost: ContextMenu;
  @ViewChild('cmService') cmService: ContextMenu;

  constructor(
    private infraService: InfraManageService,
  ) {
  }

  ngOnInit(): void {
    this.initContextMenu();
  }

  initContextMenu() {
    this.contextMenuZone = [
      { label: 'Zone Menu', command: (event) => this.cmZone.hide() },
      { separator: true },
      {
        label: 'Discovery', icon: 'fa-plus', command: (event) => {
          alert('discovery');
        }
      },
    ];
    this.contextMenuHost = [
      { label: 'Host Menu', command: (event) => this.cmHost.hide() },
      { separator: true },
      { label: 'Add sensor', icon: 'fa-plus', command: (event) => alert('Add sensor') },
      { label: 'Traceroute', icon: 'fa-plus' },
      { label: 'ARP Test', icon: 'fa-plus' },
    ];
    this.contextMenuService = [
      { label: 'Service Menu', command: (event) => this.cmService.hide() },
      { separator: true },
      { label: 'Add sensor', icon: 'fa-plus', command: (event) => alert('Add sensor') },
    ];
  }

  ngOnChanges(changes: SimpleChanges): void {
    if (changes['probeHost'].isFirstChange) {
      this.zoneNode = [];
      this.hostNode = [];
      this.getInfraWithInfraTypeKey(toMetaInfraType(MetaInfraTypeEnum.ZONE).key);
      this.getInfraWithInfraTypeKey(toMetaInfraType(MetaInfraTypeEnum.HOST).key);
      this.getInfraWithInfraTypeKey(toMetaInfraType(MetaInfraTypeEnum.SERVICE).key);
    }
  }

  getInfraWithInfraTypeKey(metaInfraTypeKey: string) {
    this.infraService.readAllByMetaInfraTypeKeyAndProbeID(
      metaInfraTypeKey, this.probeHost.probe.id)
      .pipe(
        tap(() => {
          this.pending$ = of(true);
        }),
        map((infras: Infra[]) => {
          switch (metaInfraTypeKey) {
            case toMetaInfraType(MetaInfraTypeEnum.ZONE).key:
              this.infraZones = infras;
              break;
            case toMetaInfraType(MetaInfraTypeEnum.HOST).key:
              this.infraHosts = infras;
              break;
            case toMetaInfraType(MetaInfraTypeEnum.SERVICE).key:
              this.infraServices = infras;
              break;
            default:
              break;
          }
          if (this.infraZones && this.infraHosts && this.infraServices) {
            this.generateTreeData();
          }
        }),
        catchError(error => {
          this.error$ = of(error);
          return of();
        }),
        tap(() => {
          this.pending$ = of(false);
        }),
        take(1),
    ).subscribe();
  }

  generateTreeData() {
    if (null === this.infraZones) {
      return;
    }
    this.infraZones.forEach(infraZone => {
      this.addZone(infraZone);
    });
    if (null === this.infraHosts) {
      return;
    }
    this.infraHosts.forEach(infraHost => {
      this.addHost(infraHost);
    });
    if (null === this.infraServices) {
      return;
    }
    this.infraServices.forEach(infraService => {
      this.addService(infraService);
    });
  }

  addZone(infraZone: InfraZone) {
    this.zoneNode.push({
      label: infraZone.network + '(' + infraZone.iface + ')',
      type: 'ZONE',
      data: {
        target: infraZone,
        subLabel: 'Something to display'
      },
      children: this.hostNode,
      expanded: true
    });
  }

  addHost(infraHost: InfraHost) {
    let ipAddr = infraHost.infraHostIPs[0].address.split('/')[0];
    if (infraHost.infraHostOS && infraHost.infraHostOS.name) {
      ipAddr += '(' + infraHost.infraHostOS.name + ')';
    }
    this.hostNode.push({
      type: 'HOST',
      label: ipAddr,
      data: {
        target: infraHost,
      },
      expanded: true,
      children: []
    });
  }

  addService(infraService: InfraService) {
    const idx = this.findHostIndex(infraService);
    if (idx === -1) {
      // this.addHost(infraService.infraHost);
      // this.addService(infraService);
      return;
    }
    this.hostNode[idx].children.push({
      type: 'SERVICE',
      label: 'TODO',
      data: {
        target: infraService
      },
    });
  }

  findHostIndex(infraService: InfraService): number {
    let idx = -1;
    this.hostNode.forEach((node, index) => {
      const infraHost: InfraHost = node.data.target;
      for (const infraHostIP of infraHost.infraHostIPs) {
        if (infraHostIP.id === infraService.infraHostPort.infraHostIP.id) {
          idx = index;
          return;
        }
      }
    });
    return idx;
  }


  showContextMenu(event: MouseEvent, node: any) {
    this.selectedNode = node;

    this.cmZone.hide();
    this.cmHost.hide();
    this.cmService.hide();

    if (node.type === 'ZONE') {
      this.cmZone.show(event);
    } else if (node.type === 'HOST') {
      this.cmHost.show(event);
    } else if (node.type === 'SERVICE') {
      this.cmService.show(event);
    }

    return false;
  }
}