/*
 * Software License Agreement (BSD License)
 *
 *  Copyright (c) 2011-2014, Willow Garage, Inc.
 *  Copyright (c) 2014-2015, Open Source Robotics Foundation
 *  All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions
 *  are met:
 *
 *   * Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *   * Redistributions in binary form must reproduce the above
 *     copyright notice, this list of conditions and the following
 *     disclaimer in the documentation and/or other materials provided
 *     with the distribution.
 *   * Neither the name of Open Source Robotics Foundation nor the names of its
 *     contributors may be used to endorse or promote products derived
 *     from this software without specific prior written permission.
 *
 *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 *  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 *  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 *  FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 *  COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 *  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 *  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 *  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 *  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 *  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
 *  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 *  POSSIBILITY OF SUCH DAMAGE.
 */

/** \author Jia Pan */

#include <hpp/fcl/internal/intersect.h>
#include <iostream>
#include <limits>
#include <vector>
#include <boost/math/special_functions/fpclassify.hpp>  // isnan.
#include <hpp/fcl/internal/tools.h>

namespace hpp {
namespace fcl {

bool Intersect::buildTrianglePlane(const Vec3f& v1, const Vec3f& v2,
                                   const Vec3f& v3, Vec3f* n, FCL_REAL* t) {
  Vec3f n_ = (v2 - v1).cross(v3 - v1);
  FCL_REAL norm2 = n_.squaredNorm();
  if (norm2 > 0) {
    *n = n_ / sqrt(norm2);
    *t = n->dot(v1);
    return true;
  }
  return false;
}

void TriangleDistance::segPoints(const Vec3f& P, const Vec3f& A, const Vec3f& Q,
                                 const Vec3f& B, Vec3f& VEC, Vec3f& X,
                                 Vec3f& Y) {
  Vec3f T;
  FCL_REAL A_dot_A, B_dot_B, A_dot_B, A_dot_T, B_dot_T;
  Vec3f TMP;

  T = Q - P;
  A_dot_A = A.dot(A);
  B_dot_B = B.dot(B);
  A_dot_B = A.dot(B);
  A_dot_T = A.dot(T);
  B_dot_T = B.dot(T);

  // t parameterizes ray P,A
  // u parameterizes ray Q,B

  FCL_REAL t, u;

  // compute t for the closest point on ray P,A to
  // ray Q,B

  FCL_REAL denom = A_dot_A * B_dot_B - A_dot_B * A_dot_B;

  t = (A_dot_T * B_dot_B - B_dot_T * A_dot_B) / denom;

  // clamp result so t is on the segment P,A

  if ((t < 0) || boost::math::isnan(t))
    t = 0;
  else if (t > 1)
    t = 1;

  // find u for point on ray Q,B closest to point at t

  u = (t * A_dot_B - B_dot_T) / B_dot_B;

  // if u is on segment Q,B, t and u correspond to
  // closest points, otherwise, clamp u, recompute and
  // clamp t

  if ((u <= 0) || boost::math::isnan(u)) {
    Y = Q;

    t = A_dot_T / A_dot_A;

    if ((t <= 0) || boost::math::isnan(t)) {
      X = P;
      VEC = Q - P;
    } else if (t >= 1) {
      X = P + A;
      VEC = Q - X;
    } else {
      X = P + A * t;
      TMP = T.cross(A);
      VEC = A.cross(TMP);
    }
  } else if (u >= 1) {
    Y = Q + B;

    t = (A_dot_B + A_dot_T) / A_dot_A;

    if ((t <= 0) || boost::math::isnan(t)) {
      X = P;
      VEC = Y - P;
    } else if (t >= 1) {
      X = P + A;
      VEC = Y - X;
    } else {
      X = P + A * t;
      T = Y - P;
      TMP = T.cross(A);
      VEC = A.cross(TMP);
    }
  } else {
    Y = Q + B * u;

    if ((t <= 0) || boost::math::isnan(t)) {
      X = P;
      TMP = T.cross(B);
      VEC = B.cross(TMP);
    } else if (t >= 1) {
      X = P + A;
      T = Q - X;
      TMP = T.cross(B);
      VEC = B.cross(TMP);
    } else {
      X = P + A * t;
      VEC = A.cross(B);
      if (VEC.dot(T) < 0) {
        VEC = VEC * (-1);
      }
    }
  }
}

FCL_REAL TriangleDistance::sqrTriDistance(const Vec3f S[3], const Vec3f T[3],
                                          Vec3f& P, Vec3f& Q) {
  // Compute vectors along the 6 sides

  Vec3f Sv[3];
  Vec3f Tv[3];
  Vec3f VEC;

  Sv[0] = S[1] - S[0];
  Sv[1] = S[2] - S[1];
  Sv[2] = S[0] - S[2];

  Tv[0] = T[1] - T[0];
  Tv[1] = T[2] - T[1];
  Tv[2] = T[0] - T[2];

  // For each edge pair, the vector connecting the closest points
  // of the edges defines a slab (parallel planes at head and tail
  // enclose the slab). If we can show that the off-edge vertex of
  // each triangle is outside of the slab, then the closest points
  // of the edges are the closest points for the triangles.
  // Even if these tests fail, it may be helpful to know the closest
  // points found, and whether the triangles were shown disjoint

  Vec3f V, Z, minP, minQ;
  FCL_REAL mindd;
  int shown_disjoint = 0;

  mindd = (S[0] - T[0]).squaredNorm() + 1;  // Set first minimum safely high

  for (int i = 0; i < 3; ++i) {
    for (int j = 0; j < 3; ++j) {
      // Find closest points on edges i & j, plus the
      // vector (and distance squared) between these points
      segPoints(S[i], Sv[i], T[j], Tv[j], VEC, P, Q);

      V = Q - P;
      FCL_REAL dd = V.dot(V);

      // Verify this closest point pair only if the distance
      // squared is less than the minimum found thus far.

      if (dd <= mindd) {
        minP = P;
        minQ = Q;
        mindd = dd;

        Z = S[(i + 2) % 3] - P;
        FCL_REAL a = Z.dot(VEC);
        Z = T[(j + 2) % 3] - Q;
        FCL_REAL b = Z.dot(VEC);

        if ((a <= 0) && (b >= 0)) return dd;

        FCL_REAL p = V.dot(VEC);

        if (a < 0) a = 0;
        if (b > 0) b = 0;
        if ((p - a + b) > 0) shown_disjoint = 1;
      }
    }
  }

  // No edge pairs contained the closest points.
  // either:
  // 1. one of the closest points is a vertex, and the
  //    other point is interior to a face.
  // 2. the triangles are overlapping.
  // 3. an edge of one triangle is parallel to the other's face. If
  //    cases 1 and 2 are not true, then the closest points from the 9
  //    edge pairs checks above can be taken as closest points for the
  //    triangles.
  // 4. possibly, the triangles were degenerate.  When the
  //    triangle points are nearly colinear or coincident, one
  //    of above tests might fail even though the edges tested
  //    contain the closest points.

  // First check for case 1

  Vec3f Sn;
  FCL_REAL Snl;

  Sn = Sv[0].cross(Sv[1]);  // Compute normal to S triangle
  Snl = Sn.dot(Sn);         // Compute square of length of normal

  // If cross product is long enough,

  if (Snl > 1e-15) {
    // Get projection lengths of T points

    Vec3f Tp;

    V = S[0] - T[0];
    Tp[0] = V.dot(Sn);

    V = S[0] - T[1];
    Tp[1] = V.dot(Sn);

    V = S[0] - T[2];
    Tp[2] = V.dot(Sn);

    // If Sn is a separating direction,
    // find point with smallest projection

    int point = -1;
    if ((Tp[0] > 0) && (Tp[1] > 0) && (Tp[2] > 0)) {
      if (Tp[0] < Tp[1])
        point = 0;
      else
        point = 1;
      if (Tp[2] < Tp[point]) point = 2;
    } else if ((Tp[0] < 0) && (Tp[1] < 0) && (Tp[2] < 0)) {
      if (Tp[0] > Tp[1])
        point = 0;
      else
        point = 1;
      if (Tp[2] > Tp[point]) point = 2;
    }

    // If Sn is a separating direction,

    if (point >= 0) {
      shown_disjoint = 1;

      // Test whether the point found, when projected onto the
      // other triangle, lies within the face.

      V = T[point] - S[0];
      Z = Sn.cross(Sv[0]);
      if (V.dot(Z) > 0) {
        V = T[point] - S[1];
        Z = Sn.cross(Sv[1]);
        if (V.dot(Z) > 0) {
          V = T[point] - S[2];
          Z = Sn.cross(Sv[2]);
          if (V.dot(Z) > 0) {
            // T[point] passed the test - it's a closest point for
            // the T triangle; the other point is on the face of S
            P = T[point] + Sn * (Tp[point] / Snl);
            Q = T[point];
            return (P - Q).squaredNorm();
          }
        }
      }
    }
  }

  Vec3f Tn;
  FCL_REAL Tnl;

  Tn = Tv[0].cross(Tv[1]);
  Tnl = Tn.dot(Tn);

  if (Tnl > 1e-15) {
    Vec3f Sp;

    V = T[0] - S[0];
    Sp[0] = V.dot(Tn);

    V = T[0] - S[1];
    Sp[1] = V.dot(Tn);

    V = T[0] - S[2];
    Sp[2] = V.dot(Tn);

    int point = -1;
    if ((Sp[0] > 0) && (Sp[1] > 0) && (Sp[2] > 0)) {
      if (Sp[0] < Sp[1])
        point = 0;
      else
        point = 1;
      if (Sp[2] < Sp[point]) point = 2;
    } else if ((Sp[0] < 0) && (Sp[1] < 0) && (Sp[2] < 0)) {
      if (Sp[0] > Sp[1])
        point = 0;
      else
        point = 1;
      if (Sp[2] > Sp[point]) point = 2;
    }

    if (point >= 0) {
      shown_disjoint = 1;

      V = S[point] - T[0];
      Z = Tn.cross(Tv[0]);
      if (V.dot(Z) > 0) {
        V = S[point] - T[1];
        Z = Tn.cross(Tv[1]);
        if (V.dot(Z) > 0) {
          V = S[point] - T[2];
          Z = Tn.cross(Tv[2]);
          if (V.dot(Z) > 0) {
            P = S[point];
            Q = S[point] + Tn * (Sp[point] / Tnl);
            return (P - Q).squaredNorm();
          }
        }
      }
    }
  }

  // Case 1 can't be shown.
  // If one of these tests showed the triangles disjoint,
  // we assume case 3 or 4, otherwise we conclude case 2,
  // that the triangles overlap.

  if (shown_disjoint) {
    P = minP;
    Q = minQ;
    return mindd;
  } else
    return 0;
}

FCL_REAL TriangleDistance::sqrTriDistance(const Vec3f& S1, const Vec3f& S2,
                                          const Vec3f& S3, const Vec3f& T1,
                                          const Vec3f& T2, const Vec3f& T3,
                                          Vec3f& P, Vec3f& Q) {
  Vec3f S[3];
  Vec3f T[3];
  S[0] = S1;
  S[1] = S2;
  S[2] = S3;
  T[0] = T1;
  T[1] = T2;
  T[2] = T3;

  return sqrTriDistance(S, T, P, Q);
}

FCL_REAL TriangleDistance::sqrTriDistance(const Vec3f S[3], const Vec3f T[3],
                                          const Matrix3f& R, const Vec3f& Tl,
                                          Vec3f& P, Vec3f& Q) {
  Vec3f T_transformed[3];
  T_transformed[0] = R * T[0] + Tl;
  T_transformed[1] = R * T[1] + Tl;
  T_transformed[2] = R * T[2] + Tl;

  return sqrTriDistance(S, T_transformed, P, Q);
}

FCL_REAL TriangleDistance::sqrTriDistance(const Vec3f S[3], const Vec3f T[3],
                                          const Transform3f& tf, Vec3f& P,
                                          Vec3f& Q) {
  Vec3f T_transformed[3];
  T_transformed[0] = tf.transform(T[0]);
  T_transformed[1] = tf.transform(T[1]);
  T_transformed[2] = tf.transform(T[2]);

  return sqrTriDistance(S, T_transformed, P, Q);
}

FCL_REAL TriangleDistance::sqrTriDistance(const Vec3f& S1, const Vec3f& S2,
                                          const Vec3f& S3, const Vec3f& T1,
                                          const Vec3f& T2, const Vec3f& T3,
                                          const Matrix3f& R, const Vec3f& Tl,
                                          Vec3f& P, Vec3f& Q) {
  Vec3f T1_transformed = R * T1 + Tl;
  Vec3f T2_transformed = R * T2 + Tl;
  Vec3f T3_transformed = R * T3 + Tl;
  return sqrTriDistance(S1, S2, S3, T1_transformed, T2_transformed,
                        T3_transformed, P, Q);
}

FCL_REAL TriangleDistance::sqrTriDistance(const Vec3f& S1, const Vec3f& S2,
                                          const Vec3f& S3, const Vec3f& T1,
                                          const Vec3f& T2, const Vec3f& T3,
                                          const Transform3f& tf, Vec3f& P,
                                          Vec3f& Q) {
  Vec3f T1_transformed = tf.transform(T1);
  Vec3f T2_transformed = tf.transform(T2);
  Vec3f T3_transformed = tf.transform(T3);
  return sqrTriDistance(S1, S2, S3, T1_transformed, T2_transformed,
                        T3_transformed, P, Q);
}

Project::ProjectResult Project::projectLine(const Vec3f& a, const Vec3f& b,
                                            const Vec3f& p) {
  ProjectResult res;

  const Vec3f d = b - a;
  const FCL_REAL l = d.squaredNorm();

  if (l > 0) {
    const FCL_REAL t = (p - a).dot(d);
    res.parameterization[1] = (t >= l) ? 1 : ((t <= 0) ? 0 : (t / l));
    res.parameterization[0] = 1 - res.parameterization[1];
    if (t >= l) {
      res.sqr_distance = (p - b).squaredNorm();
      res.encode = 2; /* 0x10 */
    } else if (t <= 0) {
      res.sqr_distance = (p - a).squaredNorm();
      res.encode = 1; /* 0x01 */
    } else {
      res.sqr_distance = (a + d * res.parameterization[1] - p).squaredNorm();
      res.encode = 3; /* 0x00 */
    }
  }

  return res;
}

Project::ProjectResult Project::projectTriangle(const Vec3f& a, const Vec3f& b,
                                                const Vec3f& c,
                                                const Vec3f& p) {
  ProjectResult res;

  static const size_t nexti[3] = {1, 2, 0};
  const Vec3f* vt[] = {&a, &b, &c};
  const Vec3f dl[] = {a - b, b - c, c - a};
  const Vec3f& n = dl[0].cross(dl[1]);
  const FCL_REAL l = n.squaredNorm();

  if (l > 0) {
    FCL_REAL mindist = -1;
    for (size_t i = 0; i < 3; ++i) {
      if ((*vt[i] - p).dot(dl[i].cross(n)) >
          0)  // origin is to the outside part of the triangle edge, then the
              // optimal can only be on the edge
      {
        size_t j = nexti[i];
        ProjectResult res_line = projectLine(*vt[i], *vt[j], p);

        if (mindist < 0 || res_line.sqr_distance < mindist) {
          mindist = res_line.sqr_distance;
          res.encode =
              static_cast<unsigned int>(((res_line.encode & 1) ? 1 << i : 0) +
                                        ((res_line.encode & 2) ? 1 << j : 0));
          res.parameterization[i] = res_line.parameterization[0];
          res.parameterization[j] = res_line.parameterization[1];
          res.parameterization[nexti[j]] = 0;
        }
      }
    }

    if (mindist < 0)  // the origin project is within the triangle
    {
      FCL_REAL d = (a - p).dot(n);
      FCL_REAL s = sqrt(l);
      Vec3f p_to_project = n * (d / l);
      mindist = p_to_project.squaredNorm();
      res.encode = 7;  // m = 0x111
      res.parameterization[0] = dl[1].cross(b - p - p_to_project).norm() / s;
      res.parameterization[1] = dl[2].cross(c - p - p_to_project).norm() / s;
      res.parameterization[2] =
          1 - res.parameterization[0] - res.parameterization[1];
    }

    res.sqr_distance = mindist;
  }

  return res;
}

Project::ProjectResult Project::projectTetrahedra(const Vec3f& a,
                                                  const Vec3f& b,
                                                  const Vec3f& c,
                                                  const Vec3f& d,
                                                  const Vec3f& p) {
  ProjectResult res;

  static const size_t nexti[] = {1, 2, 0};
  const Vec3f* vt[] = {&a, &b, &c, &d};
  const Vec3f dl[3] = {a - d, b - d, c - d};
  FCL_REAL vl = triple(dl[0], dl[1], dl[2]);
  bool ng = (vl * (a - p).dot((b - c).cross(a - b))) <= 0;
  if (ng &&
      std::abs(vl) > 0)  // abs(vl) == 0, the tetrahedron is degenerated; if ng
                         // is false, then the last vertex in the tetrahedron
                         // does not grow toward the origin (in fact origin is
                         // on the other side of the abc face)
  {
    FCL_REAL mindist = -1;

    for (size_t i = 0; i < 3; ++i) {
      size_t j = nexti[i];
      FCL_REAL s = vl * (d - p).dot(dl[i].cross(dl[j]));
      if (s > 0)  // the origin is to the outside part of a triangle face, then
                  // the optimal can only be on the triangle face
      {
        ProjectResult res_triangle = projectTriangle(*vt[i], *vt[j], d, p);
        if (mindist < 0 || res_triangle.sqr_distance < mindist) {
          mindist = res_triangle.sqr_distance;
          res.encode =
              static_cast<unsigned int>((res_triangle.encode & 1 ? 1 << i : 0) +
                                        (res_triangle.encode & 2 ? 1 << j : 0) +
                                        (res_triangle.encode & 4 ? 8 : 0));
          res.parameterization[i] = res_triangle.parameterization[0];
          res.parameterization[j] = res_triangle.parameterization[1];
          res.parameterization[nexti[j]] = 0;
          res.parameterization[3] = res_triangle.parameterization[2];
        }
      }
    }

    if (mindist < 0) {
      mindist = 0;
      res.encode = 15;
      res.parameterization[0] = triple(c - p, b - p, d - p) / vl;
      res.parameterization[1] = triple(a - p, c - p, d - p) / vl;
      res.parameterization[2] = triple(b - p, a - p, d - p) / vl;
      res.parameterization[3] =
          1 - (res.parameterization[0] + res.parameterization[1] +
               res.parameterization[2]);
    }

    res.sqr_distance = mindist;
  } else if (!ng) {
    res = projectTriangle(a, b, c, p);
    res.parameterization[3] = 0;
  }
  return res;
}

Project::ProjectResult Project::projectLineOrigin(const Vec3f& a,
                                                  const Vec3f& b) {
  ProjectResult res;

  const Vec3f d = b - a;
  const FCL_REAL l = d.squaredNorm();

  if (l > 0) {
    const FCL_REAL t = -a.dot(d);
    res.parameterization[1] = (t >= l) ? 1 : ((t <= 0) ? 0 : (t / l));
    res.parameterization[0] = 1 - res.parameterization[1];
    if (t >= l) {
      res.sqr_distance = b.squaredNorm();
      res.encode = 2; /* 0x10 */
    } else if (t <= 0) {
      res.sqr_distance = a.squaredNorm();
      res.encode = 1; /* 0x01 */
    } else {
      res.sqr_distance = (a + d * res.parameterization[1]).squaredNorm();
      res.encode = 3; /* 0x00 */
    }
  }

  return res;
}

Project::ProjectResult Project::projectTriangleOrigin(const Vec3f& a,
                                                      const Vec3f& b,
                                                      const Vec3f& c) {
  ProjectResult res;

  static const size_t nexti[3] = {1, 2, 0};
  const Vec3f* vt[] = {&a, &b, &c};
  const Vec3f dl[] = {a - b, b - c, c - a};
  const Vec3f& n = dl[0].cross(dl[1]);
  const FCL_REAL l = n.squaredNorm();

  if (l > 0) {
    FCL_REAL mindist = -1;
    for (size_t i = 0; i < 3; ++i) {
      if (vt[i]->dot(dl[i].cross(n)) >
          0)  // origin is to the outside part of the triangle edge, then the
              // optimal can only be on the edge
      {
        size_t j = nexti[i];
        ProjectResult res_line = projectLineOrigin(*vt[i], *vt[j]);

        if (mindist < 0 || res_line.sqr_distance < mindist) {
          mindist = res_line.sqr_distance;
          res.encode =
              static_cast<unsigned int>(((res_line.encode & 1) ? 1 << i : 0) +
                                        ((res_line.encode & 2) ? 1 << j : 0));
          res.parameterization[i] = res_line.parameterization[0];
          res.parameterization[j] = res_line.parameterization[1];
          res.parameterization[nexti[j]] = 0;
        }
      }
    }

    if (mindist < 0)  // the origin project is within the triangle
    {
      FCL_REAL d = a.dot(n);
      FCL_REAL s = sqrt(l);
      Vec3f o_to_project = n * (d / l);
      mindist = o_to_project.squaredNorm();
      res.encode = 7;  // m = 0x111
      res.parameterization[0] = dl[1].cross(b - o_to_project).norm() / s;
      res.parameterization[1] = dl[2].cross(c - o_to_project).norm() / s;
      res.parameterization[2] =
          1 - res.parameterization[0] - res.parameterization[1];
    }

    res.sqr_distance = mindist;
  }

  return res;
}

Project::ProjectResult Project::projectTetrahedraOrigin(const Vec3f& a,
                                                        const Vec3f& b,
                                                        const Vec3f& c,
                                                        const Vec3f& d) {
  ProjectResult res;

  static const size_t nexti[] = {1, 2, 0};
  const Vec3f* vt[] = {&a, &b, &c, &d};
  const Vec3f dl[3] = {a - d, b - d, c - d};
  FCL_REAL vl = triple(dl[0], dl[1], dl[2]);
  bool ng = (vl * a.dot((b - c).cross(a - b))) <= 0;
  if (ng &&
      std::abs(vl) > 0)  // abs(vl) == 0, the tetrahedron is degenerated; if ng
                         // is false, then the last vertex in the tetrahedron
                         // does not grow toward the origin (in fact origin is
                         // on the other side of the abc face)
  {
    FCL_REAL mindist = -1;

    for (size_t i = 0; i < 3; ++i) {
      size_t j = nexti[i];
      FCL_REAL s = vl * d.dot(dl[i].cross(dl[j]));
      if (s > 0)  // the origin is to the outside part of a triangle face, then
                  // the optimal can only be on the triangle face
      {
        ProjectResult res_triangle = projectTriangleOrigin(*vt[i], *vt[j], d);
        if (mindist < 0 || res_triangle.sqr_distance < mindist) {
          mindist = res_triangle.sqr_distance;
          res.encode =
              static_cast<unsigned int>((res_triangle.encode & 1 ? 1 << i : 0) +
                                        (res_triangle.encode & 2 ? 1 << j : 0) +
                                        (res_triangle.encode & 4 ? 8 : 0));
          res.parameterization[i] = res_triangle.parameterization[0];
          res.parameterization[j] = res_triangle.parameterization[1];
          res.parameterization[nexti[j]] = 0;
          res.parameterization[3] = res_triangle.parameterization[2];
        }
      }
    }

    if (mindist < 0) {
      mindist = 0;
      res.encode = 15;
      res.parameterization[0] = triple(c, b, d) / vl;
      res.parameterization[1] = triple(a, c, d) / vl;
      res.parameterization[2] = triple(b, a, d) / vl;
      res.parameterization[3] =
          1 - (res.parameterization[0] + res.parameterization[1] +
               res.parameterization[2]);
    }

    res.sqr_distance = mindist;
  } else if (!ng) {
    res = projectTriangleOrigin(a, b, c);
    res.parameterization[3] = 0;
  }
  return res;
}

}  // namespace fcl

}  // namespace hpp


