/*
 * Copyright (C) Sapphirecode - All Rights Reserved
 * This file is part of graphviz-builder which is released under MIT.
 * See file 'LICENSE' for full license details.
 * Created by Timo Hocker <timo@scode.ovh>, May 2020
 */

import { GraphStyles, NodeStyles } from '../enums/Styles';
import { GraphLayouts } from '../enums/GraphLayouts';
import { Element } from './Element';
import { Edge } from './Edge';
import { Node } from './Node';
import { Color } from './Color';

interface NodeOptions {
  name: string;
  label: string;
  style: NodeStyles;
  color: Color;
}

export class Graph extends Element {
  public children: Array<Graph> = [];
  public nodes: Array<Node> = [];
  public is_root = false;
  public edges: Array<Edge> = [];
  public style?: GraphStyles;
  public color?: Color;
  public directional = true;
  public overlap?: boolean | string;
  public splines?: boolean | string;
  public layout?: GraphLayouts;

  private get attributes (): Array<{name: string; value: string}> {
    const attributes = [];

    if (typeof this.color !== 'undefined')
      attributes.push ({ name: 'color', value: this.color.toString () });
    if (typeof this.style !== 'undefined')
      attributes.push ({ name: 'style', value: this.style.toString () });
    if (typeof this.overlap !== 'undefined')
      attributes.push ({ name: 'overlap', value: this.overlap.toString () });
    if (typeof this.splines !== 'undefined')
      attributes.push ({ name: 'splines', value: this.splines.toString () });
    if (typeof this.layout !== 'undefined')
      attributes.push ({ name: 'layout', value: this.layout.toString () });

    return attributes;
  }

  // eslint-disable-next-line @typescript-eslint/naming-convention
  public toString (): string {
    const header = this.parent
      ? `subgraph cluster_${this.full_name}`
      : `${this.directional ? 'di' : ''}graph ${this.full_name}`;

    let attrs = `\n${this.attributes.map ((v) => `${v.name} = ${v.value}`)
      .join ('\n')}\n`;
    let children = `\n${this.children.map ((c) => c.toString ())
      .join ('\n')}\n`;
    let nodes = `\n${this.nodes.map ((c) => c.toString ())
      .join ('\n')}\n`;
    let edges = `\n${this.edges.map ((c) => c.toString ())
      .join ('\n')}\n`;

    if (attrs === '\n\n')
      attrs = '';
    if (children === '\n\n')
      children = '';
    if (nodes === '\n\n')
      nodes = '';
    if (edges === '\n\n')
      edges = '';

    const indented = `${attrs}${children}${nodes}${edges}`
      .replace (/\n$/u, '')
      .replace (/^/gmu, '  ')
      .split ('\n')
      .map ((v) => v.trimRight ())
      .join ('\n');

    return `${header} {${indented}\n}`
      .replace (/^\s+$/gmu, '');
  }

  public add_node (constructor: ((n: Node) => void) | string): string {
    const node = new Node ('unnamed', this.full_name);

    if (typeof constructor === 'string') {
      node.name = constructor;
      node.label = constructor;
    }
    else { constructor (node); }

    this.nodes.push (node);
    return node.full_name;
  }

  public add_graph (constructor: ((g: Graph) => void) | string): string {
    const graph = new Graph ('unnamed', this.full_name);

    graph.directional = this.directional;

    if (typeof constructor === 'string')
      graph.name = constructor;
    else
      constructor (graph);

    this.children.push (graph);
    return graph.full_name;
  }

  public add_edge (origin: string, target: string): void {
    this.edges.push (new Edge (origin, target, this.directional));
  }
}