package tf.bug.fancadetagless import cats._ import cats.implicits._ import higherkindness.droste.data._ import polymorphic._ import tf.bug.fancadegraph.Level import tf.bug.fancadegraph.PinDefinition import tf.bug.fancadegraph.Template import tf.bug.fancadegraph.Block import tf.bug.fancadescodec.Position import scalax.collection.Graph import scalax.collection.GraphEdge.DiHyperEdge import tf.bug.fancadegraph.Pin sealed trait FancadeF[A] object FancadeF { case class Create[A](from: Vector[A], template: Exists[Template], imports: Vector[PinDefinition], exports: Vector[PinDefinition]) extends FancadeF[A] case class Select[A](from: A, indicies: Vector[Int]) extends FancadeF[A] case class Combine[A](from: Vector[A]) extends FancadeF[A] case class Sequence[A](first: A, afterPin: PinDefinition, next: A) extends FancadeF[A] case class Noop[A]() extends FancadeF[A] implicit val fancadeTraverse: Traverse[FancadeF] = new Traverse[FancadeF] { override def traverse[G[_]: Applicative, A, B](fa: FancadeF[A])(f: A => G[B]): G[FancadeF[B]] = fa match { case Create(v, t, i, e) => v.traverse(f).map(w => Create(w, t, i, e)) case Select(a, i) => f(a).map(b => Select(b, i)) case Combine(v) => v.traverse(f).map(w => Combine(w)) case Sequence(i, p, n) => (f(i), f(n)).mapN((j, o) => Sequence(j, p, o)) case Noop() => Noop[B]().pure[G].widen } override def foldLeft[A, B](fa: FancadeF[A], b: B)(f: (B, A) => B): B = fa match { case Create(v, _, _, _) => v.foldLeft(b)(f) case Select(a, _) => f(b, a) case Combine(v) => v.foldLeft(b)(f) case Sequence(i, _, n) => f(f(b, i), n) case Noop() => b } override def foldRight[A, B](fa: FancadeF[A], lb: Eval[B])(f: (A, Eval[B]) => Eval[B]): Eval[B] = fa match { case Create(v, _, _, _) => v.foldr(lb)(f) case Select(a, _) => f(a, lb) case Combine(v) => v.foldr(lb)(f) case Sequence(i, _, n) => f(i, f(n, lb)) case Noop() => lb } } def origins(root: Int, stack: Vector[FancadeF[Int]]): Vector[(Int, PinDefinition)] = { stack(root) match { case Create(_, _, _, pins) => pins.map((root, _)) case Select(f, i) => val original = origins(f, stack) i.map(original(_)) case Combine(f) => f.flatMap(origins(_, stack)) case Sequence(f, _, _) => origins(f, stack) case Noop() => Vector.empty } } def filteredIndex[A](vec: Vector[A], f: A => Boolean): Vector[(A, Int)] = { vec.zipWithIndex.filter { case (e, _) => f(e) } } def render(f: Fix[FancadeF]): Level = { val pc: PointerChain[Id, FancadeF] = PointerChain.deduplicate[Id, FancadeF](f) val pcv: Vector[FancadeF[Int]] = pc.vertices val bdVector: Vector[Exists[Template]] = pcv.collect { case Create(_, template, _, _) => template } val blocks: Set[Block] = bdVector.zipWithIndex.map { case (template, idx) => Block( Position(0, idx, 0), template ) }.toSet val pcf: Vector[(FancadeF[Int], Int)] = filteredIndex(pcv, (_: FancadeF[Int]) match { case Create(_, _, _, _) => true case _ => false }) val pcfr: Int => Int = idx => pcf.indexWhere { case (_, i) => i == idx } val wires: Graph[Pin, DiHyperEdge] = pcv.zipWithIndex.foldLeft(Graph.empty[Pin, DiHyperEdge]) { case (graph, (entry, idx)) => entry match { case Create(from, _, imports, _) => val thisH = pcfr(idx) val dependencies: Vector[(Int, PinDefinition)] = from.flatMap(origins(_, pcv)) val newWires = imports.zip(dependencies).map { case (thisPinD, (thatI, thatPinD)) => val thisPin = Pin( Position(0, thisH, 0), thisPinD ) val thatPin = Pin( Position(0, pcfr(thatI), 0), thatPinD ) DiHyperEdge(thatPin, thisPin) } graph ++ newWires case Sequence(f, p, n) => if(pcv(n) == Noop()) graph else { val po = origins(f, pcv) val to = Pin( Position(0, pcfr(po.head._1), 0), p ) val fo = origins(n, pcv) val from = Pin( Position(0, pcfr(fo.head._1), 0), fo.head._2 ) graph ++ Set(DiHyperEdge(from, to)) } case _ => graph } } Level(blocks, wires) } }