////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

/// @project        Open Space Toolkit ▸ Physics
/// @file           OpenSpaceToolkit/Physics/Time/DateTime.cpp
/// @author         Lucas Brémond <lucas@loftorbital.com>
/// @license        Apache License 2.0

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

#include <OpenSpaceToolkit/Physics/Time/DateTime.hpp>

#include <OpenSpaceToolkit/Core/Error.hpp>
#include <OpenSpaceToolkit/Core/Utilities.hpp>

#include <boost/regex.hpp>

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

namespace ostk
{
namespace physics
{
namespace time
{

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

                                DateTime::DateTime                          (   const   Date&                       aDate,
                                                                                const   Time&                       aTime                                       )
                                :   date_(aDate),
                                    time_(aTime)
{

}

                                DateTime::DateTime                          (           Uint16                      aYear,
                                                                                Uint8                       aMonth,
                                                                                Uint8                       aDay,
                                                                                Uint8                       anHour,
                                                                                Uint8                       aMinute,
                                                                                Uint8                       aSecond,
                                                                                Uint16                      aMillisecond,
                                                                                Uint16                      aMicrosecond,
                                                                                Uint16                      aNanosecond                                 )
                                :   date_({aYear, aMonth, aDay}),
                                    time_({anHour, aMinute, aSecond, aMillisecond, aMicrosecond, aNanosecond})
{

}

bool                            DateTime::operator ==                       (   const   DateTime&                   aDateTime                                   ) const
{

    if ((!this->isDefined()) || (!aDateTime.isDefined()))
    {
        return false ;
    }

    return (date_ == aDateTime.date_) && (time_ == aDateTime.time_) ;

}

bool                            DateTime::operator !=                       (   const   DateTime&                   aDateTime                                   ) const
{

    if ((!this->isDefined()) || (!aDateTime.isDefined()))
    {
        return true ;
    }

    return (date_ != aDateTime.date_) || (time_ != aDateTime.time_) ;

}

std::ostream&                   operator <<                                 (           std::ostream&               anOutputStream,
                                                                                const   DateTime&                   aDateTime                                   )
{

    ostk::core::utils::Print::Header(anOutputStream, "DateTime") ;

    ostk::core::utils::Print::Line(anOutputStream) << "Year:" << (aDateTime.isDefined() ? String::Format("{:d}", aDateTime.date_.getYear()) : "Undefined") ;
    ostk::core::utils::Print::Line(anOutputStream) << "Month:" << (aDateTime.isDefined() ? String::Format("{:d}", aDateTime.date_.getMonth()) : "Undefined") ;
    ostk::core::utils::Print::Line(anOutputStream) << "Day:" << (aDateTime.isDefined() ? String::Format("{:d}", aDateTime.date_.getDay()) : "Undefined") ;

    ostk::core::utils::Print::Line(anOutputStream) << "Hour:" << (aDateTime.isDefined() ? String::Format("{:d}", aDateTime.time_.getHour()) : "Undefined") ;
    ostk::core::utils::Print::Line(anOutputStream) << "Minute:" << (aDateTime.isDefined() ? String::Format("{:d}", aDateTime.time_.getMinute()) : "Undefined") ;
    ostk::core::utils::Print::Line(anOutputStream) << "Second:" << (aDateTime.isDefined() ? String::Format("{:d}", aDateTime.time_.getSecond()) : "Undefined") ;
    ostk::core::utils::Print::Line(anOutputStream) << "Millisecond:" << (aDateTime.isDefined() ? String::Format("{:d}", aDateTime.time_.getMillisecond()) : "Undefined") ;
    ostk::core::utils::Print::Line(anOutputStream) << "Microsecond:" << (aDateTime.isDefined() ? String::Format("{:d}", aDateTime.time_.getMicrosecond()) : "Undefined") ;
    ostk::core::utils::Print::Line(anOutputStream) << "Nanosecond:" << (aDateTime.isDefined() ? String::Format("{:d}", aDateTime.time_.getNanosecond()) : "Undefined") ;

    ostk::core::utils::Print::Footer(anOutputStream) ;

    return anOutputStream ;

}

bool                            DateTime::isDefined                         ( ) const
{
    return date_.isDefined() && time_.isDefined() ;
}

const Date&                     DateTime::accessDate                        ( ) const
{

    if (!this->isDefined())
    {
        throw ostk::core::error::runtime::Undefined("DateTime") ;
    }

    return date_ ;

}

Time                            DateTime::getTime                           ( ) const
{

    if (!this->isDefined())
    {
        throw ostk::core::error::runtime::Undefined("DateTime") ;
    }

    return time_ ;

}

Date                            DateTime::getDate                           ( ) const
{

    if (!this->isDefined())
    {
        throw ostk::core::error::runtime::Undefined("DateTime") ;
    }

    return date_ ;

}

const Time&                     DateTime::accessTime                        ( ) const
{

    if (!this->isDefined())
    {
        throw ostk::core::error::runtime::Undefined("DateTime") ;
    }

    return time_ ;

}

Real                            DateTime::getJulianDate                     ( ) const
{

    using ostk::core::types::Int16 ;
    using ostk::core::types::Int32 ;

    if (!this->isDefined())
    {
        throw ostk::core::error::runtime::Undefined("DateTime") ;
    }

    const Uint16 year = date_.getYear() ;
    const Uint8 month = date_.getMonth() ;
    const Uint8 day = date_.getDay() ;

    const Int32 julianDate = day
                           + 1461 * (year + 4800 + (month - 14) / 12) / 4
                           + 367 * (month - 2 - (month - 14) / 12 * 12) / 12
                           - 3 * ((year + 4900 + (month - 14) / 12) / 100) / 4
                           - 32075 ;

    const Int16 hour = time_.getHour() ;
    const Int16 minute = time_.getMinute() ;
    const Int16 second = time_.getSecond() ;
    const Uint16 millisecond = time_.getMillisecond() ;
    const Uint16 microsecond = time_.getMicrosecond() ;
    const Uint16 nanosecond = time_.getNanosecond() ;

    const Real fractionalDay = ((hour - 12.0) + (minute / 60.0) + (second / 3600.0) + ((millisecond / 1e3) + (microsecond / 1e6) + (nanosecond / 1e9)) / 3600.0) / 24.0 ;

    return Real(julianDate) + fractionalDay ;

}

Real                            DateTime::getModifiedJulianDate             ( ) const
{

    if (!this->isDefined())
    {
        throw ostk::core::error::runtime::Undefined("DateTime") ;
    }

    return DateTime::ModifiedJulianDateFromJulianDate(this->getJulianDate()) ;

}

String                          DateTime::toString                         (   const   DateTime::Format&           aFormat                                     ) const
{

    if (!this->isDefined())
    {
        throw ostk::core::error::runtime::Undefined("DateTime") ;
    }

    switch (aFormat)
    {

        case DateTime::Format::Standard:
            return date_.toString(Date::Format::Standard) + " " + time_.toString(Time::Format::Standard) ;

        case DateTime::Format::ISO8601:
            return date_.toString(Date::Format::Standard) + "T" + time_.toString(Time::Format::ISO8601) ;

        case DateTime::Format::STK:
            return date_.toString(Date::Format::STK) + " " + time_.toString(Time::Format::ISO8601) ;

        default:
            throw ostk::core::error::runtime::Wrong("Format") ;
            break ;

    }

    return String::Empty() ;

}

DateTime                        DateTime::Undefined                         ( )
{
    return DateTime(Date::Undefined(), Time::Undefined()) ;
}

DateTime                        DateTime::J2000                             ( )
{
    return DateTime(Date::J2000(), Time::Noon()) ;
}

DateTime                        DateTime::GPSEpoch                          ( )
{
    return DateTime(Date::GPSEpoch(), Time::Midnight()) ;
}

DateTime                        DateTime::UnixEpoch                         ( )
{
    return DateTime(Date::UnixEpoch(), Time::Midnight()) ;
}

DateTime                        DateTime::ModifiedJulianDateEpoch           ( )
{
    return DateTime(Date::ModifiedJulianDateEpoch(), Time::Midnight()) ;
}

DateTime                        DateTime::JulianDate                        (   const   Real&                       aJulianDate                                 )
{

    using ostk::core::types::Int32 ;
    using ostk::core::types::Int64 ;
    using ostk::core::types::Uint32 ;
    using ostk::core::types::Uint64 ;

    if (!aJulianDate.isDefined())
    {
        throw ostk::core::error::runtime::Undefined("Julian Date") ;
    }

    if (aJulianDate < 0.0)
    {
        throw ostk::core::error::RuntimeError("Julian Date [{}] is negative.", aJulianDate) ;
    }

    // const Int32 J = std::floor(aJulianDate) ;

    // const Int32 y = 4716 ;
    // const Int32 v = 3 ;
    // const Int32 j = 1401 ;
    // const Int32 u = 5 ;
    // const Int32 m = 2 ;
    // const Int32 s = 153 ;
    // const Int32 n = 12 ;
    // const Int32 w = 2 ;
    // const Int32 r = 4 ;
    // const Int32 B = 274277 ;
    // const Int32 p = 1461 ;
    // const Int32 C = -38 ;

    // const Int32 f = J + j + (((4 * J + B) / 146097) * 3) / 4 + C ;
    // const Int32 e = r * f + v ;
    // const Int32 g = (e % p) / r ;
    // const Int32 h = u * g + w ;

    // const Int32 D = (h % s) / u + 1 ;
    // const Int32 M = ((h / s + m) % n) + 1 ;
    // const Int32 Y = (e / p) - y + (n + m - M) / n ;

    // Uint32 year = static_cast<Uint32>(Y) ;
    // Uint32 month = static_cast<Uint32>(M) ;
    // Uint32 day = static_cast<Uint32>(D) ;

    const Int64 integerJulianDate = std::floor(aJulianDate + 0.5) ;
    const double fractionalJulianDate = static_cast<double>(aJulianDate) - integerJulianDate + 0.5 ;

    const Int32 a = integerJulianDate + 32044 ;
    const Int32 b = std::floor((4 * a + 3) / 146097) ;
    const Int32 c = a - std::floor((b * 146097) / 4) ;

    const Int32 d = std::floor((4 * c + 3) / 1461) ;
    const Int32 e = c - std::floor((1461 * d) / 4) ;
    const Int32 m = std::floor((5 * e + 2) / 153) ;

    Uint32 year = static_cast<Uint32>(b * 100 + d - 4800 + std::floor(m / 10)) ;
    Uint32 month = static_cast<Uint32>(m + 3 - 12 * std::floor(m / 10)) ;
    Uint32 day = static_cast<Uint32>(e - std::floor((153 * m + 2) / 5) + 1) ;

    Uint64 nanosecondCountOfDay = Uint64(fractionalJulianDate * 86400.0 * 1e9) ;

    Uint32 hours = static_cast<Uint32>(std::floor(nanosecondCountOfDay / (3600.0 * 1e9))) ;

    if (nanosecondCountOfDay > Uint64(hours * 3600 * 1e9))
    {
        nanosecondCountOfDay -= Uint64(hours * 3600 * 1e9) ;
    }
    else
    {
        nanosecondCountOfDay = 0 ;
    }

    Uint32 minute = static_cast<Uint32>(std::floor(nanosecondCountOfDay / (60.0 * 1e9))) ;

    if (nanosecondCountOfDay > Uint64(minute * 60 * 1e9))
    {
        nanosecondCountOfDay -= Uint64(minute * 60 * 1e9) ;
    }
    else
    {
        nanosecondCountOfDay = 0 ;
    }

    Uint32 second = static_cast<Uint32>(std::floor(nanosecondCountOfDay / 1e9)) ;

    if (nanosecondCountOfDay > Uint64(second * 1e9))
    {
        nanosecondCountOfDay -= Uint64(second * 1e9) ;
    }
    else
    {
        nanosecondCountOfDay = 0 ;
    }

    Uint32 millisecond = static_cast<Uint32>(std::round(nanosecondCountOfDay / 1e6)) ;

    if (nanosecondCountOfDay > Uint64(millisecond * 1e6))
    {
        nanosecondCountOfDay -= Uint64(millisecond * 1e6) ;
    }
    else
    {
        nanosecondCountOfDay = 0 ;
    }

    Uint32 microsecond = static_cast<Uint32>(std::round(nanosecondCountOfDay / 1e3)) ;

    if (nanosecondCountOfDay > Uint64(microsecond * 1e3))
    {
        nanosecondCountOfDay -= Uint64(microsecond * 1e3) ;
    }
    else
    {
        nanosecondCountOfDay = 0 ;
    }

    Uint32 nanosecond = nanosecondCountOfDay ;

    if (millisecond == 1000)
    {
        second++ ;
        millisecond = 0 ;
    }

    if (second == 60)
    {
        minute++ ;
        second = 0 ;
    }

    if (minute == 60)
    {
        hours++ ;
        minute = 0 ;
    }

    if (hours == 24)
    {

        day++ ;
        hours = 0 ;

        if (day > 28)
        {
            throw ostk::core::error::RuntimeError("Implementation error.") ;
        }

    }

    return DateTime(year, month, day, hours, minute, second, millisecond, microsecond, nanosecond) ;

}

DateTime                        DateTime::ModifiedJulianDate                (   const   Real&                       aModifiedJulianDate                         )
{

    if (!aModifiedJulianDate.isDefined())
    {
        throw ostk::core::error::runtime::Undefined("Modified Julian Date") ;
    }

    if (aModifiedJulianDate < 0.0)
    {
        throw ostk::core::error::RuntimeError("Modified Julian Date [{}] is negative.", aModifiedJulianDate) ;
    }

    return DateTime::JulianDate(DateTime::JulianDateFromModifiedJulianDate(aModifiedJulianDate)) ;

}

DateTime                        DateTime::Parse                             (   const   String&                     aString,
                                                                                const   DateTime::Format&           aFormat                                     )
{

    if (aString.isEmpty())
    {
        throw ostk::core::error::runtime::Undefined("String") ;
    }

    switch (aFormat)
    {

        case DateTime::Format::Undefined: // Automatic format detection
        {

            if (aString.match(std::regex("^([-]?[0-9]+-[0-9]{2}-[0-9]{2})T([0-9]{2}:[0-9]{2}:[0-9]{2}(?:\\.[0-9]{1,9})?)$")))
            {
                return DateTime::Parse(aString, DateTime::Format::ISO8601) ;
            }

            if (aString.match(std::regex("^([\\d]{1,2} [\\w]{3} [\\d]{4}) ([0-9]{2}:[0-9]{2}:[0-9]{2}(?:\\.[0-9]{1,9})?)$")))
            {
                return DateTime::Parse(aString, DateTime::Format::STK) ;
            }

            return DateTime::Parse(aString, DateTime::Format::Standard) ;

        }

        case DateTime::Format::Standard:
        {

            boost::smatch match ;

            if (boost::regex_match(aString, match, boost::regex("^([-]?[0-9]+-[0-9]{2}-[0-9]{2}) ([0-9]{2}:[0-9]{2}:[0-9]{2}(?:\\.[0-9]{1,3})?(?:\\.[0-9]{1,3})?(?:\\.[0-9]{1,3})?)$")))
            {
                return DateTime(Date::Parse(String(match[1]), Date::Format::Standard), Time::Parse(String(match[2]), Time::Format::Standard)) ;
            }
            else
            {
                throw ostk::core::error::RuntimeError("Cannot parse [Standard] date-time string [{}].", aString) ;
            }

        }

        case DateTime::Format::ISO8601:
        {

            boost::smatch match ;

            if (boost::regex_match(aString, match, boost::regex("^([-]?[0-9]+-[0-9]{2}-[0-9]{2})T([0-9]{2}:[0-9]{2}:[0-9]{2}(?:\\.[0-9]{1,9})?)$")))
            {
                return DateTime(Date::Parse(String(match[1]), Date::Format::Standard), Time::Parse(String(match[2]), Time::Format::ISO8601)) ;
            }
            else
            {
                throw ostk::core::error::RuntimeError("Cannot parse [ISO 8601] date-time string [{}].", aString) ;
            }

        }

        case DateTime::Format::STK:
        {

            boost::smatch match ;

            if (boost::regex_match(aString, match, boost::regex("^([\\d]{1,2} [\\w]{3} [\\d]{4}) ([0-9]{2}:[0-9]{2}:[0-9]{2}(?:\\.[0-9]{1,9})?)$")))
            {
                return DateTime(Date::Parse(String(match[1]), Date::Format::STK), Time::Parse(String(match[2]), Time::Format::ISO8601)) ;
            }
            else
            {
                throw ostk::core::error::RuntimeError("Cannot parse [STK] date-time string [{}].", aString) ;
            }

        }

        default:
            throw ostk::core::error::runtime::Wrong("Format") ;
            break ;

    }

    return DateTime::Undefined() ;

}

Real                            DateTime::ModifiedJulianDateFromJulianDate  (   const   Real&                       aJulianDate                                 )
{
    return aJulianDate - 2400000.5 ; // MJD = JD - 2400000.5 [day]
}

Real                            DateTime::JulianDateFromModifiedJulianDate  (   const   Real&                       aModifiedJulianDate                         )
{
    return aModifiedJulianDate + 2400000.5 ; // JD = MJD + 2400000.5 [day]
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

}
}
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////


//   -----------------------------------------------------------------------------------------------
//    Copyright 2015 André Bergner. Distributed under the Boost Software License, Version 1.0.
//     (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//      --------------------------------------------------------------------------------------------

#pragma once

#include <iostream>
#include <array>

#include <boost/mpl/int.hpp>
#include <boost/mpl/min_max.hpp>
#include <boost/typeof/typeof.hpp>
#include <boost/proto/core.hpp>
#include <boost/proto/context.hpp>
#include <boost/proto/transform.hpp>
#include <boost/proto/debug.hpp>

#include <boost/algorithm/string/erase.hpp>

#include "callable_decltype.hpp"
#include "tuple_tools.hpp"
#include "demangle.h"

namespace flowz
{

namespace mpl = boost::mpl;
namespace proto = boost::proto;

namespace building_blocks
{
   using namespace proto;

