import {
  Component, Input,
  OnInit,
} from '@angular/core';
import { Host, Port, Service } from '@overflow/commons-typescript/model/discovery';
import { TreeNode, Message, Tree } 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, MetaTargetHostTypeEnum, toMetaTargetHostType, Infra, MetaInfraTypeEnum, toMetaInfraTypeEnum, toMetaInfraType, InfraZone, Target, Page, PageParams } from '@overflow/commons-typescript';
import { InfraService as InfraRegistService } from '../../infra/service/infra.service';
import { Observable, of, Subscription } from 'rxjs';
import { catchError, map, tap, take } from 'rxjs/operators';

@Component({
  selector: 'of-discovery-result',
  templateUrl: './search-result.component.html',
  animations: Anim,
  providers: [
    TargetService,
    InfraRegistService
  ]
})
export class SearchResultComponent implements OnInit {

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

  infras: Infra[];
  existTargets: Target[];

  discoverySubscription: Subscription;
  zoneNode: TreeNode[] = [];
  hostNode: TreeNode[] = [];
  selectedItems = [];
  msgs: Message[];
  error$: Observable<any>;

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

  pending$: Observable<boolean>;

  constructor(
    private infraRegistService: InfraRegistService,
    private targetService: TargetService
  ) {
  }

  ngOnInit(): void {
    this.zoneNode.push({
      label: this.probeHost.probe.cidr,
      type: 'ZONE',
      data: {
      },
      children: this.hostNode,
      expanded: true
    });
    // this.getExistTarget();
  }

  discoveryStarted(startedAt: Date) {
    console.log('Discovery Started at: ' + startedAt);
  }

  discoveryStopped(stoppedAt: Date) {
    console.log('Discovery Stopped at: ' + stoppedAt);
    this.saveDiscoveredInfras();
  }

  getExistTarget() {
    const pageParams: PageParams = {
      pageNo: 0,
      countPerPage: 99999,
      sortCol: 'id',
      sortDirection: 'descending'
    };
    this.targetService.readAllByProbeID(this.probeHost.probe.id, pageParams)
      .pipe(
        tap(() => {
        }),
        map((targetPage: Page<Target>) => {
          this.existTargets = targetPage.content;
        }),
        catchError(error => {
          this.error$ = of(error);
          return of();
        }),
        tap(() => {
        }),
        take(1),
    ).subscribe();
  }

  // ngOnChanges(changes: SimpleChanges): void {
  //   if (changes['finished'] && changes['finished'].currentValue === true) {
  //     this.saveDiscoveredInfras();
  //   }
  // }

  displayInform() {
    this.msgs = [];
    this.msgs.push({
      severity: 'success',
      summary: 'Discovery가 완료되었습니다. 모니터링 대상(들)을 선택 후 저장하세요.',
    });
  }

  rerenderInfras() {
    this.hostNode = [];
    for (const infra of this.infras) {
      switch (infra.metaInfraType) {
        // case toMetaInfraType(MetaInfraTypeEnum.ZONE):
        //   const infraZone: InfraZone = infra;
        //   break;
        case toMetaInfraType(MetaInfraTypeEnum.HOST):
          const infraHost: InfraHost = infra;
          this.addInfraHost(infraHost);
          break;
        case toMetaInfraType(MetaInfraTypeEnum.SERVICE):
          const infraService: InfraService = infra;
          this.addInfraService(infraService)
          break;
        default:
          break;
      }
    }
  }

  saveDiscoveredInfras() {
    this.infraRegistService.registDiscoverd(
      this.probeHost.probe.id,
      this.discoveredHosts,
      this.discoveredServices)
      .pipe(
        tap(() => {
          this.infraSaved = false;
          this.pending$ = of(true);
        }),
        map((infras: Infra[]) => {
          if (infras) {
            this.infras = infras;
            this.rerenderInfras();
            this.displayInform();
          }
        }),
        catchError(error => {
          this.error$ = of(error);
          return of();
        }),
        tap(() => {
          this.infraSaved = true;
          this.pending$ = of(false);
        }),
        take(1),
    ).subscribe();
  }

  addHost(host: Host) {
    const idx = this.findHostIndex(host);
    this.hostNode.splice(idx, 0, {
      type: 'HOST',
      label: host.address,
      data: {
        ip: this.convertIPtoNumber(host.address),
        mac: host.mac,
        target: host
      },
      expanded: true,
      children: []
    });

    this.discoveredHosts.push(host);
  }

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

  addInfraHost(infraHost: InfraHost) {
    const host: Host = {
      address: infraHost.infraHostIPs[0].address,
    }
    const idx = this.findHostIndex(host);
    this.hostNode.splice(idx, 0, {
      type: 'HOST',
      label: host.address,
      data: {
        ip: this.convertIPtoNumber(host.address),
        target: infraHost
      },
      expanded: true,
      children: []
    });
  }

  addInfraService(infraService: InfraService) {
    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: {
        name: infraService.metaTargetServiceType.name,
        portType: infraService.infraHostPort.metaPortType.name,
        portNumber: infraService.infraHostPort.port,
        target: infraService
      },
    });
  }


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


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

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

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

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

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

  checkExistTarget(infra: Infra): boolean {
    if (!this.infraSaved) {
      return false;
    }
    for (const target of this.existTargets) {
      if (target.infra.id === infra.id) {
        return true;
      }
    }
    return false;
  }

  saveTargets() {
    const targets: Target[] = [];
    this.selectedItems.forEach(value => {
      const infra: Infra = value;
      let name: string;
      if (value.metaInfraType === toMetaInfraType(MetaInfraTypeEnum.ZONE)) {
        const infraZone: InfraZone = value;
        name = infraZone.network;
      } else if (value.metaInfraType === toMetaInfraType(MetaInfraTypeEnum.HOST)) {
        const infraHost: InfraHost = value;
        name = infraHost.infraHostIPs[0].address;
      } else if (value.metaInfraType === toMetaInfraType(MetaInfraTypeEnum.SERVICE)) {
        const infraService: InfraService = value;
        name = infraService.metaInfraType.name;
      }

      const target: Target = {
        infra: {
          id: infra.id
        },
        name: name,
        sensorCount: 0,
      };
      targets.push(target);
    });
    this.targetService.registAll(targets, this.probeHost.probe.id)
      .pipe(
        tap(() => {
        }),
        map((targets: Target[]) => {
          if (targets) {
            this.targetSaved = true;
          }
        }),
        catchError(error => {
          this.error$ = of(error);
          return of();
        }),
        tap(() => {
        }),
        take(1),
    ).subscribe();
  }

}
