import {
  Component,
  Input,
  OnChanges,
  SimpleChanges,
} from '@angular/core';
import { Host, Service } from '@overflow/commons-typescript/model/discovery';
import { TreeNode, Message } from 'primeng/primeng';
import { ProbeHost } from '@overflow/commons-typescript/model/probe';
import { Anim } from './animation';
import { TargetService } from '@overflow/target/service/target.service';
import { InfraService, InfraHost, Infra, MetaInfraTypeEnum, toMetaInfraType, InfraZone, Target, Page, PageParams, InfraHostIP } from '@overflow/commons-typescript';
import { InfraService as InfraManageService } from '../../infra/service/infra.service';
import { Observable, of } from 'rxjs';
import { catchError, map, tap, take } from 'rxjs/operators';

@Component({
  selector: 'of-discovery-infra-tree',
  templateUrl: './discovery-infra-tree.component.html',
  animations: Anim,
  providers: [
    TargetService,
    InfraManageService
  ]
})
export class DiscoveryInfraTreeComponent implements OnChanges {

  @Input() probeHost: ProbeHost;
  @Input() filterWord: string;
  @Input() filterServices: Map<string, boolean>;

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

  zoneNode: TreeNode[];
  hostNode: TreeNode[];
  selectedItems: TreeNode[] = [];
  msgs: Message[];

  savedInfras: Infra[];

  discoveredHosts: Host[] = [];
  discoveredServices: Service[] = [];
  infraSaved: boolean;
  targetSaved: boolean;

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

  constructor(
    private infraManageService: InfraManageService,
    private targetService: TargetService
  ) {
  }

  ngOnChanges(changes: SimpleChanges) {
    if (changes['probeHost']) {
      this.getTargets();
    }
  }

  getInfras() {
    this.infraManageService.readAllByProbeID(this.probeHost.probe.id)
      .pipe(
        tap(() => {
          this.pending$ = of(true);
        }),
        map((infras: Infra[]) => {
          this.generateTree(infras);
        }),
        catchError(error => {
          this.error$ = of(error);
          return of();
        }),
        tap(() => {
          this.pending$ = of(false);
        }),
        take(1),
    ).subscribe();
  }

  getTargets() {
    this.targetService.readAllByProbeID(this.probeHost.probe.id)
      .pipe(
        tap(() => {
          this.pending$ = of(true);
        }),
        map((targets: Target[]) => {
          this.targets = targets;
          this.getInfras();
        }),
        catchError(error => {
          this.error$ = of(error);
          return of();
        }),
        tap(() => {
          this.pending$ = of(false);
        }),
        take(1),
    ).subscribe();
  }

  generateTree(infras: Infra[]) {
    this.zoneNode = [];
    this.hostNode = [];
    this.infraZones = [];
    this.infraHosts = [];
    this.infraServices = [];

    for (const infra of infras) {
      if (infra.metaInfraType.key === toMetaInfraType(MetaInfraTypeEnum.ZONE).key) {
        this.infraZones.push(infra);
      } else if (infra.metaInfraType.key === toMetaInfraType(MetaInfraTypeEnum.HOST).key) {
        this.infraHosts.push(infra);
      } else if (infra.metaInfraType.key === toMetaInfraType(MetaInfraTypeEnum.SERVICE).key) {
        this.infraServices.push(infra);
      }
    }
    this.infraZones.forEach(infraZone => {
      this.addInfraZone(infraZone);
    });
    this.infraHosts.forEach(infraHost => {
      this.addInfraHost(infraHost);
    });
    this.infraServices.forEach(infraService => {
      this.addInfraService(infraService);
    });
  }

  addInfraZone(infraZone: InfraZone) {
    const target: Target = this.checkAlreadyTarget(infraZone.id);
    this.zoneNode.push({
      label: this.probeHost.probe.cidr,
      type: 'ZONE',
      data: {
        target: target,
        date: infraZone.createDate,
        object: infraZone,
        infraID: infraZone.id,
      },
      children: this.hostNode,
      expanded: true
    });
  }