   //  ---------------------------------------------------------------------------------------------
   // copy_domain -- all flowz expressions should be hold by value to avoid dangling references
   //  ---------------------------------------------------------------------------------------------

   template< typename E >
   struct copy_expr;

   struct copy_generator : proto::pod_generator< copy_expr > {};

   struct copy_domain : proto::domain< copy_generator >
   {
      template < typename T >
      struct as_child : proto_base_domain::as_expr<T> {};
   };

   template< typename Expr >
   struct copy_expr
   {
      BOOST_PROTO_EXTENDS( Expr, copy_expr<Expr>, copy_domain )
   };

   //  ---------------------------------------------------------------------------------------------
   // definition of the building blocks of the language
   //  ---------------------------------------------------------------------------------------------

   template< typename X >
   auto make_terminal( X x )
   {
      return  make_expr<tag::terminal,copy_domain>(x);
   }

   template < typename I >  struct placeholder       { using arity = I; };
   template < typename T >  struct placeholder_arity { using type = typename T::arity; };

   template< int n >
   auto make_placeholder()
   {
      return  make_expr<tag::terminal, copy_domain>( placeholder< mpl::int_<n> >{} );
   }

   template < typename idx = _ >
   using delayed_placeholder = subscript< terminal<placeholder<idx>> , terminal<placeholder<_>> >;

