import { Component, OnInit, HostListener, AfterContentInit } from '@angular/core';
import { AppConfiguration } from '../models/app-config.model';
import { MatDialog } from '@angular/material/dialog';
import { NodeConfiguration } from '../models/node-config.model';
import { NodeType } from '../models/node-type.model';
import { DialogData } from '../models/dialog-data.model';
import { NewDialogComponent } from './new-dialog/new-dialog.component';
import { saveAs } from 'file-saver';
import * as d3 from 'd3';
import { environment } from 'src/environments/environment';
import { DataService } from '../data.service';

import sdk from '@stackblitz/sdk';

@Component({
  selector: 'app-dashboard',
  templateUrl: './dashboard.component.html',
  styleUrls: ['./dashboard.component.css']
})
export class DashboardComponent implements AfterContentInit {

  appConfiguration: AppConfiguration = new AppConfiguration();

  margin = { top: 20, right: 90, bottom: 30, left: 90 };
  width = 0;
  height = 0;
  i = 0;
  duration = 750;
  root: any;
  tree: any;
  svg: any;
  treemap: any;
  selectedNode: any;

  selectedNodeName = '';
  newNodeName = '';

  name: string;

  isMenuOpen = false;

  showSpinner = false;

  constructor(
    private dataService: DataService,
    public dialog: MatDialog) { }

  ngAfterContentInit() {
    this.appConfiguration.nodeConfiguration = new NodeConfiguration();
    this.appConfiguration.nodeConfiguration.name = 'app';
    this.appConfiguration.nodeConfiguration.type = NodeType.module;


    this.width = 960 - this.margin.right - this.margin.left;
    this.height = 500 - this.margin.top - this.margin.bottom;

    console.log('initialized...');

    this.svg = d3.select('svg')
      .on('click', () => {
        if (this.isMenuOpen) {
          d3.select('#my_custom_menu')
            .style('display', 'none');
          this.isMenuOpen = false;
        }
      })
      // .attr('width', this.width + this.margin.right + this.margin.left)
      // .attr('height', this.height + this.margin.top + this.margin.bottom)
      .attr('width', '100%')
      .attr('height', this.height + this.margin.top + this.margin.bottom)
      .append('g')
      .attr('transform', 'translate('
        + this.margin.left + ',' + this.margin.top + ')');


    // declares a tree layout and assigns the size
    this.treemap = d3.tree().size([this.height, this.width]);

    // Assigns parent, children, height, depth
    this.root = d3.hierarchy(this.appConfiguration.nodeConfiguration, (d: any) => d.children);
    this.root.x0 = this.height / 2;
    this.root.y0 = 0;

    this.update(this.root);

    console.log(this.svg);
  }

  openDialog(action: string): void {
    if (this.isMenuOpen) {
      d3.select('#my_custom_menu')
        .style('display', 'none');
      this.isMenuOpen = false;
    }
    this.selectedNode.action = action;
    const modalData = this.selectedNode as DialogData;
    modalData.action = action;

    const dialogRef = this.dialog.open(NewDialogComponent, {
      width: '400px',
      data: modalData
    });

    dialogRef.afterClosed().subscribe((newNode: NodeConfiguration) => {
      console.log('The dialog was closed', newNode);

      if (!this.selectedNode) {
        alert('Please select parent node!');
      }

      if (!newNode) { return; }

      if (action === 'create') {
        const node = {
          name: newNode.name,
          type: newNode.type,
          route: newNode.route,
          parentModule: this.selectedNode.data.name,
          modulePath: this.getModulePath(this.selectedNode, newNode.name),

        };

        const newD3Node: any = d3.hierarchy(node);
        newD3Node.depth = this.selectedNode.depth + 1;
        newD3Node.height = this.selectedNode.height + 1;
        newD3Node.parent = this.selectedNode;
        newD3Node.id = ++this.i;
        if (!this.selectedNode.children) {
          this.selectedNode.children = [];
          this.selectedNode.data.children = [];
        }

        this.selectedNode.children.push(newD3Node);
        this.selectedNode.data.children.push(newD3Node.data);

        this.update(this.selectedNode);
      } else {
        // TODO: Need to loop through all children of selected node and update their modulePath, route and name
        // console.log('updating existing node...');
        // this.updateModulePath(newNode);
        // debugger;
        // this.svg.select('#text-' + this.selectedNode.id).text(this.getNodeText(newNode));
        // this.updateNodeName();
      }

    });
  }

