/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements.  See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership.  The ASF licenses this file
* to you 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.
*/

/*
* A third-party license is embedded for some of the code in this file:
* The tree layoutHelper implementation was originally copied from
* "d3.js"(https://github.com/d3/d3-hierarchy) with
* some modifications made for this project.
* (see more details in the comment of the specific method below.)
* The use of the source code of this file is also subject to the terms
* and consitions of the licence of "d3.js" (BSD-3Clause, see
* </licenses/LICENSE-d3>).
*/

/**
 * @file The layout algorithm of node-link tree diagrams. Here we using Reingold-Tilford algorithm to drawing
 *       the tree.
 */

import * as layout from '../../util/layout';
import { TreeNode } from '../../data/Tree';
import TreeSeriesModel from './TreeSeries';
import ExtensionAPI from '../../core/ExtensionAPI';

interface HierNode {
    defaultAncestor: TreeLayoutNode,
    ancestor: TreeLayoutNode,
    prelim: number,
    modifier: number,
    change: number,
    shift: number,
    i: number,
    thread: TreeLayoutNode
}

export interface TreeLayoutNode extends TreeNode {
    parentNode: TreeLayoutNode
    hierNode: HierNode
    children: TreeLayoutNode[]
}
/**
 * Initialize all computational message for following algorithm.
 */
export function init(inRoot: TreeNode) {
    const root = inRoot as TreeLayoutNode;
    root.hierNode = {
        defaultAncestor: null,
        ancestor: root,
        prelim: 0,
        modifier: 0,
        change: 0,
        shift: 0,
        i: 0,
        thread: null
    };

    const nodes = [root];
    let node;
    let children;

    while (node = nodes.pop()) { // jshint ignore:line
        children = node.children;
        if (node.isExpand && children.length) {
            const n = children.length;
            for (let i = n - 1; i >= 0; i--) {
                const child = children[i];
                child.hierNode = {
                    defaultAncestor: null,
                    ancestor: child,
                    prelim: 0,
                    modifier: 0,
                    change: 0,
                    shift: 0,
                    i: i,
                    thread: null
                };
                nodes.push(child);
            }
        }
    }
}

/**
 * The implementation of this function was originally copied from "d3.js"
 * <https://github.com/d3/d3-hierarchy/blob/4c1f038f2725d6eae2e49b61d01456400694bac4/src/tree.js>
 * with some modifications made for this program.
 * See the license statement at the head of this file.
 *
 * Computes a preliminary x coordinate for node. Before that, this function is
 * applied recursively to the children of node, as well as the function
 * apportion(). After spacing out the children by calling executeShifts(), the
 * node is placed to the midpoint of its outermost children.
 */
export function firstWalk(node: TreeLayoutNode, separation: SeparationFunc) {
    const children = node.isExpand ? node.children : [];
    const siblings = node.parentNode.children;
    const subtreeW = node.hierNode.i ? siblings[node.hierNode.i - 1] : null;
    if (children.length) {
        executeShifts(node);
        const midPoint = (children[0].hierNode.prelim + children[children.length - 1].hierNode.prelim) / 2;
        if (subtreeW) {
            node.hierNode.prelim = subtreeW.hierNode.prelim + separation(node, subtreeW);
            node.hierNode.modifier = node.hierNode.prelim - midPoint;
        }
        else {
            node.hierNode.prelim = midPoint;
        }
    }
    else if (subtreeW) {
        node.hierNode.prelim = subtreeW.hierNode.prelim + separation(node, subtreeW);
    }
    node.parentNode.hierNode.defaultAncestor = apportion(
        node,
        subtreeW,
        node.parentNode.hierNode.defaultAncestor || siblings[0],
        separation
    );
}


/**
 * The implementation of this function was originally copied from "d3.js"
 * <https://github.com/d3/d3-hierarchy/blob/4c1f038f2725d6eae2e49b61d01456400694bac4/src/tree.js>
 * with some modifications made for this program.
 * See the license statement at the head of this file.
 *
 * Computes all real x-coordinates by summing up the modifiers recursively.
 */
export function secondWalk(node: TreeLayoutNode) {
    const nodeX = node.hierNode.prelim + node.parentNode.hierNode.modifier;
    node.setLayout({x: nodeX}, true);
    node.hierNode.modifier += node.parentNode.hierNode.modifier;
}


export function separation(cb?: SeparationFunc) {
    return arguments.length ? cb : defaultSeparation;
}

/**
 * Transform the common coordinate to radial coordinate.
 */
export function radialCoordinate(rad: number, r: number) {
    rad -= Math.PI / 2;
    return {
        x: r * Math.cos(rad),
        y: r * Math.sin(rad)
    };
}

/**
 * Get the layout position of the whole view.
 */
export function getViewRect(seriesModel: TreeSeriesModel, api: ExtensionAPI) {
    return layout.getLayoutRect(
        seriesModel.getBoxLayoutParams(), {
            width: api.getWidth(),
            height: api.getHeight()
        }
    );
}

/**
 * All other shifts, applied to the smaller subtrees between w- and w+, are
 * performed by this function.
 *
 * The implementation of this function was originally copied from "d3.js"
 * <https://github.com/d3/d3-hierarchy/blob/4c1f038f2725d6eae2e49b61d01456400694bac4/src/tree.js>
 * with some modifications made for this program.
 * See the license statement at the head of this file.
 */
function executeShifts(node: TreeLayoutNode) {
    const children = node.children;
    let n = children.length;
    let shift = 0;
    let change = 0;
    while (--n >= 0) {
        const child = children[n];
        child.hierNode.prelim += shift;
        child.hierNode.modifier += shift;
        change += child.hierNode.change;
        shift += child.hierNode.shift + change;
    }
}