   //  ---------------------------------------------------------------------------------------------
   // block composition operators -- TODO needs correct grammar as arguments

   using channel_operator  = comma              < _ , _ >;
   using parallel_operator = bitwise_or         < _ , _ >;
   using sequence_operator = bitwise_or_assign  < _ , _ >;
   using feedback_operator = complement         < _ >;

   // private nodes

   struct binary_feedback_tag {};
   using binary_feedback_operator = binary_expr< binary_feedback_tag , _ , _ >;

   template < typename LeftExpr , typename RightExpr >
   auto make_binary_feedback( LeftExpr const & l , RightExpr const & r )
   {  return make_expr<building_blocks::binary_feedback_tag>( l, r );  }
}

using building_blocks :: make_terminal;

using building_blocks :: placeholder;
using building_blocks :: placeholder_arity;
using building_blocks :: make_placeholder;
using building_blocks :: delayed_placeholder;

using building_blocks :: channel_operator;
using building_blocks :: sequence_operator;
using building_blocks :: parallel_operator;
using building_blocks :: feedback_operator;
using building_blocks :: binary_feedback_operator;
using building_blocks :: make_binary_feedback;

BOOST_PROTO_DEFINE_ENV_VAR( current_input_t, current_input );
BOOST_PROTO_DEFINE_ENV_VAR( delayed_input_t, delayed_input );

//  ------------------------------------------------------------------------------------------------
// very simple delay_line operation on std::array --> TODO should be own type static_delay_line !

struct no_state {};

struct
{
    template< typename T , size_t N , typename Y >
    void operator()( std::array<T,N>& xs , Y y ) const
    {
        for ( size_t n = 1 ; n < xs.size() ; ++n )  xs[n-1] = xs[n];
        xs.back() = y;
    }

