const fl = require('fantasy-land')

//- Textbook rose tree.
//+ type RoseTree a = { value :: a, children :: [RoseTree a] }
function RoseTree(value, children) {
  if (this.constructor !== RoseTree)
    return new RoseTree(value, children)

  Object.assign(this, { value, children })
}

/* Setoid a => Setoid (RoseTree a) */ {
  RoseTree.prototype[fl.equals] = function ({ value, children }) {
    return this.value[fl.equals](value)
      && this.children.length === children.length
      && this.children.every((child, i) =>
           child[fl.equals](children[i]))
  }
}

/* Ord a => Ord (RoseTree a) */ {
  RoseTree.prototype[fl.lte] = function ({ value, children }) {
    return this.value[fl.lte](value)
      ? this.value[fl.equals](value)
        ? this.children[fl.lte](children)
        : true
      : false
  }
}

/* Functor RoseTree */ {
  RoseTree.prototype[fl.map] = function (f) {
    return RoseTree(f(this.value), this.children.map(f))
  }
}

/* Apply RoseTree */ {
  RoseTree.prototype[fl.ap] = function ({ value: f, children: fs }) {
    const { value: x, children: xs } = this

    return RoseTree(f(x), [].concat(
      xs.map(x => x.map(f)),
      fs.map(f => this.value.ap(f))
    ))
  }
}

/* Applicative RoseTree */ {
  RoseTree[fl.of] = x => RoseTree(x, [])
}

/* Foldable RoseTree */ {
  RoseTree.prototype[fl.reduce] = function (f, acc) {
    return this.children.reduce(
      (acc, rt) => rt[fl.reduce](f, acc),
      f(acc, this.value))
  }
}

/* Traversable RoseTree */ {
  RoseTree.prototype[fl.traverse] = function (T, f) {
    return this.children[fl.traverse](T, f)[fl.ap](
      f(this.value).map(v => cs => RoseTree(v, cs))
    )
  }
}

/* Chain RoseTree */ {
  RoseTree.prototype[fl.chain] = function (f) {
    const { value: x, children: xs } = f(this.value)

    return RoseTree(x, [].concat(xs,
      this.children.map(x => x[fl.chain](f))
    ))
  }
}

/* Extend RoseTree */ {
  RoseTree.prototype[fl.extend] = function (f) {
    return RoseTree(f(this), this.children.map(x => x[fl.extend](f)))
  }
}

/* Comonad RoseTree */ {
  RoseTree.prototype[fl.extract] = function () {
    return this.value
  }
}

module.exports = RoseTree