  addInfraHost(infraHost: InfraHost) {
    if (infraHost.infraHostIPs[0].address.indexOf('/') >= 0) {
      return;
    }
    const target: Target = this.checkAlreadyTarget(infraHost.id);
    const ip = infraHost.infraHostIPs[0].address;
    const idx = this.findHostIndex(ip);
    this.hostNode.splice(idx, 0, {
      type: 'HOST',
      label: ip,
      data: {
        target: target,
        date: infraHost.createDate,
        ip: this.convertIPtoNumber(ip),
        object: infraHost,
        infraID: infraHost.id,
      },
      expanded: true,
      children: []
    });
  }

  addInfraService(infraService: InfraService) {
    const target: Target = this.checkAlreadyTarget(infraService.id);
    const targetHostNode = this.findHostNodeByService(infraService.infraHostPort.infraHostIP.address);
    const idx = this.findServiceIndex(targetHostNode.children, infraService.metaTargetServiceType.name);
    targetHostNode.children.splice(idx, 0, {
      type: 'SERVICE',
      label: infraService.metaTargetServiceType.name + ' (' + infraService.infraHostPort.port + ')',
      data: {
        target: target,
        date: infraService.createDate,
        portType: infraService.infraHostPort.metaPortType.name,
        portNumber: infraService.infraHostPort.port,
        object: infraService,
        infraID: infraService.id,
        name: infraService.metaTargetServiceType.name
      },
    });
  }

  checkAlreadyTarget(infraID: number): Target {
    return this.targets.find(target => target.infra.id === infraID);
  }

  discoveryStarted(startedAt: Date) {
    console.log('Discovery Started at: ' + startedAt);
    this.pending$ = of(true);
  }

  discoveryStopped(stoppedAt: Date) {
    console.log('Discovery Stopped at: ' + stoppedAt);
    this.saveDiscoveredInfras();
    this.pending$ = of(false);
  }

  saveDiscoveredInfras() {
    console.log(this.discoveredHosts);
    console.log(this.discoveredServices);
    this.infraManageService.registDiscoverd(
      this.probeHost.probe.id,
      this.discoveredHosts,
      this.discoveredServices)
      .pipe(
        tap(() => {
          this.infraSaved = false;
          this.pending$ = of(true);
        }),
        map((infras: Infra[]) => {
          if (infras) {
            this.savedInfras = infras;
            this.msgs = [];
            this.msgs.push({
              severity: 'success',
              summary: infras.length + '개의 Infra가 새로 저장되었습니다. 모니터링 대상(들)을 선택 후 저장하세요.',
            });
          }
          this.pending$ = of(false);
        }),
        catchError(error => {
          this.error$ = of(error);
          return of();
        }),
        tap(() => {
          this.infraSaved = true;
        }),
        take(1),
    ).subscribe();
  }

  addHost(host: Host) {
    let exist = false;
    this.infraHosts.forEach(infraHost => {
      if (infraHost.infraHostIPs[0].address === host.address) {
        exist = true;
        return;
      }
    });
    if (exist) {
      return;
    }
    const idx = this.findHostIndex(host.address);
    this.hostNode.splice(idx, 0, {
      type: 'HOST',
      label: host.address,
      data: {
        target: null,
        date: null,
        ip: this.convertIPtoNumber(host.address),
        object: host,
        infraID: null,
      },
      expanded: true,
      children: []
    });
    this.discoveredHosts.push(host);
  }