    template< typename T , typename Y >
    void operator()( std::array<T,0>& , Y ) const { }

    template< typename T , typename Y >
    void operator()( no_state , Y ) const { }

    template< typename T , typename Y >   // if input is not an array, e.g. for not fully
    void operator()( T& , Y ) const { }   // unpacked state while traversing the tree (tuple o'tuples)
}
rotate_push_back;

namespace transforms
{
   using namespace proto;

   //  ---------------------------------------------------------------------------------------------
   // static analysis transforms  --  helpers that inspect structure of expression
   //  ---------------------------------------------------------------------------------------------

   struct output_arity;

   struct input_arity : or_
   <  when
      <  delayed_placeholder<>
      ,  placeholder_arity<_value(_left)>()
      >
   ,  when
      <  terminal< placeholder<_> >
      ,  placeholder_arity<_value>()
      >
   ,  when
      <  terminal<_>
      ,  mpl::int_<0>()
      >
   ,  when
      <  feedback_operator
      ,  mpl::max
         <  mpl::int_<0>
         ,  mpl::minus< input_arity(_child) , output_arity(_child) >
         >()
      >
   ,  when
      <  binary_feedback_operator
      ,  mpl::plus
         <  mpl::max
            <  mpl::int_<0>
            ,  mpl::minus< input_arity(_left) , output_arity(_right) >
            >
         ,  mpl::max
            <  mpl::int_<0>
            ,  mpl::minus< input_arity(_right) , output_arity(_left) >
            >
         >()
      >
   ,  when
      <  parallel_operator
      ,  mpl::plus< input_arity(_left) , input_arity(_right) >()
      >
   ,  when
      <  sequence_operator
      ,  mpl::plus
         <  input_arity(_left)
         ,  mpl::max
            <  mpl::int_<0>
            ,  mpl::minus< input_arity(_right) , output_arity(_left) >
            >
         >()
      >
   ,  when
      <  nary_expr<_, vararg<_>>
      ,  fold<_, mpl::int_<0>(), mpl::max<input_arity, _state>()>
      >
   >
   {};

   struct output_arity : or_
   <  when
      <  channel_operator
      ,  mpl::plus< output_arity(_left) , output_arity(_right) >()
      >
   ,  when
      <  feedback_operator
      ,  output_arity(_child)
      >
   ,  when
      <  binary_feedback_operator
      ,  output_arity(_right)
      >
   ,  when
      <  parallel_operator
      ,  mpl::plus< output_arity(_left) , output_arity(_right) >()
      >
   ,  when
      <  sequence_operator
      ,  mpl::plus
         <  output_arity(_right)
         ,  mpl::max
            <  mpl::int_<0>
            ,  mpl::minus< output_arity(_left) , input_arity(_right) >
            >
         >()
      >
   ,  otherwise< mpl::int_<1>() >
   >
   {};

   template < typename Expr >
   using input_arity_t = typename boost::result_of<input_arity(Expr)>::type;

   template < typename Expr >
   using output_arity_t = typename boost::result_of<output_arity(Expr)>::type;

   // -------------------------------------------------------------------------------------------
   // front-panel for the expressions -- collects all loose wires
   // -------------------------------------------------------------------------------------------

   template < std::size_t N >
   inline auto make_front()
   {
      return make_front<N-1>() | make_placeholder<1>();
   }

   template <>
   inline auto make_front<1>()
   {
      return make_placeholder<1>();
   }

   template < typename Expr >
   auto add_front_panel( Expr const & x )
   {
      return  make_front<input_arity_t<Expr>::value>() |= x;
   }

   // -------------------------------------------------------------------------------------------
   // state and wire related tuple tools
   // -------------------------------------------------------------------------------------------

   template < typename arity , typename delay , typename base_other >
   struct make_arity_impl
   {
      using type = tuple_cat_t< repeat_t< arity::value-1ul, base_other >
                              , std::tuple<delay>
                              >;
   };

   template < typename delay , typename base_other >
   struct make_arity_impl<mpl::int_<0>, delay, base_other>
   {
      using type = std::tuple<>;
   };

   template < typename arity , typename delay = mpl::int_<0>, typename base_other = mpl::int_<0> >
   using make_arity = typename make_arity_impl<arity,delay,base_other>::type;

   struct delay_per_wire : callable_decltype
   {
      using default_t = placeholder<mpl::int_<0>>;

      template < typename Placeholder = default_t , typename Delay = default_t>
      auto operator()( Placeholder const & = default_t{}, Delay const & = default_t{} ) const
      {
         return make_arity<typename Placeholder::arity, typename Delay::arity>{};
      }
   };

   struct delay_per_wire_2 : callable_decltype
   {
      using default_t = placeholder<mpl::int_<0>>;

      template < typename Placeholder = default_t , typename Delay = default_t>
      auto operator()( Placeholder const & = default_t{}, Delay const & = default_t{} ) const
      {
         return make_arity<typename Placeholder::arity, typename Delay::arity, mpl::int_<-1>>{};
      }
   };

   struct tuple_cat_fn : callable_decltype
   {
      template < typename... Tuples >
      auto operator()( Tuples&&... ts ) const
      {
         return std::tuple_cat( std::forward<Tuples>(ts)... );
      }
   };

   struct make_tuple_fn : callable_decltype
   {
      template < typename... Ts >
      auto operator()( Ts&&... ts ) const
      {
         return std::make_tuple( std::forward<Ts>(ts)... );
      }
   };

   // -------------------------------------------------------------------------------------------
   // max-zip tuple
   // -------------------------------------------------------------------------------------------

   template < typename Tuple1, typename Tuple2, std::size_t... Ns >
   auto zip_with_max( Tuple1&& t1, Tuple2&& t2, std::index_sequence<Ns...> )
   {
      using namespace std;
      return tuple< typename mpl::max< decay_t<decltype(get<Ns>(t1))> , decay_t<decltype(get<Ns>(t2))> >::type... >{};
   }

   template < typename Tuple1, typename Tuple2 >
   auto zip_with_max( Tuple1&&, Tuple2&&, std::index_sequence<> )
   {
      return std::tuple<>{};
   }

   struct max_delay_of_wires : callable_decltype
   {
      template < typename Tuple_l , typename Tuple_r >
      auto operator()( Tuple_l&& tl, Tuple_r&& tr ) const
      {
         using tuple_tl = std::decay_t<Tuple_l>;
         using tuple_tr = std::decay_t<Tuple_r>;
         using min_size = typename mpl::min< mpl::int_<std::tuple_size<tuple_tl>::value>
                                           , mpl::int_<std::tuple_size<tuple_tr>::value>
                                           >::type;
         return  std::tuple_cat(  zip_with_max(tl,tr,std::make_index_sequence<min_size::value>{})
                               ,  tuple_drop<min_size::value>( std::forward<Tuple_l>(tl) )
                               ,  tuple_drop<min_size::value>( std::forward<Tuple_r>(tr) )
                               );
      }
   };

   template < int n , int m >
   using map_min = mpl::int_< n == -1 ? m : m == -1 ? n : (n<m)?n:m >;
   // -1 ,  m  ->  m
   //  n , -1  ->  n
   //  n ,  m  ->  min(n,m)

   template < typename Tuple1, typename Tuple2, std::size_t... Ns >
   auto zip_with_min( Tuple1&& t1, Tuple2&& t2, std::index_sequence<Ns...> )
   {
      using namespace std;
      return tuple< map_min< decay_t<decltype(get<Ns>(t1))>::value , decay_t<decltype(get<Ns>(t2))>::value >... >{};
   }

   template < typename Tuple1, typename Tuple2 >
   auto zip_with_min( Tuple1&&, Tuple2&&, std::index_sequence<> )
   {
      return std::tuple<>{};
   }

   struct min_delay_of_wires : callable_decltype
   {
      template < typename Tuple_l , typename Tuple_r >
      auto operator()( Tuple_l&& tl, Tuple_r&& tr ) const
      {
         using tuple_tl = std::decay_t<Tuple_l>;
         using tuple_tr = std::decay_t<Tuple_r>;
         using min_size = typename mpl::min< mpl::int_<std::tuple_size<tuple_tl>::value>
                                           , mpl::int_<std::tuple_size<tuple_tr>::value>
                                           >::type;
         return  std::tuple_cat(  zip_with_min(tl,tr,std::make_index_sequence<min_size::value>{})
                               ,  tuple_drop<min_size::value>( std::forward<Tuple_l>(tl) )
                               ,  tuple_drop<min_size::value>( std::forward<Tuple_r>(tr) )
                               );
      }
   };

   struct tuple_take_ : callable_decltype
   {
      template< typename Drop , typename Tuple >
      decltype(auto) operator()( Drop , Tuple const & t ) const
      {
         return tuple_take<Drop::value>(t);
      }
   };

   struct tuple_drop_ : callable_decltype
   {
      template< typename Drop , typename Tuple >
      decltype(auto) operator()( Drop , Tuple&& t ) const
      {
         return tuple_drop<Drop::value>( std::forward<Tuple>(t) );
      }
   };

   template < typename Generator , typename Combiner >
   struct input_delays
   {
      struct apply : or_
      <  when
         <  delayed_placeholder<>
         ,  Generator( _value(_left) , _value(_right) )
         >
      ,  when
         <  terminal< placeholder<_> >
         ,  Generator( _value() )
         >
      ,  when
         <  terminal<_>
         ,  std::tuple<>()
         >
      ,  when
         <  feedback_operator
         ,  tuple_drop_
            (  output_arity(_child)
            ,  apply(_child)
            )
         >
      ,  when
         <  binary_feedback_operator
         ,  tuple_cat_fn
            (  tuple_drop_
               (  output_arity(_right)
               ,  apply(_left)
               )
            ,  tuple_drop_
               (  output_arity(_left)
               ,  apply(_right)
               )
            )
         >
      ,  when
         <  parallel_operator
         ,  tuple_cat_fn( apply(_left) , apply(_right) )
         >
      ,  when
         <  sequence_operator
         ,  tuple_cat_fn
            (  apply(_left)
            ,  tuple_drop_
               (  output_arity(_left)
               ,  apply(_right)
               )
            )
         >
      ,  when
         <  nary_expr<_, vararg<_>>
         ,  fold<_, std::tuple<>(), Combiner(apply, _state) >
         >
      >
      {};
   };