  updateModulePath(newNode: NodeConfiguration) {
    const parts = newNode.modulePath.split('/');
    parts[parts.length - 1] = newNode.name;
    newNode.modulePath = parts.join('/');


  }

  update(source: any) {
    // console.log(this.appConfiguration.nodeConfiguration);

    // Assigns the x and y position for the nodes
    const treeData = this.treemap(this.root);

    // Compute the new tree layout.
    const nodes = treeData.descendants();
    const links = treeData.descendants().slice(1);

    // Normalize for fixed-depth.
    nodes.forEach((d: any) => { d.y = d.depth * 180; });

    // ****************** Nodes section ***************************

    // Update the nodes...
    const node = this.svg.selectAll('g.node')
      .data(nodes, (d: any) => d.id || (d.id = ++this.i));

    // Enter any new modes at the parent's previous position.
    const nodeEnter = node.enter().append('g')
      .attr('class', 'node')
      .attr('transform', (d: any) => {
        return 'translate(' + source.y0 + ',' + source.x0 + ')';
      })
      .on('click', (e: any) => { this.click(e); });

    // Add Circle for the nodes
    nodeEnter.append('circle')
      .attr('class', 'node')
      .attr('r', 1e-6)
      .style('fill', (d: any) => {
        // return d.data.type == NodeType.module ? "black" : "white";
        // return d._children ? "lightsteelblue" : "#fff";
      })
      .on('click', (selectedNode) => {
        this.isMenuOpen = true;
        this.selectedNode = selectedNode;
        d3.select('#my_custom_menu')
          .style('position', 'absolute')
          .style('left', d3.event.clientX + 10 + 'px')
          .style('top', d3.event.clientY + 10 + 'px')
          .style('display', 'block');
        d3.event.stopPropagation();
      });


    // Add labels for the nodes
    nodeEnter.append('text')
      .attr('dy', '.35em')
      .attr('x', (d: any) => {
        return d.children || d._children ? -13 : 13;
      })
      .attr('text-anchor', (d: any) => {
        return d.children || d._children ? 'end' : 'start';
      })
      .attr('id', (d: any) => 'text-' + d.id)
      .text((d: any) => {
        return this.getNodeText(d.data);
      });

    // UPDATE
    const nodeUpdate = nodeEnter.merge(node);

    // Transition to the proper position for the node
    nodeUpdate.transition()
      .duration(this.duration)
      .attr('transform', (d: any) => {
        return 'translate(' + d.y + ',' + d.x + ')';
      });

    // Update the node attributes and style
    nodeUpdate.select('circle.node')
      .attr('r', 10)
      .style('fill', (d: any) => {
        // return d.data.type == NodeType.module ? "black" : "white";

        return d._children ? 'lightsteelblue' : '#fff';
      })
      .attr('cursor', 'pointer');

    // Remove any exiting nodes
    const nodeExit = node.exit().transition()
      .duration(this.duration)
      .attr('transform', (d: any) => {
        return 'translate(' + source.y + ',' + source.x + ')';
      })
      .remove();

    // On exit reduce the node circles size to 0
    nodeExit.select('circle')
      .attr('r', 1e-6);

    // On exit reduce the opacity of text labels
    nodeExit.select('text')
      .style('fill-opacity', 1e-6);

    // ****************** links section ***************************

    // Update the links...
    const link = this.svg.selectAll('path.link')
      .data(links, (d: any) => d.id);

    // Enter any new links at the parent's previous position.
    const linkEnter = link.enter().insert('path', 'g')
      .attr('class', 'link')
      .attr('d', (d: any) => {
        const o = { x: source.x0, y: source.y0 };
        return this.diagonal(o, o);
      });

    // UPDATE
    const linkUpdate = linkEnter.merge(link);

    // Transition back to the parent element position
    linkUpdate.transition()
      .duration(this.duration)
      .attr('d', (d: any) => this.diagonal(d, d.parent));

    // Remove any exiting links
    const linkExit = link.exit().transition()
      .duration(this.duration)
      .attr('d', (d: any) => {
        const o = { x: source.x, y: source.y };
        return this.diagonal(o, o);
      })
      .remove();

    // Store the old positions for transition.
    nodes.forEach((d: any) => {
      d.x0 = d.x;
      d.y0 = d.y;
    });
  }

  getNodeText(node: any) {
    return node.name + ' (' + node.type + ')';
  }

  diagonal(s: any, d: any) {
    const path = `M ${s.y} ${s.x}
        C ${(s.y + d.y) / 2} ${s.x},
        ${(s.y + d.y) / 2} ${d.x},
        ${d.y} ${d.x}`;

    return path;
  }

