"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var Array_1 = require("./Array");
var function_1 = require("./function");
exports.URI = 'Tree';
/**
 * Multi-way trees (aka rose trees) and forests, where a forest is
 *
 * ```ts
 * type Forest<A> = Array<Tree<A>>
 * ```
 *
 * @data
 * @constructor Tree
 * @since 1.6.0
 */
var Tree = /** @class */ (function () {
    function Tree(value, forest) {
        this.value = value;
        this.forest = forest;
    }
    Tree.prototype.map = function (f) {
        return new Tree(f(this.value), this.forest.map(function (tree) { return tree.map(f); }));
    };
    Tree.prototype.ap = function (fab) {
        var _this = this;
        return fab.chain(function (f) { return _this.map(f); }); // <- derived
    };
    /**
     * Flipped version of {@link ap}
     * @since 1.6.0
     */
    Tree.prototype.ap_ = function (fb) {
        return fb.ap(this);
    };
    Tree.prototype.chain = function (f) {
        var _a = f(this.value), value = _a.value, forest = _a.forest;
        return new Tree(value, function_1.concat(forest, this.forest.map(function (t) { return t.chain(f); })));
    };
    Tree.prototype.extract = function () {
        return this.value;
    };
    Tree.prototype.extend = function (f) {
        return new Tree(f(this), this.forest.map(function (t) { return t.extend(f); }));
    };
    Tree.prototype.reduce = function (b, f) {
        var r = f(b, this.value);
        var len = this.forest.length;
        for (var i = 0; i < len; i++) {
            r = this.forest[i].reduce(r, f);
        }
        return r;
    };
    Tree.prototype.inspect = function () {
        return this.toString();
    };
    Tree.prototype.toString = function () {
        return "new Tree(" + function_1.toString(this.value) + ", " + function_1.toString(this.forest) + ")";
    };
    return Tree;
}());
exports.Tree = Tree;
var map = function (fa, f) {
    return fa.map(f);
};
var of = function (a) {
    return new Tree(a, []);
};
var ap = function (fab, fa) {
    return fa.ap(fab);
};
var chain = function (fa, f) {
    return fa.chain(f);
};
var extract = function (fa) {
    return fa.extract();
};
var extend = function (fa, f) {
    return fa.extend(f);
};
var reduce = function (fa, b, f) {
    return fa.reduce(b, f);
};
var foldMap = function (M) { return function (fa, f) {
    return fa.reduce(M.empty, function (acc, a) { return M.concat(acc, f(a)); });
}; };
var foldr = function (fa, b, f) {
    var r = b;
    var len = fa.forest.length;
    for (var i = len - 1; i >= 0; i--) {
        r = foldr(fa.forest[i], r, f);
    }
    return f(fa.value, r);
};
function traverse(F) {
    var traverseF = Array_1.traverse(F);
    var r = function (ta, f) {
        return F.ap(F.map(f(ta.value), function (value) { return function (forest) { return new Tree(value, forest); }; }), traverseF(ta.forest, function (t) { return r(t, f); }));
    };
    return r;
}
function sequence(F) {
    var traverseF = traverse(F);
    return function (ta) { return traverseF(ta, function_1.identity); };
}
/**
 * @function
 * @since 1.6.0
 */
exports.getSetoid = function (S) {
    var SA;
    var R = {
        equals: function (x, y) { return S.equals(x.value, y.value) && SA.equals(x.forest, y.forest); }
    };
    SA = Array_1.getSetoid(R);
    return R;
};
/**
 * @instance
 * @since 1.6.0
 */
exports.tree = {
    URI: exports.URI,
    map: map,
    of: of,
    ap: ap,
    chain: chain,
    reduce: reduce,
    foldMap: foldMap,
    foldr: foldr,
    traverse: traverse,
    sequence: sequence,
    extract: extract,
    extend: extend
};
var asIndentedLines = function (indentation, forest) {
    var r = [];
    var len = forest.length;
    var tree;
    for (var i = 0; i < len; i++) {
        tree = forest[i];
        r.push({ value: tree.value, indentation: indentation, isLast: i === len - 1 });
        r.push.apply(r, asIndentedLines(indentation + 1, tree.forest));
    }
    return r;
};
var indentation = '   ';
var getIndentation = function (n) {
    var r = '';
    for (var i = 0; i < n; i++) {
        r += indentation;
    }
    return r;
};
var drawLine = function (line) {
    return getIndentation(line.indentation) + (line.isLast ? '└' : '├') + '─ ' + line.value;
};
/**
 * Neat 2-dimensional drawing of a forest
 * @function
 * @since 1.6.0
 */
exports.drawForest = function (forest) {
    return asIndentedLines(0, forest)
        .map(drawLine)
        .join('\n');
};
/**
 * Neat 2-dimensional drawing of a tree
 *
 * @example
 * import { Tree, drawTree } from 'fp-ts/lib/Tree'
 *
 * const fa = new Tree('a', [
 *   new Tree('b', []),
 *   new Tree('c', []),
 *   new Tree('d', [new Tree('e', []), new Tree('f', [])])
 * ])
 *
 * assert.strictEqual(drawTree(fa), `a
 * ├─ b
 * ├─ c
 * └─ d
 *    ├─ e
 *    └─ f`)
 *
 * @function
 * @since 1.6.0
 */
exports.drawTree = function (tree) {
    return tree.value + '\n' + exports.drawForest(tree.forest);
};
/**
 * Build a tree from a seed value
 * @function
 * @since 1.6.0
 */
exports.unfoldTree = function (b, f) {
    var _a = f(b), a = _a[0], bs = _a[1];
    return new Tree(a, exports.unfoldForest(bs, f));
};
/**
 * Build a tree from a seed value
 * @function
 * @since 1.6.0
 */
exports.unfoldForest = function (bs, f) {
    return bs.map(function (b) { return exports.unfoldTree(b, f); });
};
function unfoldTreeM(M) {
    var unfoldForestMM = unfoldForestM(M);
    return function (b, f) { return M.chain(f(b), function (_a) {
        var a = _a[0], bs = _a[1];
        return M.chain(unfoldForestMM(bs, f), function (ts) { return M.of(new Tree(a, ts)); });
    }); };
}
exports.unfoldTreeM = unfoldTreeM;
function unfoldForestM(M) {
    var traverseM = Array_1.traverse(M);
    var unfoldTree;
    return function (bs, f) {
        // tslint:disable-next-line
        if (unfoldTree === undefined) {
            unfoldTree = unfoldTreeM(M);
        }
        return traverseM(bs, function (b) { return unfoldTree(b, f); });
    };
}
exports.unfoldForestM = unfoldForestM;