   using max_input_delays = typename input_delays< delay_per_wire , max_delay_of_wires >::apply;
   using min_input_delays = typename input_delays< delay_per_wire_2 , min_delay_of_wires >::apply;

   template < typename Expr >
   using min_input_delays_t = decltype( min_input_delays{}( std::declval<Expr>() ) );

   // -------------------------------------------------------------------------------------------
   // state and wire related tuple tools -- type per tuple
   // -------------------------------------------------------------------------------------------

   // ResultType computes the result type for a given expression and a given input.

   // Handling of feedback.
   //
   //    Consider the following exmaple:
   //
   //    ~(_1[_1] + _2 + _3)   with typeof(_2) == float and typeof(_3) == complex<double>
   //                          --> What should be the type of _1 to be used for the delay?
   //
   //    Problem:  _1[_1] + _2 + _3   needs all three input types to determine its output type
   //                                 but the output type depends on expressions result and so
   //                                 depends the input type --> cyclic dependency
   //    Solution:
   //
   //      assign temporary type 'absorber' to _1.
   //
   //      absorber is special type that returns for all binary operations always the other type.
   //
   //      unary-op absorber   → absorber
   //      binary-op absorber T → T

   struct absorber {};

   using left_binary_absorber  = binary_expr< _ , terminal<absorber> , _ >;
   using right_binary_absorber = binary_expr< _ , _ , terminal<absorber> >;

   template <typename Transform>
   using absorb_left = when< left_binary_absorber, Transform(_right) >;

   template <typename Transform>
   using absorb_right = when< right_binary_absorber, Transform(_left) >;

   struct get_fn : callable_decltype
   {
      template < typename Tuple, int N >
      auto operator()( Tuple&& t, mpl::int_<N> ) const
      {
         return std::get<N-1>(t);
      }
   };

   struct make_flat_tuple : callable_decltype
   {
      template < typename T >
      auto operator()( T&& t ) const
      {
         return flatten_tuple( std::forward_as_tuple(t) );
      }
   };

   struct repeat_fn : callable_decltype
   {
      template < typename N, typename T >
      auto operator()( N, T&& ) const
      {
         return repeat_t< N::value, std::decay_t<T> >{};
      }
   };

   struct make_terminal : callable_decltype
   {
      template < typename T >
      auto operator()( T&& x ) const
      {
         return proto::make_expr<proto::tag::terminal>(x);
      }
   };

   struct ResultType : or_
   <  or_< absorb_left<ResultType>, absorb_right<ResultType> >
   ,  when
      <  delayed_placeholder<>
      ,  get_fn( _state , placeholder_arity<_value(_left)>() )
      >
   ,  when
      <  terminal< placeholder<_> >
      ,  get_fn( _state , placeholder_arity<_value>() )
      >
   ,  when
      <  terminal<_>
      ,  _value
      >
   ,  when
      <  binary_feedback_operator
         // we have to call this transform twice
         // 1. substitutes all that depend on the recursion with the absorber type
         // 2. removes all absorbers by merging them with their respecitve operations (absorb left/right)
         // TODO static_assert that result must not contain any leftover absorbers,
         //      e.g. the expression  ~(_1[_1]) will return absorber due to lacking information about
         //           the type inside the recursion cycle. User has to resolve explicitly with
         //           ~(_1[_1] * 1.f) for instance
      ,  make_flat_tuple( ResultType(
            ResultType( _right, tuple_cat_fn( repeat_fn(output_arity(_left), make_terminal(absorber())), _state ))
         ))
      >
   ,  when
      <  feedback_operator   // here same comment as for binary_feedback_operator applies
      ,  make_flat_tuple( ResultType(
            ResultType( _child, tuple_cat_fn( repeat_fn(output_arity(_child), make_terminal(absorber())), _state ))
         ))
      >
   ,  when
      <  sequence_operator
      ,  tuple_cat_fn(
            make_flat_tuple(
               ResultType( _right, tuple_take_( input_arity(_right), make_flat_tuple(ResultType(_left)) ) )
            )
         ,  tuple_drop_( input_arity(_right), make_flat_tuple(ResultType(_left)) )
         )
      >
   ,  when
      <  parallel_operator
      ,  tuple_cat_fn( make_flat_tuple(ResultType( _left, tuple_take_(input_arity(_left), _state) ) )
                     , make_flat_tuple(ResultType( _right, tuple_drop_(input_arity(_left), _state) ) )
                     )
      >
   ,  when
      <  channel_operator
      ,  tuple_cat_fn( make_flat_tuple(ResultType(_left))
                     , make_flat_tuple(ResultType(_right))
                     )
      >
   ,  when
      <  _
      ,  _default<ResultType>
      >
   >
   {};

   // -------------------------------------------------------------------------------------------
   //
   // -------------------------------------------------------------------------------------------

   template < bool... bs >
   struct bools {};

   template < typename Tuple , typename T >
   struct any_of_;

   template < typename... Ts , typename T >
   struct any_of_< std::tuple<Ts...> , T > : std::integral_constant
   <  bool
   ,  !std::is_same
      <  bools< std::is_same<T , Ts>::value... >
      ,  bools< !std::is_same<Ts, Ts>::value... >
      >::value
   >
   {};

   template < typename Expr >
   using needs_direct_input = any_of_< min_input_delays_t<Expr> , mpl::int_<0> >;

   template < typename Expr , std::size_t N >
   using needs_num_direct_input = any_of_< tuple_take_t<N,min_input_delays_t<Expr>> , mpl::int_<0> >;

   struct identity : callable_decltype
   {
      template < typename X >
      auto operator()( X&& x ) { return std::forward<X>(x); }
   };

   template < typename StateCtor >
   struct build_state_impl
   {
      struct apply : or_
      <  when
         <  feedback_operator
         ,  make_tuple_fn
            (  StateCtor(max_input_delays( _child ))
            ,  apply( _child )
            )
         >
      ,  when
         <  binary_feedback_operator
         ,  make_tuple_fn
            (  StateCtor( tuple_take_( output_arity(_left) , max_input_delays(_right) ))
            ,  apply( _left  )
            ,  apply( _right )
            )
         >
      ,  when
         <  sequence_operator
         ,  make_tuple_fn
            (  StateCtor( tuple_take_( output_arity(_left) , max_input_delays(_right) ))
            ,  apply( _left  )
            ,  apply( _right )
            )
         >
      ,  when
         <  parallel_operator
         ,  make_tuple_fn
            (  apply( _left  )
            ,  apply( _right )
            )
         >
      ,  otherwise< std::tuple<>() >
      >
      {};
   };

   template < typename StateCtor = identity >
   using build_state = typename build_state_impl<StateCtor>::apply;

   //  ---------------------------------------------------------------------------------------------
   // Evaluators -- transforms that work together for the evaluation of flowz-expressions
   //  ---------------------------------------------------------------------------------------------

   struct place_the_holder;
   struct place_delay;
   struct sequence;
   struct feedback;
   struct binary_feedback;
   struct parallel;

   struct eval_it : or_
   <  when
      <  delayed_placeholder<>
      ,  place_delay( _value(_left) , _value(_right) , _env_var<delayed_input_t> )
      >
   ,  when
      <  terminal< placeholder<_> >
      ,  place_the_holder( _value , _env_var<current_input_t> )
      >
   ,  when
      <  feedback_operator
      ,  feedback( _child , _env_var<current_input_t> , _env_var<delayed_input_t> )
      >
   ,  when
      <  binary_feedback_operator
      ,  binary_feedback( _left , _right, _env_var<current_input_t> , _env_var<delayed_input_t> )
      >
   ,  when
      <  sequence_operator
      ,  sequence( _left , _right , _env_var<current_input_t> , _env_var<delayed_input_t> )
      >
   ,  when
      <  parallel_operator
      ,  parallel( _left , _right , _env_var<current_input_t> , _env_var<delayed_input_t> )
      >
   ,  when
      <  channel_operator    //  grammar rule: only allowed after sequence or recursion at top of sub-tree
      ,  make_tuple_fn( eval_it(_left), eval_it(_right) )
      >
   ,  when
      <  _
      ,  _default< eval_it >
      >
   >
   {};

   //-----------------------------------------------------------------------------------------------

   template < typename T >
   using in_t = T const &;
   //using in_t = T;

   //  ---------------------------------------------------------------------------------------------
   // expression rebuilder -- transforms unary feedback nodes into binary ones
   //  ---------------------------------------------------------------------------------------------

   // example: a*_1 |= _1 + _2 |= _1[_1]   -->  future_expr: a*_1 |= _1 + _2

   //  ~( _1 |= ~(a*_1[_1]+_2[_1]) )

   struct split_future_subexpr;

   struct make_canonical : or_
   <  when
      <  terminal<_>
      ,  _
      >
   ,  when
      <  feedback_operator
      ,  split_future_subexpr( _child )
      >
   ,  otherwise< _default<make_canonical> >
   >
   {};

   struct split_in_sequence_combinator;
   struct lifted_default;

   struct unary_to_binary_feedback : or_
   <  when
      <  terminal<_>
      ,  make_tuple_fn(_)
      >
   ,  when
      <  sequence_operator
      ,  split_in_sequence_combinator( _left , _right , _state )
      >
   ,  when
      <  _
      ,  lifted_default( _ , _state )
      >
   >
   {};

   struct lifted_default : callable_decltype
   {
      template< typename Expr , typename State , std::size_t... Ns >
      auto apply( std::index_sequence<Ns...> , Expr const & x , State const & s ) const
      {
         using  tag = typename tag_of<Expr>::type;
         unary_to_binary_feedback   e;
         auto res = std::make_tuple( e(child_c<Ns>(x),s)... );
         return std::tuple_cat( std::make_tuple( make_expr<tag>( std::get<0>(std::get<Ns>(res))... ) )
                              , tail(std::get<0>(res))
                              );
      }

      template< typename Expr, typename State >
      auto operator()( Expr const & e , State const & s ) const
      {
         using idx_seq = std::make_index_sequence< proto::arity_of<Expr>::value >;
         return apply( idx_seq{}, e , s );
      }
   };