  click(d: any) {
    this.selectedNode = d;
    this.selectedNodeName = this.selectedNode.data.name;

    this.svg.selectAll('circle')
      .style('fill', '#fff');

    this.svg.selectAll('circle')
      .filter((node: any) => node.id === d.id)
      .style('fill', (node: any) => {
        return node.id === d.id ? 'black' : '#fff';
      });

  }

  addNode() {
    if (!this.selectedNode) {
      alert('Please select parent node!');
    }
    const newNode = { name: this.newNodeName, type: NodeType.moduleWithRoute };
    const newD3Node: any = d3.hierarchy(newNode);
    newD3Node.depth = this.selectedNode.depth + 1;
    newD3Node.height = this.selectedNode.height + 1;
    newD3Node.parent = this.selectedNode;
    newD3Node.id = ++this.i;
    if (!this.selectedNode.children) {
      this.selectedNode.children = [];
      this.selectedNode.data.children = [];
    }

    this.selectedNode.children.push(newD3Node);
    this.selectedNode.data.children.push(newD3Node.data);

    this.update(this.selectedNode);

  }

  removeNode() {
    const children = [];
    this.selectedNode.parent.children.forEach(child => {
      if (child.id !== this.selectedNode.id) {
        children.push(child);
      }
    });

    const dataChildren = [];
    this.selectedNode.parent.data.children.forEach(child => {
      if (child.id !== this.selectedNode.id) {
        dataChildren.push(child);
      }
    });

    this.selectedNode.parent.children = children;
    this.selectedNode.parent.data.children = children;

    if (this.selectedNode.parent.children.length === 0) {
      delete this.selectedNode.parent.children;
    }
    this.update(this.selectedNode.parent);

    if (this.isMenuOpen) {
      d3.select('#my_custom_menu')
        .style('display', 'none');
      this.isMenuOpen = false;
    }
  }

  updateNodeName() {
    this.selectedNode.data.name = this.selectedNodeName;
    // console.log(this.selectedNode);
    this.svg.select('#text-' + this.selectedNode.id).text(this.selectedNodeName);

    this.update(this.selectedNode);
  }

  addNodeWithComponent() {
    if (!this.selectedNode) {
      alert('Please select parent node!');
    }
    const newNode = {
      name: this.newNodeName,
      type: NodeType.moduleWithRoute,
      // modulePath: this.selectedNode.data.name == 'app' ? '' : this.selectedNode.data.name,
      modulePath: this.getModulePath(this.selectedNode, this.newNodeName),
      route: this.newNodeName,
      parentModule: this.selectedNode.data.name
    };
    const newD3Node: any = d3.hierarchy(newNode);
    newD3Node.depth = this.selectedNode.depth + 1;
    newD3Node.height = this.selectedNode.height + 1;
    newD3Node.parent = this.selectedNode;
    newD3Node.id = ++this.i;
    if (!this.selectedNode.children) {
      this.selectedNode.children = [];
      this.selectedNode.data.children = [];
    }

    this.selectedNode.children.push(newD3Node);
    this.selectedNode.data.children.push(newD3Node.data);

    this.update(this.selectedNode);

  }

  getModulePath(node: any, newNodeName: string) {
    // this.selectedNode.data.name == 'app' ? '' : this.selectedNode.data.name
    console.log(node);

    const modulePath: Array<string> = [];
    modulePath.push(newNodeName);

    if (node.parent && node.parent.data.name === 'app') {
      modulePath.push(node.data.name);
    } else {
      while (node.parent && node.data.name !== 'app') {
        modulePath.push(node.data.name);
        node = node.parent;
      }
    }
    if (modulePath.length === 1) {
      return newNodeName;
    }
    const fullModulePath = modulePath.reverse().join('/');
    console.log(fullModulePath);

    return fullModulePath;
  }

  generateApp() {
    this.showSpinner = true;
    this.dataService.generateApp(this.appConfiguration).subscribe((response: any) => {
      console.log(response);
      sdk.openProject(response);
      this.showSpinner = false;
    }, (err) => {
      this.showSpinner = false;
      alert(JSON.stringify(err));
      console.error(err);
    });

  }

  downloadApp() {
    this.showSpinner = true;
    this.dataService.downloadApp(this.appConfiguration).subscribe((response: any) => {
      saveAs(response, this.appConfiguration.name + '.zip');
      console.log(response);
      this.showSpinner = false;
    }, (err) => {
      this.showSpinner = false;
      alert(JSON.stringify(err));
      console.error(err);
    });

  }
}