/**
 * The implementation of this function was originally copied from "d3.js"
 * <https://github.com/d3/d3-hierarchy/blob/4c1f038f2725d6eae2e49b61d01456400694bac4/src/tree.js>
 * with some modifications made for this program.
 * See the license statement at the head of this file.
 *
 * The core of the algorithm. Here, a new subtree is combined with the
 * previous subtrees. Threads are used to traverse the inside and outside
 * contours of the left and right subtree up to the highest common level.
 * Whenever two nodes of the inside contours conflict, we compute the left
 * one of the greatest uncommon ancestors using the function nextAncestor()
 * and call moveSubtree() to shift the subtree and prepare the shifts of
 * smaller subtrees. Finally, we add a new thread (if necessary).
 */
function apportion(
    subtreeV: TreeLayoutNode,
    subtreeW: TreeLayoutNode,
    ancestor: TreeLayoutNode,
    separation: SeparationFunc
): TreeLayoutNode {

    if (subtreeW) {
        let nodeOutRight = subtreeV;
        let nodeInRight = subtreeV;
        let nodeOutLeft = nodeInRight.parentNode.children[0];
        let nodeInLeft = subtreeW;

        let sumOutRight = nodeOutRight.hierNode.modifier;
        let sumInRight = nodeInRight.hierNode.modifier;
        let sumOutLeft = nodeOutLeft.hierNode.modifier;
        let sumInLeft = nodeInLeft.hierNode.modifier;

        while (nodeInLeft = nextRight(nodeInLeft), nodeInRight = nextLeft(nodeInRight), nodeInLeft && nodeInRight) {
            nodeOutRight = nextRight(nodeOutRight);
            nodeOutLeft = nextLeft(nodeOutLeft);
            nodeOutRight.hierNode.ancestor = subtreeV;
            const shift = nodeInLeft.hierNode.prelim + sumInLeft - nodeInRight.hierNode.prelim
                    - sumInRight + separation(nodeInLeft, nodeInRight);
            if (shift > 0) {
                moveSubtree(nextAncestor(nodeInLeft, subtreeV, ancestor), subtreeV, shift);
                sumInRight += shift;
                sumOutRight += shift;
            }
            sumInLeft += nodeInLeft.hierNode.modifier;
            sumInRight += nodeInRight.hierNode.modifier;
            sumOutRight += nodeOutRight.hierNode.modifier;
            sumOutLeft += nodeOutLeft.hierNode.modifier;
        }
        if (nodeInLeft && !nextRight(nodeOutRight)) {
            nodeOutRight.hierNode.thread = nodeInLeft;
            nodeOutRight.hierNode.modifier += sumInLeft - sumOutRight;

        }
        if (nodeInRight && !nextLeft(nodeOutLeft)) {
            nodeOutLeft.hierNode.thread = nodeInRight;
            nodeOutLeft.hierNode.modifier += sumInRight - sumOutLeft;
            ancestor = subtreeV;
        }
    }
    return ancestor;
}

/**
 * This function is used to traverse the right contour of a subtree.
 * It returns the rightmost child of node or the thread of node. The function
 * returns null if and only if node is on the highest depth of its subtree.
 */
function nextRight(node: TreeLayoutNode): TreeLayoutNode {
    const children = node.children;
    return children.length && node.isExpand ? children[children.length - 1] : node.hierNode.thread;
}

/**
 * This function is used to traverse the left contour of a subtree (or a subforest).
 * It returns the leftmost child of node or the thread of node. The function
 * returns null if and only if node is on the highest depth of its subtree.
 */
function nextLeft(node: TreeLayoutNode) {
    const children = node.children;
    return children.length && node.isExpand ? children[0] : node.hierNode.thread;
}

/**
 * If nodeInLeft’s ancestor is a sibling of node, returns nodeInLeft’s ancestor.
 * Otherwise, returns the specified ancestor.
 */
function nextAncestor(
    nodeInLeft: TreeLayoutNode,
    node: TreeLayoutNode,
    ancestor: TreeLayoutNode
): TreeLayoutNode {
    return nodeInLeft.hierNode.ancestor.parentNode === node.parentNode
        ? nodeInLeft.hierNode.ancestor : ancestor;
}

/**
 * The implementation of this function was originally copied from "d3.js"
 * <https://github.com/d3/d3-hierarchy/blob/4c1f038f2725d6eae2e49b61d01456400694bac4/src/tree.js>
 * with some modifications made for this program.
 * See the license statement at the head of this file.
 *
 * Shifts the current subtree rooted at wr.
 * This is done by increasing prelim(w+) and modifier(w+) by shift.
 */
function moveSubtree(
    wl: TreeLayoutNode,
    wr: TreeLayoutNode,
    shift: number
) {
    const change = shift / (wr.hierNode.i - wl.hierNode.i);
    wr.hierNode.change -= change;
    wr.hierNode.shift += shift;
    wr.hierNode.modifier += shift;
    wr.hierNode.prelim += shift;
    wl.hierNode.change += change;
}

/**
 * The implementation of this function was originally copied from "d3.js"
 * <https://github.com/d3/d3-hierarchy/blob/4c1f038f2725d6eae2e49b61d01456400694bac4/src/tree.js>
 * with some modifications made for this program.
 * See the license statement at the head of this file.
 */
function defaultSeparation(node1: TreeLayoutNode, node2: TreeLayoutNode): number {
    return node1.parentNode === node2.parentNode ? 1 : 2;
}

interface SeparationFunc {
    (node1: TreeLayoutNode, node2: TreeLayoutNode): number
}