   // TODO
   // • in compile: transform expression into new expression w/ binary feedback node
   // • handle expr w/o future part, e.g. _1[_1] |= _1[_1] -->  (expr,())
   // • needs_direct_input must be parametrized prev. output arity
   // • handle parallel combiner, e.g. (_1 |= _1[_1]) | (_1 |= _1[_1]) should work (diff. split point)
   // • handle expr w/o current part, e.g. _1 |= _1 -->  ((),expr) compile error
   // • nested recursion:
   //   - idea: trafo1 -> go down and rebuild expr
   //                   , when in first (i.e. deepest) una_feedback call rebuild-splitter
   //                   , return this expr
   //                   , what to do with bin_feeback?

   struct split_future_subexpr : callable_decltype
   {
      template < typename Expr >
      auto operator()( in_t<Expr> x ) const
      {
         make_canonical  t;
         unary_to_binary_feedback  e;
         auto split_pred = [](auto const & l, auto const & r)
         {
            using l_expr_t = decltype(l);
            using r_expr_t = decltype(r);
            return needs_num_direct_input<r_expr_t,output_arity_t<l_expr_t>::value>{};
         };
         //return impl( e(t(add_front_panel(x)), split_pred) );
         return impl( e(t(make_front<output_arity_t<Expr>::value>() |= x), split_pred) );
      }

      template < typename LiftedExpr >
      auto impl( in_t<LiftedExpr> x ) const
      {
         return make_binary_feedback( std::get<0>(x), std::get<1>(x) );
      }
   };

   struct split_in_sequence_combinator : callable_decltype
   {
      template < typename LeftExpr , typename RightExpr , typename State >
      auto operator()( in_t<LeftExpr> l , in_t<RightExpr> r , State const & s ) const
      {
         unary_to_binary_feedback  e;
         return impl( e(l,s), r, s );
      }

   private:

      template < typename LL , typename LR , typename R , typename Pred >
      auto impl( in_t<std::tuple<LL,LR>> l , in_t<R> r , in_t<Pred> p ) const
      {
         return std::make_tuple( std::get<0>(l), std::get<1>(l) |= r );
      }

      template < typename L , typename R , typename Pred >
      auto impl( in_t<std::tuple<L>> l , in_t<R> r , in_t<Pred> p ) const
      {
         return impl2( p(std::get<0>(l),r), l, r, p );
      }

      template < typename L , typename R , typename P >
      auto impl2( std::true_type , in_t<L> l , in_t<R> r , in_t<P> p ) const
      {
         unary_to_binary_feedback  e;
         return impl3( l , e(r,p) );
      }

      template < typename LeftExpr , typename RL , typename RR >
      auto impl3( in_t<LeftExpr> l , in_t<std::tuple<RL,RR>> r ) const
      {
         return std::make_tuple( std::get<0>(l) |= std::get<0>(r) , std::get<1>(r) );
      }

      template < typename LeftExpr , typename R >
      auto impl3( in_t<LeftExpr> l , in_t<std::tuple<R>> r ) const
      {
         return std::make_tuple( std::get<0>(l) |= std::get<0>(r) );
      }

      template < typename L , typename RightExpr , typename P >
      auto impl2( std::false_type , in_t<L> l , in_t<RightExpr> r , in_t<P> ) const
      {
         return std::make_tuple( std::get<0>(l) , r );
      }

   };

   //-----------------------------------------------------------------------------------------------

   struct place_the_holder : callable_decltype
   {
      template < typename I , typename Tuple >
      auto operator()( placeholder<I> const & , Tuple const & args ) const
      {
         return std::get<I::value-1>( args );
      }
   };

   struct place_delay : callable_decltype
   {
      template < typename I , typename J , typename Delayed_input >
      auto operator()( placeholder<I> , placeholder<J> , in_t<Delayed_input> del_in ) const
      {
         auto const & s = std::get<I::value-1>(std::get<0>(del_in));
         return s[s.size()-J::value];
      }
   };

   struct sequence : callable_decltype
   {
      template < typename LeftExpr , typename RightExpr , typename Input , typename State >
      auto operator()( in_t<LeftExpr> l , in_t<RightExpr> r , in_t<Input> input , State state ) const
      {
         eval_it  e;

         auto in_state    = deep_tie(std::get<0>(state));
         auto node_state  = deep_tie(std::get<0>(std::get<1>(state)));
         auto left_state  = deep_tie(std::get<1>(std::get<1>(state)));
         auto right_state = deep_tie(std::get<2>(std::get<1>(state)));

         //static_assert( std::tuple_size<Input>::value == std::tuple_size<decltype(in_state)>::value );

         auto left_delayed_input = tuple_take<input_arity_t<LeftExpr>::value>(in_state);

         auto left_result =
            flatten_tuple( std::make_tuple(
               e( l, 0, ( current_input = tuple_take<input_arity_t<LeftExpr>::value>(input)
                        , delayed_input = std::tie( left_delayed_input, left_state )
                        ))
            ));

         using left_size = std::tuple_size<decltype(left_result)>;
         auto right_input = tuple_cat( left_result, tuple_drop<input_arity_t<LeftExpr>::value>(input) );
         auto right_delayed_input = std::tuple_cat( node_state , tuple_drop<input_arity_t<LeftExpr>::value>(in_state) );

         auto right_result =
            flatten_tuple( std::make_tuple(
               e( r, 0, ( current_input = right_input
                        , delayed_input = std::tie( right_delayed_input, right_state )
                        ))
            ));

         tuple_for_each( rotate_push_back, node_state, left_result );

         return tuple_cat( right_result
                         , tuple_drop< input_arity_t<RightExpr>::value >( std::move(left_result) )
                         , tuple_drop< input_arity_t<LeftExpr>::value + left_size::value >( input )
                         );
      }
   };

   struct bottom_type {};

   struct feedback : callable_decltype
   {
      template < typename Expr , typename Input , typename State >
      auto operator()( in_t<Expr> x , in_t<Input> input , State state ) const
      {
         eval_it  e;

         auto in_state    = deep_tie(std::get<0>(state));
         auto node_state  = deep_tie(std::get<0>(std::get<1>(state)));
         auto child_state = deep_tie(std::get<1>(std::get<1>(state)));
         auto next_state  = std::tuple_cat( node_state, in_state );

         auto aligned_input = std::tuple_cat( repeat_t<output_arity_t<Expr>::value, bottom_type>{} , input );
         auto result =
            flatten_tuple( std::make_tuple(
               e( x, 0, ( current_input = aligned_input , delayed_input = std::tie(next_state,child_state) ))
            ));

         tuple_for_each( rotate_push_back, node_state, std::tuple_cat( result, input) );
         using size = std::tuple_size<decltype(result)>;
         return tuple_cat( result, tuple_drop<size::value>(input) );
      }
   };

   struct binary_feedback : callable_decltype
   {
      template < typename LeftExpr , typename RightExpr , typename Input , typename State >
      auto operator()( in_t<LeftExpr> l , in_t<RightExpr> r , in_t<Input> input , State state ) const
      {
         eval_it  e;

         auto in_state    = deep_tie(std::get<0>(state));
         auto node_state  = deep_tie(std::get<0>(std::get<1>(state)));
         auto left_state  = deep_tie(std::get<1>(std::get<1>(state)));
         auto right_state = deep_tie(std::get<2>(std::get<1>(state)));

         auto future_input = std::tuple_cat
                           ( repeat_t<output_arity_t<LeftExpr>::value, bottom_type>{}
                           //, tuple_drop< input_arity_t<LeftExpr>::value-output_arity_t<RightExpr>::value >(input) );
                           , tuple_drop< std::min(0,input_arity_t<LeftExpr>::value-output_arity_t<RightExpr>::value) >(input) );
                           // TODO min( 0 , drop_value )
         auto left_delayed_input = std::tuple_cat
               //( node_state , tuple_drop< input_arity_t<LeftExpr>::value-output_arity_t<RightExpr>::value >(in_state) );
               ( node_state , tuple_drop< std::min(0,input_arity_t<LeftExpr>::value-output_arity_t<RightExpr>::value) >(in_state) );

         auto result =
            flatten_tuple( std::make_tuple(
               e( r, 0, ( current_input = future_input , delayed_input = std::tie(left_delayed_input,right_state) ))
            ));

         auto promise_input = std::tuple_cat( result, tuple_take< input_arity_t<LeftExpr>::value-output_arity_t<RightExpr>::value >(input) );
         auto right_delayed_input = std::tuple_cat
               ( repeat_t<output_arity_t<LeftExpr>::value, no_state>{}
               , tuple_take< input_arity_t<LeftExpr>::value-output_arity_t<RightExpr>::value >(in_state) );

         auto promise_result =
            flatten_tuple( std::make_tuple(
               e( l, 0, ( current_input = promise_input , delayed_input = std::tie( right_delayed_input ,left_state) ))
            ));

         tuple_for_each( rotate_push_back, node_state, promise_result );

         return tuple_cat( result
                         //, tuple_drop< input_arity_t<RightExpr>::value >( std::move(left_result) )
                         //, tuple_drop< input_arity_t<LeftExpr>::value + left_size::value >( input )
                         );
      }
   };

   struct parallel : callable_decltype
   {
      template < typename LeftExpr , typename RightExpr , typename Input , typename State >
      auto operator()( in_t<LeftExpr> l , in_t<RightExpr> r , in_t<Input> input , State state ) const
      {
         eval_it  e;

         auto in_state    = deep_tie(std::get<0>(state));
         auto left_state  = deep_tie(std::get<0>(std::get<1>(state)));
         auto right_state = deep_tie(std::get<1>(std::get<1>(state)));

         auto in_state_l = tuple_take<input_arity_t<LeftExpr>::value>(in_state);
         auto left_state_  = ( current_input = tuple_take<input_arity_t<LeftExpr>::value>(input)
                             , delayed_input = std::tie(in_state_l,left_state)
                             );

         auto in_state_r = tuple_drop<input_arity_t<LeftExpr>::value>(in_state);
         auto right_state_ = ( current_input = tuple_drop<input_arity_t<LeftExpr>::value>(input)
                             , delayed_input = std::tie(in_state_r,right_state)
                             );
         return std::make_tuple
                (  e( l, 0, left_state_ )
                ,  e( r, 0, right_state_ )
                );
      }
   };

}

using  transforms :: input_arity;
using  transforms :: max_input_delays;
using  transforms :: build_state;
using  transforms :: input_arity_t;
using  transforms :: output_arity;
using  transforms :: eval_it;
using  transforms :: add_front_panel;

//  ------------------------------------------------------------------------------------------------
// supporting state tools
//  ------------------------------------------------------------------------------------------------

//  ------------------------------------------------------------------------------------------------
// lift_into_tuple  --  lifts a meta-function into a tuple,
//                      i.e. applies it on each type in tuple, returns tuple of new types

template< typename F , typename Tuple >
struct lift_into_tuple;

template< typename F , typename... Ts >
struct lift_into_tuple< F , std::tuple<Ts...> >
{
    using type = std::tuple< typename F::template apply_t<Ts>... >;
};

template< typename F , typename Tuple >
using lift_into_tuple_t = typename lift_into_tuple<F,Tuple>::type;

//  ------------------------------------------------------------------------------------------------
// to_array  --  meta-function that maps mpl::int_<N> to array<T,N>

template< typename T , typename Int >
struct to_array_impl
{
   using type = std::array< T , std::decay_t<Int>::value >;
};

template< typename T >
struct to_array_impl< T, mpl::int_<0> >
{
   using type = no_state;     // array<T,N> prevents empty base class optimization to kick in.
};

template< typename T >
struct to_array
{
   template< typename Int >
   using apply_t = typename to_array_impl<T,std::decay_t<Int>>::type;
};

template < typename T >
struct to_array_tuple
{
   struct apply : proto::callable_decltype
   {
       template< typename Tuple >
       auto operator()( Tuple ) { return lift_into_tuple_t< to_array<T>, Tuple > {}; }
   };
};

//  ------------------------------------------------------------------------------------------------
// compile  --  main function of framework
//  • takes an expression
//  • returns closure holding the expression and an associated context with state & ceofficients
//  ------------------------------------------------------------------------------------------------

template
<  typename Expression
,  typename State
,  size_t   arity
>
struct stateful_lambda
{
private:

