/*
 * Copyright 2019 ABSA Group Limited
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
import { AfterViewInit, Component, Input, OnDestroy, OnInit, ViewChild } from '@angular/core'
import { Store } from '@ngrx/store'
import { CytoscapeNgLibComponent } from 'cytoscape-ng-lib'
import * as _ from 'lodash'
import { Subscription } from 'rxjs'
import { filter, map, switchMap } from 'rxjs/operators'
import { AppState } from 'src/app/model/app-state'
import { RouterStateUrl } from 'src/app/model/routerStateUrl'
import { LineageOverviewNodeType } from 'src/app/model/types/lineageOverviewNodeType'
import * as ContextMenuAction from 'src/app/store/actions/context-menu.actions'
import * as DetailsInfosAction from 'src/app/store/actions/details-info.actions'
import * as ExecutionPlanAction from 'src/app/store/actions/execution-plan.actions'
import * as LayoutAction from 'src/app/store/actions/layout.actions'
import * as LineageOverviewAction from 'src/app/store/actions/lineage-overview.actions'
import * as RouterAction from 'src/app/store/actions/router.actions'
import { getWriteOperationIdFromExecutionId } from 'src/app/util/execution-plan'
import { cyStyles } from '../../../model/lineage-graph'


@Component({
  selector: 'lineage-overview-graph',
  templateUrl: './lineage-overview-graph.component.html'
})
export class LineageOverviewGraphComponent implements OnInit, AfterViewInit, OnDestroy {

  @Input()
  public embeddedMode: boolean

  @ViewChild(CytoscapeNgLibComponent, { static: true })
  private cytograph: CytoscapeNgLibComponent

  private subscriptions: Subscription[] = []

  private currentUrl: string


  constructor(private store: Store<AppState>) {
    this.getContextMenuConfiguration()
    this.getLayoutConfiguration()
    this.getOverviewLineage()
  }

  ngOnInit(): void {
    this.subscriptions.push(
      this.store
        .select('router', 'state', 'url')
        .subscribe(url => {
          this.currentUrl = url
        }))

    this.subscriptions.push(this.store
      .select('layout')
      .pipe(
        switchMap(layout => {
          return this.store
            .select('contextMenu')
            .pipe(
              map(contextMenu => {
                return { layout, contextMenu }
              })
            )
        }),
        switchMap(res => {
          return this.store
            .select('lineageOverview')
            .pipe(
              filter(state => !_.isNil(state)),
              map(state => {
                return { graph: state, layout: res.layout, contextMenu: res.contextMenu }
              }),
            )
        })
      )
      .subscribe(state => {
        if (state && this.cytograph.cy) {
          this.cytograph.cy.elements().remove()
          this.cytograph.cy.add(state.graph.lineage)
          this.cytograph.cy.nodeHtmlLabel([{
            tpl: function (data) {
              if (data.icon) {
                return `<i class='fa fa-4x' style='color:${data.color}'> ${String.fromCharCode(data.icon)}</i>`
              }
              return null
            }
          }])
          this.cytograph.cy.panzoom()
          this.cytograph.cy.layout(state.layout).run()
        }
      }))
  }

  public ngAfterViewInit(): void {
    this.cytograph.cy.ready(() => {
      this.cytograph.cy.style(cyStyles)
      this.cytograph.cy.on('mouseover', 'node', e => e.originalEvent.target.style.cursor = 'pointer')
      this.cytograph.cy.on('mouseout', 'node', e => e.originalEvent.target.style.cursor = '')
      const doubleClickDelayMs = 350
      let previousTimeStamp

      this.cytograph.cy.on('tap', (event) => {
        const currentTimeStamp = event.timeStamp
        const msFromLastTap = currentTimeStamp - previousTimeStamp

        if (msFromLastTap < doubleClickDelayMs) {
          event.target.trigger('doubleTap', event)
        } else {
          previousTimeStamp = currentTimeStamp
          const clikedTarget = event.target
          const nodeId = (clikedTarget != this.cytograph.cy && clikedTarget.isNode()) ? clikedTarget.id() : null
          const params = {} as RouterStateUrl
          params.queryParams = { selectedNodeId: nodeId }
          this.store.dispatch(new RouterAction.Go(params))
        }
      })
      this.cytograph.cy.on('doubleTap', (event) => {
        const clikedTarget = event.target
        const nodeId = (clikedTarget != this.cytograph.cy && clikedTarget.isNode()) ? clikedTarget.id() : null
        if (nodeId && clikedTarget.data()._type == LineageOverviewNodeType.Execution) {
          const params: RouterStateUrl = {
            url: `/app/lineage-detailed/${nodeId}`,
            queryParams: {
              returnUrl: this.currentUrl
            }
          }
          this.store.dispatch(new RouterAction.Go(params))
        }
      })
    })

    this.cytograph.cy.on('layoutstop', () => {
      this.subscriptions.push(
        this.store
          .select('router', 'state', 'queryParams', 'selectedNodeId')
          .subscribe((selectedNodeId) => {
            this.cytograph.cy.nodes().unselect()
            const selectedNode = this.cytograph.cy.nodes().filter(`[id='${selectedNodeId}']`)
            selectedNode.select()
            this.getNodeInfo(selectedNode.data())
          })
      )
    })
  }

  private getOverviewLineage = (): void => {
    this.subscriptions.push(
      this.store
        .select('router', 'state', 'queryParams', 'executionEventId')
        .pipe(
          filter(state => state != null)
        )
        .subscribe(
          executionEventId => this.store.dispatch(new LineageOverviewAction.Get({ executionEventId }))
        )
    )
  }

  private getNodeInfo = (node: any): void => {
    if (node) {
      this.store.dispatch(new DetailsInfosAction.Reset())
      switch (node._type) {
        case LineageOverviewNodeType.Execution: {
          this.store.dispatch(new ExecutionPlanAction.Get(node.id))
          break
        }
        case LineageOverviewNodeType.DataSource: {
          this.store.dispatch(new ExecutionPlanAction.Reset())
          this.subscriptions.push(
            this.store.select('lineageOverview')
              .subscribe(lineageOverview => {
                const edge = lineageOverview.lineage.edges.find(e => e.data.target == node.id)
                if (edge) {
                  const executionPlanId = edge.data.source
                  this.store.dispatch(new DetailsInfosAction.Get(getWriteOperationIdFromExecutionId(executionPlanId)))
                }
              })
          )
          break
        }
      }
    } else {
      this.store.dispatch(new DetailsInfosAction.Reset())
      this.store.dispatch(new ExecutionPlanAction.Reset())
    }
  }

  private getContextMenuConfiguration = (): void => {
    this.store.dispatch(new ContextMenuAction.Get())
  }

  private getLayoutConfiguration = (): void => {
    this.store.dispatch(new LayoutAction.Get())
  }

  ngOnDestroy(): void {
    this.subscriptions.forEach(s => s.unsubscribe())
  }

}