  addService(service: Service) {
    let exist = false;
    this.infraServices.forEach(infraService => {
      if (infraService.metaTargetServiceType.key === service.key &&
        infraService.infraHostPort.port === service.port.portNumber &&
        infraService.infraHostPort.infraHostIP.address === service.port.host.address &&
        infraService.infraHostPort.metaPortType.key === service.port.metaPortType.key
      ) {
        exist = true;
        return;
      }
    });
    if (exist) {
      return;
    }

    const targetHostNode = this.findHostNodeByService(service.port.host.address);
    const idx = this.findServiceIndex(targetHostNode.children, service.key);
    targetHostNode.children.splice(idx, 0, {
      type: 'SERVICE',
      label: service.key + ' (' + service.port.portNumber + ')',
      data: {
        target: null,
        date: null,
        portType: service.port.metaPortType.name,
        portNumber: service.port.portNumber,
        object: service,
        InfraID: null,
        name: service.key
      },
    });

    this.discoveredServices.push(service);
  }


  findHostIndex(hostIP: string): number {
    let index = 0;
    this.hostNode.forEach(node => {
      if (node.data.ip < this.convertIPtoNumber(hostIP)) {
        index++;
      }
    });
    return index;
  }

  findServiceIndex(serviceNodes: TreeNode[], serviceName: string): number {
    let index = 0;
    serviceNodes.forEach(node => {
      if (node.data.name.toUpperCase().localeCompare(serviceName.toUpperCase()) === -1) {
        index++;
      }
    });
    return index;
  }

  findHostNodeByService(serviceAddress: string): TreeNode {
    let targetHost = null;
    this.hostNode.forEach((value, i) => {
      if (value.data.ip === this.convertIPtoNumber(serviceAddress)) {
        targetHost = value;
        return;
      }
    });
    return targetHost;
  }

  convertIPtoNumber(ip: string): number {
    return ip.split('.').map((octet, index, array) => {
      return parseInt(octet) * Math.pow(256, (array.length - index - 1));
    }).reduce((prev, curr) => {
      return prev + curr;
    });
  }

  checkHighlight(name: string, type: number) {
    let highlight = true;
    if (this.filterWord && (name.toUpperCase().indexOf(this.filterWord.toUpperCase()) < 0)) {
      highlight = false;
    }
    if (type === 1 && this.filterServices[name] === false) {
      highlight = false;
    }
    return highlight;
  }

  onTargetSelect(e, node: TreeNode) {
    if (e.checked) {
      this.selectedItems.push(node);
    } else {
      const index = this.selectedItems.indexOf(node);
      this.selectedItems.splice(index, 1);
    }
  }

  saveTargets() {
    this.pending$ = of(true);
    const infraIDs: number[] = [];
    this.selectedItems.forEach(node => {
      let infraID = node.data.infraID;
      if (null === infraID) { // 새로 발견된 Host or Service
        for (const infra of this.savedInfras) {
          if (infra.metaInfraType.key === toMetaInfraType(MetaInfraTypeEnum.HOST).key) {
            const infraHost: InfraHost = infra;
            const host: Host = node.data.object;
            if (infraHost.infraHostIPs[0].address === host.address) {
              infraID = infraHost.id;
            }
          } else if (infra.metaInfraType.key === toMetaInfraType(MetaInfraTypeEnum.SERVICE).key) {
            const infraService: InfraService = infra;
            const service: Service = node.data.object;
            if (infraService.metaTargetServiceType.key === service.key &&
              infraService.infraHostPort.infraHostIP.address === service.port.host.address &&
              infraService.infraHostPort.port === service.port.portNumber &&
              infraService.infraHostPort.metaPortType.key === service.port.metaPortType.key
            ) {
              infraID = infraService.id;
            }
          }
        }
      }

      if (null === infraID) {
        console.log('INFRA ID not found.');
        return;
      }
      infraIDs.push(infraID);
    });

    console.log(infraIDs);

    this.targetService.registAll(infraIDs, this.probeHost.probe.id)
      .pipe(
        tap(() => {
        }),
        map((targets: Target[]) => {
          if (targets) {
            this.targetSaved = true;
            this.pending$ = of(false);
          }
        }),
        catchError(error => {
          this.error$ = of(error);
          return of();
        }),
        tap(() => {
        }),
        take(1),
    ).subscribe();
  }

}