   Expression  expr_;
   State       state_;

   template < typename... Args >
   auto call_impl( mpl::int_<0> , Args const &... args ) -> decltype(auto)
   {
      std::tuple<> in_state;
      auto in_tuple = std::tie(in_state,state_);
      auto result = eval_it{}( expr_, 0, ( current_input = std::make_tuple(args...)
                                         , delayed_input = in_tuple ) );
      return flatten_tuple( std::make_tuple( result ));
   }

   template < int arg_diff , typename... Args >
   auto call_impl( mpl::int_<arg_diff> , Args const &... args )
   {
      return [ args...
             , expr = *this ]           // TODO should not be a lambda, but a type that is
      ( auto const &... missing_args ) mutable  //      aware of the # of args (enable_if them)
      {
         return expr( args..., missing_args... );
      };
   }

public:

   stateful_lambda( Expression expr ) : expr_( expr )
   {
      //build_state<> b;
      //std::cout << "• size expr:  " << sizeof(expr_)  << std::endl;
      //std::cout << "• size state: " << sizeof(state_) << std::endl;
      //std::cout << "• state: ";     print_state(state_);
      ////print_state(b(expr));
   }

   template < typename... Args , typename = std::enable_if_t< sizeof...(Args) <= arity > >
   auto operator()( Args const &... args ) -> decltype(auto)
   {
      return call_impl( mpl::int_< arity - sizeof...(Args) >{} , args... );
   }
};

auto compile = []( auto expr_ )
{
   // static_assert( is_valid_expr<decltype(expr_)>::value , "This expression is not valid." );
   // TODO must not contain private expressions, yet.

   using arity_t = input_arity_t<decltype(expr_)>;

   transforms :: make_canonical  canonize;

   auto expr = canonize( add_front_panel(expr_) );
   using expr_t = decltype(expr);

   auto builder = build_state< to_array_tuple<float>::apply >{};
   using state_t = decltype( builder(expr) );

   return stateful_lambda< expr_t, state_t, arity_t::value>{ expr };
};

const auto _1 = make_placeholder<1>();
const auto _2 = make_placeholder<2>();
const auto _3 = make_placeholder<3>();
const auto _4 = make_placeholder<4>();
const auto _5 = make_placeholder<5>();
const auto _6 = make_placeholder<6>();

}  // flowz


#include <config.h>
#include <assert.h>
#include <boost/concept/usage.hpp>
#include <boost/format.hpp>
#include <dune/common/exceptions.hh>
#include <dune/istl/scalarproducts.hh>
#include <dune/istl/solvers.hh>
#include <dune/multiscale/msfem/localproblems/localoperator.hh>
#include <dune/multiscale/problems/selector.hh>
#include <dune/multiscale/common/df_io.hh>
#include <dune/multiscale/msfem/localproblems/localsolutionmanager.hh>
#include <dune/multiscale/tools/misc.hh>
#include <dune/xt/common/logging.hh>
#include <dune/xt/common/math.hh>
#include <dune/xt/common/memory.hh>
#include <dune/xt/common/configuration.hh>
#include <dune/xt/common/timings.hh>
#include <dune/xt/common/ranges.hh>
#include <dune/xt/common/parallel/partitioner.hh>
#include <dune/grid/utility/partitioning/seedlist.hh>
#include <dune/gdt/products/l2.hh>
#include <dune/stuff/grid/walker.hh>
#include <dune/stuff/grid/walker/functors.hh>
#include <iterator>
#include <memory>
#include <sstream>
#include <string>
#include <tuple>
#include <vector>

#include <dune/multiscale/msfem/localproblems/localgridlist.hh>
#include <dune/multiscale/tools/misc/outputparameter.hh>
#include "localproblemsolver.hh"

namespace Dune {
namespace Multiscale {

/** \brief define output parameters for local problems
 *  appends "local_problems" for path
 **/
struct LocalProblemDataOutputParameters : public OutputParameters
{
public:
  explicit LocalProblemDataOutputParameters(const Problem::ProblemContainer& problem);
};

LocalProblemDataOutputParameters::LocalProblemDataOutputParameters(const DMP::ProblemContainer& problem)
  : OutputParameters(problem.config().get("global.datadir", "data") + std::string("/local_problems/"))
{
}

LocalProblemSolver::LocalProblemSolver(const Problem::ProblemContainer& problem,
                                       CommonTraits::SpaceType coarse_space,
                                       LocalGridList& localgrid_list)
  : localgrid_list_(localgrid_list)
  , coarse_space_(coarse_space)
  , problem_(problem)
{
}

void LocalProblemSolver::solve_all_on_single_cell(
    const MsFEMTraits::CoarseEntityType& coarseCell,
    MsFEMTraits::LocalSolutionVectorType& all_localproblem_solutions) const
{
  assert(all_localproblem_solutions.size() > 0);

  const bool hasBoundary = coarseCell.hasBoundaryIntersections();
  const auto numBoundaryCorrectors = DSG::is_simplex_grid(*coarse_space_) ? 1u : 2u;
  const auto numInnerCorrectors = all_localproblem_solutions.size() - numBoundaryCorrectors;

  // clear return argument
  for (auto& localSol : all_localproblem_solutions)
    localSol->vector() *= 0;

  const auto& local_space = all_localproblem_solutions[0]->space();

  //! define the discrete (elliptic) local MsFEM problem operator
  // ( effect of the discretized differential operator on a certain discrete function )
  LocalProblemOperator localProblemOperator(problem_, *coarse_space_, local_space);

  // right hand side vector of the algebraic local MsFEM problem
  MsFEMTraits::LocalSolutionVectorType allLocalRHS(all_localproblem_solutions.size());
  for (auto& it : allLocalRHS)
    it = Dune::XT::Common::make_unique<MsFEMTraits::LocalGridDiscreteFunctionType>(local_space,
                                                                                "rhs of local MsFEM problem");

  localProblemOperator.assemble_all_local_rhs(coarseCell, allLocalRHS);

  for (auto i : Dune::XT::Common::value_range(all_localproblem_solutions.size())) {
    auto& current_rhs = *allLocalRHS[i];
    auto& current_solution = *all_localproblem_solutions[i];

    // is the right hand side of the local MsFEM problem equal to zero or almost identical to zero?
    // if yes, the solution of the local MsFEM problem is also identical to zero. The solver is getting a problem with
    // this situation, which is why we do not solve local msfem problems for zero-right-hand-side, since we already know
    // the result.
    //! TODO calculating the norm has really bad performance impact, is the instability actually still there?
    //    const auto norm = GDT::Products::L2<typename MsFEMTraits::LocalGridViewType>(current_rhs.space().grid_view())
    //                          .induced_norm(current_rhs);
    //    if (norm < 1e-12) {
    //      current_solution.vector() *= 0;
    //      MS_LOG_DEBUG << boost::format("Local MsFEM problem with solution zero. (corrector %d)") % i << std::endl;
    //      continue;
    //    }

    // don't solve local problems for boundary correctors if coarse cell has no boundary intersections
    if (i >= numInnerCorrectors && !hasBoundary) {
      current_solution.vector() *= 0;
      MS_LOG_DEBUG << "Zero-Boundary corrector." << std::endl;
      continue;
    }
    localProblemOperator.apply_inverse(current_rhs, current_solution);
  }
}

void LocalProblemSolver::solve_for_all_cells()
{
  const auto& grid = coarse_space_->grid_view().grid();
  const auto coarseGridSize = grid.size(0) - grid.overlapSize(0);

  MS_LOG_INFO << boost::format("Rank %d will solve local problems for %d coarse entities\n") % grid.comm().rank()
                     % coarseGridSize;
  DXTC_TIMINGS.start("msfem.local.solve_for_all_cells");

  // we want to determine minimum, average and maxiumum time for solving a local msfem problem in the current method
  Dune::XT::Common::MinMaxAvg<double> solveTime;

  const auto interior = coarse_space_->grid_view().grid().template leafGridView<InteriorBorder_Partition>();
  typedef std::remove_const<decltype(interior)>::type InteriorType;
  GDT::SystemAssembler<CommonTraits::SpaceType, InteriorType> walker(*coarse_space_, interior);
  Dune::XT::Common::IndexSetPartitioner<InteriorType> ip(interior.indexSet());
  SeedListPartitioning<typename InteriorType::Grid, 0> partitioning(interior, ip);

  const std::function<void(const CommonTraits::EntityType&)> func = [&](const CommonTraits::EntityType& coarseEntity) {
    const int coarse_index = walker.ansatz_space().grid_view().indexSet().index(coarseEntity);
    MS_LOG_DEBUG << "-------------------------" << std::endl << "Coarse index " << coarse_index << std::endl;

    // take time
    //    DXTC_TIMINGS.start("msfem.local.solve_all_on_single_cell");
    LocalproblemSolutionManager localSolutionManager(*coarse_space_, coarseEntity, localgrid_list_);
    // solve the problems
    solve_all_on_single_cell(coarseEntity, localSolutionManager.getLocalSolutions());
    //    solveTime(DXTC_TIMINGS.stop("msfem.local.solve_all_on_single_cell") / 1000.f);

    // save the local solutions to disk/mem
    localSolutionManager.save();

    //    DXTC_TIMINGS.resetTiming("msfem.local.solve_all_on_single_cell");
  };

  walker.add(func);
  walker.assemble(partitioning);

  //! @todo The following debug-output is wrong (number of local problems may be different)
  const auto totalTime = DXTC_TIMINGS.stop("msfem.local.solve_for_all_cells") / 1000.f;
  MS_LOG_INFO << "Local problems solved for " << coarseGridSize << " coarse grid entities.\n"
              //               << "Minimum time for solving a local problem = " << solveTime.min() << "s.\n"
              //               << "Maximum time for solving a local problem = " << solveTime.max() << "s.\n"
              //               << "Average time for solving a local problem = " << solveTime.average() << "s.\n"
              << "Total time for computing and saving the localproblems = " << totalTime << "s on rank"
              << coarse_space_->grid_view().grid().comm().rank() << std::endl;
} // assemble_all

} // namespace Multiscale {
} // namespace Dune {