/**
 * Copyright (c) 2014-present, Facebook, Inc.
 * All rights reserved.
 *
 * This source code is licensed under the BSD-style license found in the
 * LICENSE file in the root directory of this source tree. An additional grant
 * of patent rights can be found in the PATENTS file in the same directory.
 */

#include <string.h>

#include "CSSLayout.h"
#include "CSSNodeList.h"

#ifdef _MSC_VER
#include <float.h>
#define isnan _isnan

/* define fmaxf if < VC12 */
#if _MSC_VER < 1800
__forceinline const float fmaxf(const float a, const float b) {
  return (a > b) ? a : b;
}
#endif
#endif

typedef struct CSSCachedMeasurement {
  float availableWidth;
  float availableHeight;
  CSSMeasureMode widthMeasureMode;
  CSSMeasureMode heightMeasureMode;

  float computedWidth;
  float computedHeight;
} CSSCachedMeasurement;

// This value was chosen based on empiracle data. Even the most complicated
// layouts should not require more than 16 entries to fit within the cache.
enum { CSS_MAX_CACHED_RESULT_COUNT = 16 };

typedef struct CSSLayout {
  float position[4];
  float dimensions[2];
  CSSDirection direction;

  float computedFlexBasis;

  // Instead of recomputing the entire layout every single time, we
  // cache some information to break early when nothing changed
  uint32_t generationCount;
  CSSDirection lastParentDirection;

  uint32_t nextCachedMeasurementsIndex;
  CSSCachedMeasurement cachedMeasurements[CSS_MAX_CACHED_RESULT_COUNT];
  float measuredDimensions[2];

  CSSCachedMeasurement cachedLayout;
} CSSLayout;

typedef struct CSSStyle {
  CSSDirection direction;
  CSSFlexDirection flexDirection;
  CSSJustify justifyContent;
  CSSAlign alignContent;
  CSSAlign alignItems;
  CSSAlign alignSelf;
  CSSPositionType positionType;
  CSSWrapType flexWrap;
  CSSOverflow overflow;
  float flexGrow;
  float flexShrink;
  float flexBasis;
  float margin[CSSEdgeCount];
  float position[CSSEdgeCount];
  float padding[CSSEdgeCount];
  float border[CSSEdgeCount];
  float dimensions[2];
  float minDimensions[2];
  float maxDimensions[2];
} CSSStyle;

typedef struct CSSNode {
  CSSStyle style;
  CSSLayout layout;
  uint32_t lineIndex;
  bool hasNewLayout;
  bool isTextNode;
  CSSNodeRef parent;
  CSSNodeListRef children;
  bool isDirty;

  struct CSSNode *nextChild;

  CSSSize (*measure)(void *context,
                     float width,
                     CSSMeasureMode widthMode,
                     float height,
                     CSSMeasureMode heightMode);
  void (*print)(void *context);
  void *context;
} CSSNode;

static float
computedEdgeValue(const float edges[CSSEdgeCount], const CSSEdge edge, const float defaultValue) {
  CSS_ASSERT(edge <= CSSEdgeEnd, "Cannot get computed value of multi-edge shorthands");

  if (!CSSValueIsUndefined(edges[edge])) {
    return edges[edge];
  }

  if ((edge == CSSEdgeTop || edge == CSSEdgeBottom) &&
      !CSSValueIsUndefined(edges[CSSEdgeVertical])) {
    return edges[CSSEdgeVertical];
  }

  if ((edge == CSSEdgeLeft || edge == CSSEdgeRight || edge == CSSEdgeStart || edge == CSSEdgeEnd) &&
      !CSSValueIsUndefined(edges[CSSEdgeHorizontal])) {
    return edges[CSSEdgeHorizontal];
  }

  if (!CSSValueIsUndefined(edges[CSSEdgeAll])) {
    return edges[CSSEdgeAll];
  }

  if (edge == CSSEdgeStart || edge == CSSEdgeEnd) {
    return CSSUndefined;
  }

  return defaultValue;
}

static int32_t gNodeInstanceCount = 0;

CSSNodeRef CSSNodeNew() {
  const CSSNodeRef node = calloc(1, sizeof(CSSNode));
  CSS_ASSERT(node, "Could not allocate memory for node");
  gNodeInstanceCount++;

  CSSNodeInit(node);
  return node;
}

void CSSNodeFree(const CSSNodeRef node) {
  CSSNodeListFree(node->children);
  free(node);
  gNodeInstanceCount--;
}

void CSSNodeFreeRecursive(const CSSNodeRef root) {
  while (CSSNodeChildCount(root) > 0) {
    const CSSNodeRef child = CSSNodeGetChild(root, 0);
    CSSNodeRemoveChild(root, child);
    CSSNodeFreeRecursive(child);
  }
  CSSNodeFree(root);
}

int32_t CSSNodeGetInstanceCount() {
  return gNodeInstanceCount;
}

void CSSNodeInit(const CSSNodeRef node) {
  node->parent = NULL;
  node->children = CSSNodeListNew(4);
  node->hasNewLayout = true;
  node->isDirty = false;

  node->style.flexGrow = 0;
  node->style.flexShrink = 0;
  node->style.flexBasis = CSSUndefined;

  node->style.alignItems = CSSAlignStretch;
  node->style.alignContent = CSSAlignFlexStart;

  node->style.direction = CSSDirectionInherit;
  node->style.flexDirection = CSSFlexDirectionColumn;

  node->style.overflow = CSSOverflowVisible;

  // Some of the fields default to undefined and not 0
  node->style.dimensions[CSSDimensionWidth] = CSSUndefined;
  node->style.dimensions[CSSDimensionHeight] = CSSUndefined;

  node->style.minDimensions[CSSDimensionWidth] = CSSUndefined;
  node->style.minDimensions[CSSDimensionHeight] = CSSUndefined;

  node->style.maxDimensions[CSSDimensionWidth] = CSSUndefined;
  node->style.maxDimensions[CSSDimensionHeight] = CSSUndefined;

  for (CSSEdge edge = CSSEdgeLeft; edge < CSSEdgeCount; edge++) {
    node->style.position[edge] = CSSUndefined;
    node->style.margin[edge] = CSSUndefined;
    node->style.padding[edge] = CSSUndefined;
    node->style.border[edge] = CSSUndefined;
  }

  node->layout.dimensions[CSSDimensionWidth] = CSSUndefined;
  node->layout.dimensions[CSSDimensionHeight] = CSSUndefined;

  // Such that the comparison is always going to be false
  node->layout.lastParentDirection = (CSSDirection) -1;
  node->layout.nextCachedMeasurementsIndex = 0;
  node->layout.computedFlexBasis = CSSUndefined;

  node->layout.measuredDimensions[CSSDimensionWidth] = CSSUndefined;
  node->layout.measuredDimensions[CSSDimensionHeight] = CSSUndefined;
  node->layout.cachedLayout.widthMeasureMode = (CSSMeasureMode) -1;
  node->layout.cachedLayout.heightMeasureMode = (CSSMeasureMode) -1;
}

void _CSSNodeMarkDirty(const CSSNodeRef node) {
  if (!node->isDirty) {
    node->isDirty = true;
    node->layout.computedFlexBasis = CSSUndefined;
    if (node->parent) {
      _CSSNodeMarkDirty(node->parent);
    }
  }
}

void CSSNodeInsertChild(const CSSNodeRef node, const CSSNodeRef child, const uint32_t index) {
  CSS_ASSERT(child->parent == NULL, "Child already has a parent, it must be removed first.");
  CSSNodeListInsert(node->children, child, index);
  child->parent = node;
  _CSSNodeMarkDirty(node);
}

void CSSNodeRemoveChild(const CSSNodeRef node, const CSSNodeRef child) {
  CSSNodeListDelete(node->children, child);
  child->parent = NULL;
  _CSSNodeMarkDirty(node);
}

CSSNodeRef CSSNodeGetChild(const CSSNodeRef node, const uint32_t index) {
  return CSSNodeListGet(node->children, index);
}

uint32_t CSSNodeChildCount(const CSSNodeRef node) {
  return CSSNodeListCount(node->children);
}

void CSSNodeMarkDirty(const CSSNodeRef node) {
  CSS_ASSERT(node->measure != NULL,
             "Nodes without custom measure functions "
             "should not manually mark themselves as "
             "dirty");
  _CSSNodeMarkDirty(node);
}

bool CSSNodeIsDirty(const CSSNodeRef node) {
  return node->isDirty;
}

void CSSNodeStyleSetFlex(const CSSNodeRef node, const float flex) {
  if (CSSValueIsUndefined(flex) || flex == 0) {
    CSSNodeStyleSetFlexGrow(node, 0);
    CSSNodeStyleSetFlexShrink(node, 0);
    CSSNodeStyleSetFlexBasis(node, CSSUndefined);
  } else if (flex > 0) {
    CSSNodeStyleSetFlexGrow(node, flex);
    CSSNodeStyleSetFlexShrink(node, 0);
    CSSNodeStyleSetFlexBasis(node, 0);
  } else {
    CSSNodeStyleSetFlexGrow(node, 0);
    CSSNodeStyleSetFlexShrink(node, -flex);
    CSSNodeStyleSetFlexBasis(node, CSSUndefined);
  }
}

float CSSNodeStyleGetFlex(const CSSNodeRef node) {
  if (node->style.flexGrow > 0) {
    return node->style.flexGrow;
  } else if (node->style.flexShrink > 0) {
    return -node->style.flexShrink;
  }

  return 0;
}

#define CSS_NODE_PROPERTY_IMPL(type, name, paramName, instanceName) \
  void CSSNodeSet##name(const CSSNodeRef node, type paramName) {    \
    node->instanceName = paramName;                                 \
  }                                                                 \
                                                                    \
  type CSSNodeGet##name(const CSSNodeRef node) {                    \
    return node->instanceName;                                      \
  }

#define CSS_NODE_STYLE_PROPERTY_IMPL(type, name, paramName, instanceName)   \
  void CSSNodeStyleSet##name(const CSSNodeRef node, const type paramName) { \
    if (node->style.instanceName != paramName) {                            \
      node->style.instanceName = paramName;                                 \
      _CSSNodeMarkDirty(node);                                              \
    }                                                                       \
  }                                                                         \
                                                                            \
  type CSSNodeStyleGet##name(const CSSNodeRef node) {                       \
    return node->style.instanceName;                                        \
  }

#define CSS_NODE_STYLE_EDGE_PROPERTY_IMPL(type, name, paramName, instanceName, defaultValue)    \
  void CSSNodeStyleSet##name(const CSSNodeRef node, const CSSEdge edge, const type paramName) { \
    if (node->style.instanceName[edge] != paramName) {                                          \
      node->style.instanceName[edge] = paramName;                                               \
      _CSSNodeMarkDirty(node);                                                                  \
    }                                                                                           \
  }                                                                                             \
                                                                                                \
  type CSSNodeStyleGet##name(const CSSNodeRef node, const CSSEdge edge) {                       \
    return computedEdgeValue(node->style.instanceName, edge, defaultValue);                     \
  }

#define CSS_NODE_LAYOUT_PROPERTY_IMPL(type, name, instanceName) \
  type CSSNodeLayoutGet##name(const CSSNodeRef node) {          \
    return node->layout.instanceName;                           \
  }

CSS_NODE_PROPERTY_IMPL(void *, Context, context, context);
CSS_NODE_PROPERTY_IMPL(CSSMeasureFunc, MeasureFunc, measureFunc, measure);
CSS_NODE_PROPERTY_IMPL(CSSPrintFunc, PrintFunc, printFunc, print);
CSS_NODE_PROPERTY_IMPL(bool, IsTextnode, isTextNode, isTextNode);
CSS_NODE_PROPERTY_IMPL(bool, HasNewLayout, hasNewLayout, hasNewLayout);

CSS_NODE_STYLE_PROPERTY_IMPL(CSSDirection, Direction, direction, direction);
CSS_NODE_STYLE_PROPERTY_IMPL(CSSFlexDirection, FlexDirection, flexDirection, flexDirection);
CSS_NODE_STYLE_PROPERTY_IMPL(CSSJustify, JustifyContent, justifyContent, justifyContent);
CSS_NODE_STYLE_PROPERTY_IMPL(CSSAlign, AlignContent, alignContent, alignContent);
CSS_NODE_STYLE_PROPERTY_IMPL(CSSAlign, AlignItems, alignItems, alignItems);
CSS_NODE_STYLE_PROPERTY_IMPL(CSSAlign, AlignSelf, alignSelf, alignSelf);
CSS_NODE_STYLE_PROPERTY_IMPL(CSSPositionType, PositionType, positionType, positionType);
CSS_NODE_STYLE_PROPERTY_IMPL(CSSWrapType, FlexWrap, flexWrap, flexWrap);
CSS_NODE_STYLE_PROPERTY_IMPL(CSSOverflow, Overflow, overflow, overflow);
CSS_NODE_STYLE_PROPERTY_IMPL(float, FlexGrow, flexGrow, flexGrow);
CSS_NODE_STYLE_PROPERTY_IMPL(float, FlexShrink, flexShrink, flexShrink);
CSS_NODE_STYLE_PROPERTY_IMPL(float, FlexBasis, flexBasis, flexBasis);

CSS_NODE_STYLE_EDGE_PROPERTY_IMPL(float, Position, position, position, CSSUndefined);
CSS_NODE_STYLE_EDGE_PROPERTY_IMPL(float, Margin, margin, margin, 0);
CSS_NODE_STYLE_EDGE_PROPERTY_IMPL(float, Padding, padding, padding, 0);
CSS_NODE_STYLE_EDGE_PROPERTY_IMPL(float, Border, border, border, 0);

CSS_NODE_STYLE_PROPERTY_IMPL(float, Width, width, dimensions[CSSDimensionWidth]);
CSS_NODE_STYLE_PROPERTY_IMPL(float, Height, height, dimensions[CSSDimensionHeight]);
CSS_NODE_STYLE_PROPERTY_IMPL(float, MinWidth, minWidth, minDimensions[CSSDimensionWidth]);
CSS_NODE_STYLE_PROPERTY_IMPL(float, MinHeight, minHeight, minDimensions[CSSDimensionHeight]);
CSS_NODE_STYLE_PROPERTY_IMPL(float, MaxWidth, maxWidth, maxDimensions[CSSDimensionWidth]);
CSS_NODE_STYLE_PROPERTY_IMPL(float, MaxHeight, maxHeight, maxDimensions[CSSDimensionHeight]);

CSS_NODE_LAYOUT_PROPERTY_IMPL(float, Left, position[CSSEdgeLeft]);
CSS_NODE_LAYOUT_PROPERTY_IMPL(float, Top, position[CSSEdgeTop]);
CSS_NODE_LAYOUT_PROPERTY_IMPL(float, Right, position[CSSEdgeRight]);
CSS_NODE_LAYOUT_PROPERTY_IMPL(float, Bottom, position[CSSEdgeBottom]);
CSS_NODE_LAYOUT_PROPERTY_IMPL(float, Width, dimensions[CSSDimensionWidth]);
CSS_NODE_LAYOUT_PROPERTY_IMPL(float, Height, dimensions[CSSDimensionHeight]);
CSS_NODE_LAYOUT_PROPERTY_IMPL(CSSDirection, Direction, direction);

uint32_t gCurrentGenerationCount = 0;

bool layoutNodeInternal(const CSSNodeRef node,
                        const float availableWidth,
                        const float availableHeight,
                        const CSSDirection parentDirection,
                        const CSSMeasureMode widthMeasureMode,
                        const CSSMeasureMode heightMeasureMode,
                        const bool performLayout,
                        const char *reason);

bool CSSValueIsUndefined(const float value) {
  return isnan(value);
}

static bool eq(const float a, const float b) {
  if (CSSValueIsUndefined(a)) {
    return CSSValueIsUndefined(b);
  }
  return fabs(a - b) < 0.0001;
}

static void indent(const uint32_t n) {
  for (uint32_t i = 0; i < n; i++) {
    printf("  ");
  }
}

static void printNumberIfNotZero(const char *str, const float number) {
  if (!eq(number, 0)) {
    printf("%s: %g, ", str, number);
  }
}

static void printNumberIfNotUndefined(const char *str, const float number) {
  if (!CSSValueIsUndefined(number)) {
    printf("%s: %g, ", str, number);
  }
}

static bool eqFour(const float four[4]) {
  return eq(four[0], four[1]) && eq(four[0], four[2]) && eq(four[0], four[3]);
}

static void
_CSSNodePrint(const CSSNodeRef node, const CSSPrintOptions options, const uint32_t level) {
  indent(level);
  printf("{");

  if (node->print) {
    node->print(node->context);
  }

  if (options & CSSPrintOptionsLayout) {
    printf("layout: {");
    printf("width: %g, ", node->layout.dimensions[CSSDimensionWidth]);
    printf("height: %g, ", node->layout.dimensions[CSSDimensionHeight]);
    printf("top: %g, ", node->layout.position[CSSEdgeTop]);
    printf("left: %g", node->layout.position[CSSEdgeLeft]);
    printf("}, ");
  }

  if (options & CSSPrintOptionsStyle) {
    if (node->style.flexDirection == CSSFlexDirectionColumn) {
      printf("flexDirection: 'column', ");
    } else if (node->style.flexDirection == CSSFlexDirectionColumnReverse) {
      printf("flexDirection: 'column-reverse', ");
    } else if (node->style.flexDirection == CSSFlexDirectionRow) {
      printf("flexDirection: 'row', ");
    } else if (node->style.flexDirection == CSSFlexDirectionRowReverse) {
      printf("flexDirection: 'row-reverse', ");
    }

    if (node->style.justifyContent == CSSJustifyCenter) {
      printf("justifyContent: 'center', ");
    } else if (node->style.justifyContent == CSSJustifyFlexEnd) {
      printf("justifyContent: 'flex-end', ");
    } else if (node->style.justifyContent == CSSJustifySpaceAround) {
      printf("justifyContent: 'space-around', ");
    } else if (node->style.justifyContent == CSSJustifySpaceBetween) {
      printf("justifyContent: 'space-between', ");
    }

    if (node->style.alignItems == CSSAlignCenter) {
      printf("alignItems: 'center', ");
    } else if (node->style.alignItems == CSSAlignFlexEnd) {
      printf("alignItems: 'flex-end', ");
    } else if (node->style.alignItems == CSSAlignStretch) {
      printf("alignItems: 'stretch', ");
    }

    if (node->style.alignContent == CSSAlignCenter) {
      printf("alignContent: 'center', ");
    } else if (node->style.alignContent == CSSAlignFlexEnd) {
      printf("alignContent: 'flex-end', ");
    } else if (node->style.alignContent == CSSAlignStretch) {
      printf("alignContent: 'stretch', ");
    }

    if (node->style.alignSelf == CSSAlignFlexStart) {
      printf("alignSelf: 'flex-start', ");
    } else if (node->style.alignSelf == CSSAlignCenter) {
      printf("alignSelf: 'center', ");
    } else if (node->style.alignSelf == CSSAlignFlexEnd) {
      printf("alignSelf: 'flex-end', ");
    } else if (node->style.alignSelf == CSSAlignStretch) {
      printf("alignSelf: 'stretch', ");
    }

    printNumberIfNotUndefined("flexGrow", node->style.flexGrow);
    printNumberIfNotUndefined("flexShrink", node->style.flexShrink);
    printNumberIfNotUndefined("flexBasis", node->style.flexBasis);

    if (node->style.overflow == CSSOverflowHidden) {
      printf("overflow: 'hidden', ");
    } else if (node->style.overflow == CSSOverflowVisible) {
      printf("overflow: 'visible', ");
    } else if (node->style.overflow == CSSOverflowScroll) {
      printf("overflow: 'scroll', ");
    }

    if (eqFour(node->style.margin)) {
      printNumberIfNotZero("margin", computedEdgeValue(node->style.margin, CSSEdgeLeft, 0));
    } else {
      printNumberIfNotZero("marginLeft", computedEdgeValue(node->style.margin, CSSEdgeLeft, 0));
      printNumberIfNotZero("marginRight", computedEdgeValue(node->style.margin, CSSEdgeRight, 0));
      printNumberIfNotZero("marginTop", computedEdgeValue(node->style.margin, CSSEdgeTop, 0));
      printNumberIfNotZero("marginBottom", computedEdgeValue(node->style.margin, CSSEdgeBottom, 0));
      printNumberIfNotZero("marginStart", computedEdgeValue(node->style.margin, CSSEdgeStart, 0));
      printNumberIfNotZero("marginEnd", computedEdgeValue(node->style.margin, CSSEdgeEnd, 0));
    }

    if (eqFour(node->style.padding)) {
      printNumberIfNotZero("padding", computedEdgeValue(node->style.padding, CSSEdgeLeft, 0));
    } else {
      printNumberIfNotZero("paddingLeft", computedEdgeValue(node->style.padding, CSSEdgeLeft, 0));
      printNumberIfNotZero("paddingRight", computedEdgeValue(node->style.padding, CSSEdgeRight, 0));
      printNumberIfNotZero("paddingTop", computedEdgeValue(node->style.padding, CSSEdgeTop, 0));
      printNumberIfNotZero("paddingBottom",
                           computedEdgeValue(node->style.padding, CSSEdgeBottom, 0));
      printNumberIfNotZero("paddingStart", computedEdgeValue(node->style.padding, CSSEdgeStart, 0));
      printNumberIfNotZero("paddingEnd", computedEdgeValue(node->style.padding, CSSEdgeEnd, 0));
    }

    if (eqFour(node->style.border)) {
      printNumberIfNotZero("borderWidth", computedEdgeValue(node->style.border, CSSEdgeLeft, 0));
    } else {
      printNumberIfNotZero("borderLeftWidth",
                           computedEdgeValue(node->style.border, CSSEdgeLeft, 0));
      printNumberIfNotZero("borderRightWidth",
                           computedEdgeValue(node->style.border, CSSEdgeRight, 0));
      printNumberIfNotZero("borderTopWidth", computedEdgeValue(node->style.border, CSSEdgeTop, 0));
      printNumberIfNotZero("borderBottomWidth",
                           computedEdgeValue(node->style.border, CSSEdgeBottom, 0));
      printNumberIfNotZero("borderStartWidth",
                           computedEdgeValue(node->style.border, CSSEdgeStart, 0));
      printNumberIfNotZero("borderEndWidth", computedEdgeValue(node->style.border, CSSEdgeEnd, 0));
    }

    printNumberIfNotUndefined("width", node->style.dimensions[CSSDimensionWidth]);
    printNumberIfNotUndefined("height", node->style.dimensions[CSSDimensionHeight]);
    printNumberIfNotUndefined("maxWidth", node->style.maxDimensions[CSSDimensionWidth]);
    printNumberIfNotUndefined("maxHeight", node->style.maxDimensions[CSSDimensionHeight]);
    printNumberIfNotUndefined("minWidth", node->style.minDimensions[CSSDimensionWidth]);
    printNumberIfNotUndefined("minHeight", node->style.minDimensions[CSSDimensionHeight]);

    if (node->style.positionType == CSSPositionTypeAbsolute) {
      printf("position: 'absolute', ");
    }

    printNumberIfNotUndefined("left",
                              computedEdgeValue(node->style.position, CSSEdgeLeft, CSSUndefined));
    printNumberIfNotUndefined("right",
                              computedEdgeValue(node->style.position, CSSEdgeRight, CSSUndefined));
    printNumberIfNotUndefined("top",
                              computedEdgeValue(node->style.position, CSSEdgeTop, CSSUndefined));
    printNumberIfNotUndefined("bottom",
                              computedEdgeValue(node->style.position, CSSEdgeBottom, CSSUndefined));
  }

  const uint32_t childCount = CSSNodeListCount(node->children);
  if (options & CSSPrintOptionsChildren && childCount > 0) {
    printf("children: [\n");
    for (uint32_t i = 0; i < childCount; i++) {
      _CSSNodePrint(CSSNodeGetChild(node, i), options, level + 1);
    }
    indent(level);
    printf("]},\n");
  } else {
    printf("},\n");
  }
}

void CSSNodePrint(const CSSNodeRef node, const CSSPrintOptions options) {
  _CSSNodePrint(node, options, 0);
}

static const CSSEdge leading[4] = {
        [CSSFlexDirectionColumn] = CSSEdgeTop,
        [CSSFlexDirectionColumnReverse] = CSSEdgeBottom,
        [CSSFlexDirectionRow] = CSSEdgeLeft,
        [CSSFlexDirectionRowReverse] = CSSEdgeRight,
};
static const CSSEdge trailing[4] = {
        [CSSFlexDirectionColumn] = CSSEdgeBottom,
        [CSSFlexDirectionColumnReverse] = CSSEdgeTop,
        [CSSFlexDirectionRow] = CSSEdgeRight,
        [CSSFlexDirectionRowReverse] = CSSEdgeLeft,
};
static const CSSEdge pos[4] = {
        [CSSFlexDirectionColumn] = CSSEdgeTop,
        [CSSFlexDirectionColumnReverse] = CSSEdgeBottom,
        [CSSFlexDirectionRow] = CSSEdgeLeft,
        [CSSFlexDirectionRowReverse] = CSSEdgeRight,
};
static const CSSDimension dim[4] = {
        [CSSFlexDirectionColumn] = CSSDimensionHeight,
        [CSSFlexDirectionColumnReverse] = CSSDimensionHeight,
        [CSSFlexDirectionRow] = CSSDimensionWidth,
        [CSSFlexDirectionRowReverse] = CSSDimensionWidth,
};

static bool isRowDirection(const CSSFlexDirection flexDirection) {
  return flexDirection == CSSFlexDirectionRow || flexDirection == CSSFlexDirectionRowReverse;
}

static bool isColumnDirection(const CSSFlexDirection flexDirection) {
  return flexDirection == CSSFlexDirectionColumn || flexDirection == CSSFlexDirectionColumnReverse;
}

static float getLeadingMargin(const CSSNodeRef node, const CSSFlexDirection axis) {
  if (isRowDirection(axis) && !CSSValueIsUndefined(node->style.margin[CSSEdgeStart])) {
    return node->style.margin[CSSEdgeStart];
  }

  return computedEdgeValue(node->style.margin, leading[axis], 0);
}

static float getTrailingMargin(const CSSNodeRef node, const CSSFlexDirection axis) {
  if (isRowDirection(axis) && !CSSValueIsUndefined(node->style.margin[CSSEdgeEnd])) {
    return node->style.margin[CSSEdgeEnd];
  }

  return computedEdgeValue(node->style.margin, trailing[axis], 0);
}

static float getLeadingPadding(const CSSNodeRef node, const CSSFlexDirection axis) {
  if (isRowDirection(axis) && !CSSValueIsUndefined(node->style.padding[CSSEdgeStart]) &&
      node->style.padding[CSSEdgeStart] >= 0) {
    return node->style.padding[CSSEdgeStart];
  }

  if (computedEdgeValue(node->style.padding, leading[axis], 0) >= 0) {
    return computedEdgeValue(node->style.padding, leading[axis], 0);
  }

  return 0;
}

static float getTrailingPadding(const CSSNodeRef node, const CSSFlexDirection axis) {
  if (isRowDirection(axis) && !CSSValueIsUndefined(node->style.padding[CSSEdgeEnd]) &&
      node->style.padding[CSSEdgeEnd] >= 0) {
    return node->style.padding[CSSEdgeEnd];
  }

  if (computedEdgeValue(node->style.padding, trailing[axis], 0) >= 0) {
    return computedEdgeValue(node->style.padding, trailing[axis], 0);
  }

  return 0;
}

static float getLeadingBorder(const CSSNodeRef node, const CSSFlexDirection axis) {
  if (isRowDirection(axis) && !CSSValueIsUndefined(node->style.border[CSSEdgeStart]) &&
      node->style.border[CSSEdgeStart] >= 0) {
    return node->style.border[CSSEdgeStart];
  }

  if (computedEdgeValue(node->style.border, leading[axis], 0) >= 0) {
    return computedEdgeValue(node->style.border, leading[axis], 0);
  }

  return 0;
}

static float getTrailingBorder(const CSSNodeRef node, const CSSFlexDirection axis) {
  if (isRowDirection(axis) && !CSSValueIsUndefined(node->style.border[CSSEdgeEnd]) &&
      node->style.border[CSSEdgeEnd] >= 0) {
    return node->style.border[CSSEdgeEnd];
  }

  if (computedEdgeValue(node->style.border, trailing[axis], 0) >= 0) {
    return computedEdgeValue(node->style.border, trailing[axis], 0);
  }

  return 0;
}

static float getLeadingPaddingAndBorder(const CSSNodeRef node, const CSSFlexDirection axis) {
  return getLeadingPadding(node, axis) + getLeadingBorder(node, axis);
}

static float getTrailingPaddingAndBorder(const CSSNodeRef node, const CSSFlexDirection axis) {
  return getTrailingPadding(node, axis) + getTrailingBorder(node, axis);
}

static float getMarginAxis(const CSSNodeRef node, const CSSFlexDirection axis) {
  return getLeadingMargin(node, axis) + getTrailingMargin(node, axis);
}

static float getPaddingAndBorderAxis(const CSSNodeRef node, const CSSFlexDirection axis) {
  return getLeadingPaddingAndBorder(node, axis) + getTrailingPaddingAndBorder(node, axis);
}

static CSSAlign getAlignItem(const CSSNodeRef node, const CSSNodeRef child) {
  if (child->style.alignSelf != CSSAlignAuto) {
    return child->style.alignSelf;
  }
  return node->style.alignItems;
}

static CSSDirection resolveDirection(const CSSNodeRef node, const CSSDirection parentDirection) {
  if (node->style.direction == CSSDirectionInherit) {
    return parentDirection > CSSDirectionInherit ? parentDirection : CSSDirectionLTR;
  } else {
    return node->style.direction;
  }
}

static CSSFlexDirection resolveAxis(const CSSFlexDirection flexDirection,
                                    const CSSDirection direction) {
  if (direction == CSSDirectionRTL) {
    if (flexDirection == CSSFlexDirectionRow) {
      return CSSFlexDirectionRowReverse;
    } else if (flexDirection == CSSFlexDirectionRowReverse) {
      return CSSFlexDirectionRow;
    }
  }

  return flexDirection;
}

static CSSFlexDirection getCrossFlexDirection(const CSSFlexDirection flexDirection,
                                              const CSSDirection direction) {
  if (isColumnDirection(flexDirection)) {
    return resolveAxis(CSSFlexDirectionRow, direction);
  } else {
    return CSSFlexDirectionColumn;
  }
}

static bool isFlex(const CSSNodeRef node) {
  return (node->style.positionType == CSSPositionTypeRelative &&
          (node->style.flexGrow != 0 || node->style.flexShrink != 0));
}

static float getDimWithMargin(const CSSNodeRef node, const CSSFlexDirection axis) {
  return node->layout.measuredDimensions[dim[axis]] + getLeadingMargin(node, axis) +
         getTrailingMargin(node, axis);
}

static bool isStyleDimDefined(const CSSNodeRef node, const CSSFlexDirection axis) {
  const float value = node->style.dimensions[dim[axis]];
  return !CSSValueIsUndefined(value) && value >= 0.0;
}

static bool isLayoutDimDefined(const CSSNodeRef node, const CSSFlexDirection axis) {
  const float value = node->layout.measuredDimensions[dim[axis]];
  return !CSSValueIsUndefined(value) && value >= 0.0;
}

static bool isLeadingPosDefined(const CSSNodeRef node, const CSSFlexDirection axis) {
  return (isRowDirection(axis) &&
          !CSSValueIsUndefined(
              computedEdgeValue(node->style.position, CSSEdgeStart, CSSUndefined))) ||
         !CSSValueIsUndefined(computedEdgeValue(node->style.position, leading[axis], CSSUndefined));
}

static bool isTrailingPosDefined(const CSSNodeRef node, const CSSFlexDirection axis) {
  return (isRowDirection(axis) &&
          !CSSValueIsUndefined(
              computedEdgeValue(node->style.position, CSSEdgeEnd, CSSUndefined))) ||
         !CSSValueIsUndefined(
             computedEdgeValue(node->style.position, trailing[axis], CSSUndefined));
}

static float getLeadingPosition(const CSSNodeRef node, const CSSFlexDirection axis) {
  if (isRowDirection(axis) &&
      !CSSValueIsUndefined(computedEdgeValue(node->style.position, CSSEdgeStart, CSSUndefined))) {
    return computedEdgeValue(node->style.position, CSSEdgeStart, CSSUndefined);
  }
  if (!CSSValueIsUndefined(computedEdgeValue(node->style.position, leading[axis], CSSUndefined))) {
    return computedEdgeValue(node->style.position, leading[axis], CSSUndefined);
  }
  return 0;
}

static float getTrailingPosition(const CSSNodeRef node, const CSSFlexDirection axis) {
  if (isRowDirection(axis) &&
      !CSSValueIsUndefined(computedEdgeValue(node->style.position, CSSEdgeEnd, CSSUndefined))) {
    return computedEdgeValue(node->style.position, CSSEdgeEnd, CSSUndefined);
  }
  if (!CSSValueIsUndefined(computedEdgeValue(node->style.position, trailing[axis], CSSUndefined))) {
    return computedEdgeValue(node->style.position, trailing[axis], CSSUndefined);
  }
  return 0;
}

static float
boundAxisWithinMinAndMax(const CSSNodeRef node, const CSSFlexDirection axis, const float value) {
  float min = CSSUndefined;
  float max = CSSUndefined;

  if (isColumnDirection(axis)) {
    min = node->style.minDimensions[CSSDimensionHeight];
    max = node->style.maxDimensions[CSSDimensionHeight];
  } else if (isRowDirection(axis)) {
    min = node->style.minDimensions[CSSDimensionWidth];
    max = node->style.maxDimensions[CSSDimensionWidth];
  }

  float boundValue = value;

  if (!CSSValueIsUndefined(max) && max >= 0.0 && boundValue > max) {
    boundValue = max;
  }

  if (!CSSValueIsUndefined(min) && min >= 0.0 && boundValue < min) {
    boundValue = min;
  }

  return boundValue;
}

// Like boundAxisWithinMinAndMax but also ensures that the value doesn't go
// below the
// padding and border amount.
static float boundAxis(const CSSNodeRef node, const CSSFlexDirection axis, const float value) {
  return fmaxf(boundAxisWithinMinAndMax(node, axis, value), getPaddingAndBorderAxis(node, axis));
}

static void
setTrailingPosition(const CSSNodeRef node, const CSSNodeRef child, const CSSFlexDirection axis) {
  const float size = child->layout.measuredDimensions[dim[axis]];
  child->layout.position[trailing[axis]] =
      node->layout.measuredDimensions[dim[axis]] - size - child->layout.position[pos[axis]];
}

// If both left and right are defined, then use left. Otherwise return
// +left or -right depending on which is defined.
static float getRelativePosition(const CSSNodeRef node, const CSSFlexDirection axis) {
  if (isLeadingPosDefined(node, axis)) {
    return getLeadingPosition(node, axis);
  }
  return -getTrailingPosition(node, axis);
}

static void setPosition(const CSSNodeRef node, const CSSDirection direction) {
  const CSSFlexDirection mainAxis = resolveAxis(node->style.flexDirection, direction);
  const CSSFlexDirection crossAxis = getCrossFlexDirection(mainAxis, direction);

  node->layout.position[leading[mainAxis]] =
      getLeadingMargin(node, mainAxis) + getRelativePosition(node, mainAxis);
  node->layout.position[trailing[mainAxis]] =
      getTrailingMargin(node, mainAxis) + getRelativePosition(node, mainAxis);
  node->layout.position[leading[crossAxis]] =
      getLeadingMargin(node, crossAxis) + getRelativePosition(node, crossAxis);
  node->layout.position[trailing[crossAxis]] =
      getTrailingMargin(node, crossAxis) + getRelativePosition(node, crossAxis);
}

//
// This is the main routine that implements a subset of the flexbox layout
// algorithm
// described in the W3C CSS documentation: https://www.w3.org/TR/css3-flexbox/.
//
// Limitations of this algorithm, compared to the full standard:
//  * Display property is always assumed to be 'flex' except for Text nodes,
//  which
//    are assumed to be 'inline-flex'.
//  * The 'zIndex' property (or any form of z ordering) is not supported. Nodes
//  are
//    stacked in document order.
//  * The 'order' property is not supported. The order of flex items is always
//  defined
//    by document order.
//  * The 'visibility' property is always assumed to be 'visible'. Values of
//  'collapse'
//    and 'hidden' are not supported.
//  * The 'wrap' property supports only 'nowrap' (which is the default) or
//  'wrap'. The
//    rarely-used 'wrap-reverse' is not supported.
//  * Rather than allowing arbitrary combinations of flexGrow, flexShrink and
//    flexBasis, this algorithm supports only the three most common
//    combinations:
//      flex: 0 is equiavlent to flex: 0 0 auto
//      flex: n (where n is a positive value) is equivalent to flex: n 1 auto
//          If POSITIVE_FLEX_IS_AUTO is 0, then it is equivalent to flex: n 0 0
//          This is faster because the content doesn't need to be measured, but
//          it's
//          less flexible because the basis is always 0 and can't be overriden
//          with
//          the width/height attributes.
//      flex: -1 (or any negative value) is equivalent to flex: 0 1 auto
//  * Margins cannot be specified as 'auto'. They must be specified in terms of
//  pixel
//    values, and the default value is 0.
//  * The 'baseline' value is not supported for alignItems and alignSelf
//  properties.
//  * Values of width, maxWidth, minWidth, height, maxHeight and minHeight must
//  be
//    specified as pixel values, not as percentages.
//  * There is no support for calculation of dimensions based on intrinsic
//  aspect ratios
//     (e.g. images).
//  * There is no support for forced breaks.
//  * It does not support vertical inline directions (top-to-bottom or
//  bottom-to-top text).
//
// Deviations from standard:
//  * Section 4.5 of the spec indicates that all flex items have a default
//  minimum
//    main size. For text blocks, for example, this is the width of the widest
//    word.
//    Calculating the minimum width is expensive, so we forego it and assume a
//    default
//    minimum main size of 0.
//  * Min/Max sizes in the main axis are not honored when resolving flexible
//  lengths.
//  * The spec indicates that the default value for 'flexDirection' is 'row',
//  but
//    the algorithm below assumes a default of 'column'.
//
// Input parameters:
//    - node: current node to be sized and layed out
//    - availableWidth & availableHeight: available size to be used for sizing
//    the node
//      or CSSUndefined if the size is not available; interpretation depends on
//      layout
//      flags
//    - parentDirection: the inline (text) direction within the parent
//    (left-to-right or
//      right-to-left)
//    - widthMeasureMode: indicates the sizing rules for the width (see below
//    for explanation)
//    - heightMeasureMode: indicates the sizing rules for the height (see below
//    for explanation)
//    - performLayout: specifies whether the caller is interested in just the
//    dimensions
//      of the node or it requires the entire node and its subtree to be layed
//      out
//      (with final positions)
//
// Details:
//    This routine is called recursively to lay out subtrees of flexbox
//    elements. It uses the
//    information in node.style, which is treated as a read-only input. It is
//    responsible for
//    setting the layout.direction and layout.measuredDimensions fields for the
//    input node as well
//    as the layout.position and layout.lineIndex fields for its child nodes.
//    The
//    layout.measuredDimensions field includes any border or padding for the
//    node but does
//    not include margins.
//
//    The spec describes four different layout modes: "fill available", "max
//    content", "min
//    content",
//    and "fit content". Of these, we don't use "min content" because we don't
//    support default
//    minimum main sizes (see above for details). Each of our measure modes maps
//    to a layout mode
//    from the spec (https://www.w3.org/TR/css3-sizing/#terms):
//      - CSSMeasureModeUndefined: max content
//      - CSSMeasureModeExactly: fill available
//      - CSSMeasureModeAtMost: fit content
//
//    When calling layoutNodeImpl and layoutNodeInternal, if the caller passes
//    an available size of
//    undefined then it must also pass a measure mode of CSSMeasureModeUndefined
//    in that dimension.
//
static void layoutNodeImpl(const CSSNodeRef node,
                           const float availableWidth,
                           const float availableHeight,
                           const CSSDirection parentDirection,
                           const CSSMeasureMode widthMeasureMode,
                           const CSSMeasureMode heightMeasureMode,
                           const bool performLayout) {
  CSS_ASSERT(CSSValueIsUndefined(availableWidth) ? widthMeasureMode == CSSMeasureModeUndefined
                                                 : true,
             "availableWidth is indefinite so widthMeasureMode must be "
             "CSSMeasureModeUndefined");
  CSS_ASSERT(CSSValueIsUndefined(availableHeight) ? heightMeasureMode == CSSMeasureModeUndefined
                                                  : true,
             "availableHeight is indefinite so heightMeasureMode must be "
             "CSSMeasureModeUndefined");

  const float paddingAndBorderAxisRow = getPaddingAndBorderAxis(node, CSSFlexDirectionRow);
  const float paddingAndBorderAxisColumn = getPaddingAndBorderAxis(node, CSSFlexDirectionColumn);
  const float marginAxisRow = getMarginAxis(node, CSSFlexDirectionRow);
  const float marginAxisColumn = getMarginAxis(node, CSSFlexDirectionColumn);

  // Set the resolved resolution in the node's layout.
  const CSSDirection direction = resolveDirection(node, parentDirection);
  node->layout.direction = direction;

  // For content (text) nodes, determine the dimensions based on the text
  // contents.
  if (node->measure) {
    const float innerWidth = availableWidth - marginAxisRow - paddingAndBorderAxisRow;
    const float innerHeight = availableHeight - marginAxisColumn - paddingAndBorderAxisColumn;

    if (widthMeasureMode == CSSMeasureModeExactly && heightMeasureMode == CSSMeasureModeExactly) {
      // Don't bother sizing the text if both dimensions are already defined.
      node->layout.measuredDimensions[CSSDimensionWidth] =
          boundAxis(node, CSSFlexDirectionRow, availableWidth - marginAxisRow);
      node->layout.measuredDimensions[CSSDimensionHeight] =
          boundAxis(node, CSSFlexDirectionColumn, availableHeight - marginAxisColumn);
    } else if (innerWidth <= 0 || innerHeight <= 0) {
      // Don't bother sizing the text if there's no horizontal or vertical
      // space.
      node->layout.measuredDimensions[CSSDimensionWidth] = boundAxis(node, CSSFlexDirectionRow, 0);
      node->layout.measuredDimensions[CSSDimensionHeight] =
          boundAxis(node, CSSFlexDirectionColumn, 0);
    } else {
      // Measure the text under the current constraints.
      const CSSSize measuredSize =
          node->measure(node->context, innerWidth, widthMeasureMode, innerHeight, heightMeasureMode);

      node->layout.measuredDimensions[CSSDimensionWidth] =
          boundAxis(node,
                    CSSFlexDirectionRow,
                    (widthMeasureMode == CSSMeasureModeUndefined ||
                     widthMeasureMode == CSSMeasureModeAtMost)
                        ? measuredSize.width + paddingAndBorderAxisRow
                        : availableWidth - marginAxisRow);
      node->layout.measuredDimensions[CSSDimensionHeight] =
          boundAxis(node,
                    CSSFlexDirectionColumn,
                    (heightMeasureMode == CSSMeasureModeUndefined ||
                     heightMeasureMode == CSSMeasureModeAtMost)
                        ? measuredSize.height + paddingAndBorderAxisColumn
                        : availableHeight - marginAxisColumn);
    }

    return;
  }

  // For nodes with no children, use the available values if they were provided,
  // or
  // the minimum size as indicated by the padding and border sizes.
  const uint32_t childCount = CSSNodeListCount(node->children);
  if (childCount == 0) {
    node->layout.measuredDimensions[CSSDimensionWidth] =
        boundAxis(node,
                  CSSFlexDirectionRow,
                  (widthMeasureMode == CSSMeasureModeUndefined ||
                   widthMeasureMode == CSSMeasureModeAtMost)
                      ? paddingAndBorderAxisRow
                      : availableWidth - marginAxisRow);
    node->layout.measuredDimensions[CSSDimensionHeight] =
        boundAxis(node,
                  CSSFlexDirectionColumn,
                  (heightMeasureMode == CSSMeasureModeUndefined ||
                   heightMeasureMode == CSSMeasureModeAtMost)
                      ? paddingAndBorderAxisColumn
                      : availableHeight - marginAxisColumn);
    return;
  }

  // If we're not being asked to perform a full layout, we can handle a number
  // of common
  // cases here without incurring the cost of the remaining function.
  if (!performLayout) {
    // If we're being asked to size the content with an at most constraint but
    // there is no available
    // width,
    // the measurement will always be zero.
    if (widthMeasureMode == CSSMeasureModeAtMost && availableWidth <= 0 &&
        heightMeasureMode == CSSMeasureModeAtMost && availableHeight <= 0) {
      node->layout.measuredDimensions[CSSDimensionWidth] = boundAxis(node, CSSFlexDirectionRow, 0);
      node->layout.measuredDimensions[CSSDimensionHeight] =
          boundAxis(node, CSSFlexDirectionColumn, 0);
      return;
    }

    if (widthMeasureMode == CSSMeasureModeAtMost && availableWidth <= 0) {
      node->layout.measuredDimensions[CSSDimensionWidth] = boundAxis(node, CSSFlexDirectionRow, 0);
      node->layout.measuredDimensions[CSSDimensionHeight] =
          boundAxis(node,
                    CSSFlexDirectionColumn,
                    CSSValueIsUndefined(availableHeight) ? 0
                                                         : (availableHeight - marginAxisColumn));
      return;
    }

    if (heightMeasureMode == CSSMeasureModeAtMost && availableHeight <= 0) {
      node->layout.measuredDimensions[CSSDimensionWidth] =
          boundAxis(node,
                    CSSFlexDirectionRow,
                    CSSValueIsUndefined(availableWidth) ? 0 : (availableWidth - marginAxisRow));
      node->layout.measuredDimensions[CSSDimensionHeight] =
          boundAxis(node, CSSFlexDirectionColumn, 0);
      return;
    }

    // If we're being asked to use an exact width/height, there's no need to
    // measure the children.
    if (widthMeasureMode == CSSMeasureModeExactly && heightMeasureMode == CSSMeasureModeExactly) {
      node->layout.measuredDimensions[CSSDimensionWidth] =
          boundAxis(node, CSSFlexDirectionRow, availableWidth - marginAxisRow);
      node->layout.measuredDimensions[CSSDimensionHeight] =
          boundAxis(node, CSSFlexDirectionColumn, availableHeight - marginAxisColumn);
      return;
    }
  }

  // STEP 1: CALCULATE VALUES FOR REMAINDER OF ALGORITHM
  const CSSFlexDirection mainAxis = resolveAxis(node->style.flexDirection, direction);
  const CSSFlexDirection crossAxis = getCrossFlexDirection(mainAxis, direction);
  const bool isMainAxisRow = isRowDirection(mainAxis);
  const CSSJustify justifyContent = node->style.justifyContent;
  const bool isNodeFlexWrap = node->style.flexWrap == CSSWrapTypeWrap;

  CSSNodeRef firstAbsoluteChild = NULL;
  CSSNodeRef currentAbsoluteChild = NULL;

  const float leadingPaddingAndBorderMain = getLeadingPaddingAndBorder(node, mainAxis);
  const float trailingPaddingAndBorderMain = getTrailingPaddingAndBorder(node, mainAxis);
  const float leadingPaddingAndBorderCross = getLeadingPaddingAndBorder(node, crossAxis);
  const float paddingAndBorderAxisMain = getPaddingAndBorderAxis(node, mainAxis);
  const float paddingAndBorderAxisCross = getPaddingAndBorderAxis(node, crossAxis);

  const CSSMeasureMode measureModeMainDim = isMainAxisRow ? widthMeasureMode : heightMeasureMode;
  const CSSMeasureMode measureModeCrossDim = isMainAxisRow ? heightMeasureMode : widthMeasureMode;

  // STEP 2: DETERMINE AVAILABLE SIZE IN MAIN AND CROSS DIRECTIONS
  const float availableInnerWidth = availableWidth - marginAxisRow - paddingAndBorderAxisRow;
  const float availableInnerHeight =
      availableHeight - marginAxisColumn - paddingAndBorderAxisColumn;
  const float availableInnerMainDim = isMainAxisRow ? availableInnerWidth : availableInnerHeight;
  const float availableInnerCrossDim = isMainAxisRow ? availableInnerHeight : availableInnerWidth;

  // STEP 3: DETERMINE FLEX BASIS FOR EACH ITEM
  float childWidth;
  float childHeight;
  CSSMeasureMode childWidthMeasureMode;
  CSSMeasureMode childHeightMeasureMode;
  for (uint32_t i = 0; i < childCount; i++) {
    const CSSNodeRef child = CSSNodeListGet(node->children, i);

    if (performLayout) {
      // Set the initial position (relative to the parent).
      const CSSDirection childDirection = resolveDirection(child, direction);
      setPosition(child, childDirection);
    }

    // Absolute-positioned children don't participate in flex layout. Add them
    // to a list that we can process later.
    if (child->style.positionType == CSSPositionTypeAbsolute) {
      // Store a private linked list of absolutely positioned children
      // so that we can efficiently traverse them later.
      if (firstAbsoluteChild == NULL) {
        firstAbsoluteChild = child;
      }
      if (currentAbsoluteChild != NULL) {
        currentAbsoluteChild->nextChild = child;
      }
      currentAbsoluteChild = child;
      child->nextChild = NULL;
    } else {
      if (isMainAxisRow && isStyleDimDefined(child, CSSFlexDirectionRow)) {
        // The width is definite, so use that as the flex basis.
        child->layout.computedFlexBasis =
            fmaxf(child->style.dimensions[CSSDimensionWidth],
                  getPaddingAndBorderAxis(child, CSSFlexDirectionRow));
      } else if (!isMainAxisRow && isStyleDimDefined(child, CSSFlexDirectionColumn)) {
        // The height is definite, so use that as the flex basis.
        child->layout.computedFlexBasis =
            fmaxf(child->style.dimensions[CSSDimensionHeight],
                  getPaddingAndBorderAxis(child, CSSFlexDirectionColumn));
      } else if (!CSSValueIsUndefined(child->style.flexBasis) &&
                 !CSSValueIsUndefined(availableInnerMainDim)) {
        if (CSSValueIsUndefined(child->layout.computedFlexBasis)) {
          child->layout.computedFlexBasis =
              fmaxf(child->style.flexBasis, getPaddingAndBorderAxis(child, mainAxis));
        }
      } else {
        // Compute the flex basis and hypothetical main size (i.e. the clamped
        // flex basis).
        childWidth = CSSUndefined;
        childHeight = CSSUndefined;
        childWidthMeasureMode = CSSMeasureModeUndefined;
        childHeightMeasureMode = CSSMeasureModeUndefined;

        if (isStyleDimDefined(child, CSSFlexDirectionRow)) {
          childWidth = child->style.dimensions[CSSDimensionWidth] +
                       getMarginAxis(child, CSSFlexDirectionRow);
          childWidthMeasureMode = CSSMeasureModeExactly;
        }
        if (isStyleDimDefined(child, CSSFlexDirectionColumn)) {
          childHeight = child->style.dimensions[CSSDimensionHeight] +
                        getMarginAxis(child, CSSFlexDirectionColumn);
          childHeightMeasureMode = CSSMeasureModeExactly;
        }

        // The W3C spec doesn't say anything about the 'overflow' property,
        // but all major browsers appear to implement the following logic.
        if ((!isMainAxisRow && node->style.overflow == CSSOverflowScroll) ||
            node->style.overflow != CSSOverflowScroll) {
          if (CSSValueIsUndefined(childWidth) && !CSSValueIsUndefined(availableInnerWidth)) {
            childWidth = availableInnerWidth;
            childWidthMeasureMode = CSSMeasureModeAtMost;
          }
        }

        if ((isMainAxisRow && node->style.overflow == CSSOverflowScroll) ||
            node->style.overflow != CSSOverflowScroll) {
          if (CSSValueIsUndefined(childHeight) && !CSSValueIsUndefined(availableInnerHeight)) {
            childHeight = availableInnerHeight;
            childHeightMeasureMode = CSSMeasureModeAtMost;
          }
        }

        // If child has no defined size in the cross axis and is set to stretch,
        // set the cross
        // axis to be measured exactly with the available inner width
        if (!isMainAxisRow && !CSSValueIsUndefined(availableInnerWidth) &&
            !isStyleDimDefined(child, CSSFlexDirectionRow) &&
            widthMeasureMode == CSSMeasureModeExactly &&
            getAlignItem(node, child) == CSSAlignStretch) {
          childWidth = availableInnerWidth;
          childWidthMeasureMode = CSSMeasureModeExactly;
        }
        if (isMainAxisRow && !CSSValueIsUndefined(availableInnerHeight) &&
            !isStyleDimDefined(child, CSSFlexDirectionColumn) &&
            heightMeasureMode == CSSMeasureModeExactly &&
            getAlignItem(node, child) == CSSAlignStretch) {
          childHeight = availableInnerHeight;
          childHeightMeasureMode = CSSMeasureModeExactly;
        }

        // Measure the child
        layoutNodeInternal(child,
                           childWidth,
                           childHeight,
                           direction,
                           childWidthMeasureMode,
                           childHeightMeasureMode,
                           false,
                           "measure");

        child->layout.computedFlexBasis =
            fmaxf(isMainAxisRow ? child->layout.measuredDimensions[CSSDimensionWidth]
                                : child->layout.measuredDimensions[CSSDimensionHeight],
                  getPaddingAndBorderAxis(child, mainAxis));
      }
    }
  }

  // STEP 4: COLLECT FLEX ITEMS INTO FLEX LINES

  // Indexes of children that represent the first and last items in the line.
  uint32_t startOfLineIndex = 0;
  uint32_t endOfLineIndex = 0;

  // Number of lines.
  uint32_t lineCount = 0;

  // Accumulated cross dimensions of all lines so far.
  float totalLineCrossDim = 0;

  // Max main dimension of all the lines.
  float maxLineMainDim = 0;

  for (; endOfLineIndex < childCount; lineCount++, startOfLineIndex = endOfLineIndex) {
    // Number of items on the currently line. May be different than the
    // difference
    // between start and end indicates because we skip over absolute-positioned
    // items.
    uint32_t itemsOnLine = 0;

    // sizeConsumedOnCurrentLine is accumulation of the dimensions and margin
    // of all the children on the current line. This will be used in order to
    // either set the dimensions of the node if none already exist or to compute
    // the remaining space left for the flexible children.
    float sizeConsumedOnCurrentLine = 0;

    float totalFlexGrowFactors = 0;
    float totalFlexShrinkScaledFactors = 0;

    // Maintain a linked list of the child nodes that can shrink and/or grow.
    CSSNodeRef firstRelativeChild = NULL;
    CSSNodeRef currentRelativeChild = NULL;

    // Add items to the current line until it's full or we run out of items.
    for (uint32_t i = startOfLineIndex; i < childCount; i++, endOfLineIndex++) {
      const CSSNodeRef child = CSSNodeListGet(node->children, i);
      child->lineIndex = lineCount;

      if (child->style.positionType != CSSPositionTypeAbsolute) {
        const float outerFlexBasis =
            child->layout.computedFlexBasis + getMarginAxis(child, mainAxis);

        // If this is a multi-line flow and this item pushes us over the
        // available size, we've
        // hit the end of the current line. Break out of the loop and lay out
        // the current line.
        if (sizeConsumedOnCurrentLine + outerFlexBasis > availableInnerMainDim && isNodeFlexWrap &&
            itemsOnLine > 0) {
          break;
        }

        sizeConsumedOnCurrentLine += outerFlexBasis;
        itemsOnLine++;

        if (isFlex(child)) {
          totalFlexGrowFactors += child->style.flexGrow;

          // Unlike the grow factor, the shrink factor is scaled relative to the
          // child
          // dimension.
          totalFlexShrinkScaledFactors +=
              -child->style.flexShrink * child->layout.computedFlexBasis;
        }

        // Store a private linked list of children that need to be layed out.
        if (firstRelativeChild == NULL) {
          firstRelativeChild = child;
        }
        if (currentRelativeChild != NULL) {
          currentRelativeChild->nextChild = child;
        }
        currentRelativeChild = child;
        child->nextChild = NULL;
      }
    }

    // If we don't need to measure the cross axis, we can skip the entire flex
    // step.
    const bool canSkipFlex = !performLayout && measureModeCrossDim == CSSMeasureModeExactly;

    // In order to position the elements in the main axis, we have two
    // controls. The space between the beginning and the first element
    // and the space between each two elements.
    float leadingMainDim = 0;
    float betweenMainDim = 0;

    // STEP 5: RESOLVING FLEXIBLE LENGTHS ON MAIN AXIS
    // Calculate the remaining available space that needs to be allocated.
    // If the main dimension size isn't known, it is computed based on
    // the line length, so there's no more space left to distribute.
    float remainingFreeSpace = 0;
    if (!CSSValueIsUndefined(availableInnerMainDim)) {
      remainingFreeSpace = availableInnerMainDim - sizeConsumedOnCurrentLine;
    } else if (sizeConsumedOnCurrentLine < 0) {
      // availableInnerMainDim is indefinite which means the node is being sized
      // based on its
      // content.
      // sizeConsumedOnCurrentLine is negative which means the node will
      // allocate 0 pixels for
      // its content. Consequently, remainingFreeSpace is 0 -
      // sizeConsumedOnCurrentLine.
      remainingFreeSpace = -sizeConsumedOnCurrentLine;
    }

    const float originalRemainingFreeSpace = remainingFreeSpace;
    float deltaFreeSpace = 0;

    if (!canSkipFlex) {
      float childFlexBasis;
      float flexShrinkScaledFactor;
      float flexGrowFactor;
      float baseMainSize;
      float boundMainSize;

      // Do two passes over the flex items to figure out how to distribute the
      // remaining space.
      // The first pass finds the items whose min/max constraints trigger,
      // freezes them at those
      // sizes, and excludes those sizes from the remaining space. The second
      // pass sets the size
      // of each flexible item. It distributes the remaining space amongst the
      // items whose min/max
      // constraints didn't trigger in pass 1. For the other items, it sets
      // their sizes by forcing
      // their min/max constraints to trigger again.
      //
      // This two pass approach for resolving min/max constraints deviates from
      // the spec. The
      // spec (https://www.w3.org/TR/css-flexbox-1/#resolve-flexible-lengths)
      // describes a process
      // that needs to be repeated a variable number of times. The algorithm
      // implemented here
      // won't handle all cases but it was simpler to implement and it mitigates
      // performance
      // concerns because we know exactly how many passes it'll do.

      // First pass: detect the flex items whose min/max constraints trigger
      float deltaFlexShrinkScaledFactors = 0;
      float deltaFlexGrowFactors = 0;
      currentRelativeChild = firstRelativeChild;
      while (currentRelativeChild != NULL) {
        childFlexBasis = currentRelativeChild->layout.computedFlexBasis;

        if (remainingFreeSpace < 0) {
          flexShrinkScaledFactor = -currentRelativeChild->style.flexShrink * childFlexBasis;

          // Is this child able to shrink?
          if (flexShrinkScaledFactor != 0) {
            baseMainSize =
                childFlexBasis +
                remainingFreeSpace / totalFlexShrinkScaledFactors * flexShrinkScaledFactor;
            boundMainSize = boundAxis(currentRelativeChild, mainAxis, baseMainSize);
            if (baseMainSize != boundMainSize) {
              // By excluding this item's size and flex factor from remaining,
              // this item's
              // min/max constraints should also trigger in the second pass
              // resulting in the
              // item's size calculation being identical in the first and second
              // passes.
              deltaFreeSpace -= boundMainSize - childFlexBasis;
              deltaFlexShrinkScaledFactors -= flexShrinkScaledFactor;
            }
          }
        } else if (remainingFreeSpace > 0) {
          flexGrowFactor = currentRelativeChild->style.flexGrow;

          // Is this child able to grow?
          if (flexGrowFactor != 0) {
            baseMainSize =
                childFlexBasis + remainingFreeSpace / totalFlexGrowFactors * flexGrowFactor;
            boundMainSize = boundAxis(currentRelativeChild, mainAxis, baseMainSize);
            if (baseMainSize != boundMainSize) {
              // By excluding this item's size and flex factor from remaining,
              // this item's
              // min/max constraints should also trigger in the second pass
              // resulting in the
              // item's size calculation being identical in the first and second
              // passes.
              deltaFreeSpace -= boundMainSize - childFlexBasis;
              deltaFlexGrowFactors -= flexGrowFactor;
            }
          }
        }

        currentRelativeChild = currentRelativeChild->nextChild;
      }

      totalFlexShrinkScaledFactors += deltaFlexShrinkScaledFactors;
      totalFlexGrowFactors += deltaFlexGrowFactors;
      remainingFreeSpace += deltaFreeSpace;

      // Second pass: resolve the sizes of the flexible items
      deltaFreeSpace = 0;
      currentRelativeChild = firstRelativeChild;
      while (currentRelativeChild != NULL) {
        childFlexBasis = currentRelativeChild->layout.computedFlexBasis;
        float updatedMainSize = childFlexBasis;

        if (remainingFreeSpace < 0) {
          flexShrinkScaledFactor = -currentRelativeChild->style.flexShrink * childFlexBasis;

          // Is this child able to shrink?
          if (flexShrinkScaledFactor != 0) {
            updatedMainSize = boundAxis(currentRelativeChild,
                                        mainAxis,
                                        childFlexBasis +
                                            remainingFreeSpace / totalFlexShrinkScaledFactors *
                                                flexShrinkScaledFactor);
          }
        } else if (remainingFreeSpace > 0) {
          flexGrowFactor = currentRelativeChild->style.flexGrow;

          // Is this child able to grow?
          if (flexGrowFactor != 0) {
            updatedMainSize =
                boundAxis(currentRelativeChild,
                          mainAxis,
                          childFlexBasis +
                              remainingFreeSpace / totalFlexGrowFactors * flexGrowFactor);
          }
        }

        deltaFreeSpace -= updatedMainSize - childFlexBasis;

        if (isMainAxisRow) {
          childWidth = updatedMainSize + getMarginAxis(currentRelativeChild, CSSFlexDirectionRow);
          childWidthMeasureMode = CSSMeasureModeExactly;

          if (!CSSValueIsUndefined(availableInnerCrossDim) &&
              !isStyleDimDefined(currentRelativeChild, CSSFlexDirectionColumn) &&
              heightMeasureMode == CSSMeasureModeExactly &&
              getAlignItem(node, currentRelativeChild) == CSSAlignStretch) {
            childHeight = availableInnerCrossDim;
            childHeightMeasureMode = CSSMeasureModeExactly;
          } else if (!isStyleDimDefined(currentRelativeChild, CSSFlexDirectionColumn)) {
            childHeight = availableInnerCrossDim;
            childHeightMeasureMode =
                CSSValueIsUndefined(childHeight) ? CSSMeasureModeUndefined : CSSMeasureModeAtMost;
          } else {
            childHeight = currentRelativeChild->style.dimensions[CSSDimensionHeight] +
                          getMarginAxis(currentRelativeChild, CSSFlexDirectionColumn);
            childHeightMeasureMode = CSSMeasureModeExactly;
          }
        } else {
          childHeight =
              updatedMainSize + getMarginAxis(currentRelativeChild, CSSFlexDirectionColumn);
          childHeightMeasureMode = CSSMeasureModeExactly;

          if (!CSSValueIsUndefined(availableInnerCrossDim) &&
              !isStyleDimDefined(currentRelativeChild, CSSFlexDirectionRow) &&
              widthMeasureMode == CSSMeasureModeExactly &&
              getAlignItem(node, currentRelativeChild) == CSSAlignStretch) {
            childWidth = availableInnerCrossDim;
            childWidthMeasureMode = CSSMeasureModeExactly;
          } else if (!isStyleDimDefined(currentRelativeChild, CSSFlexDirectionRow)) {
            childWidth = availableInnerCrossDim;
            childWidthMeasureMode =
                CSSValueIsUndefined(childWidth) ? CSSMeasureModeUndefined : CSSMeasureModeAtMost;
          } else {
            childWidth = currentRelativeChild->style.dimensions[CSSDimensionWidth] +
                         getMarginAxis(currentRelativeChild, CSSFlexDirectionRow);
            childWidthMeasureMode = CSSMeasureModeExactly;
          }
        }

        const bool requiresStretchLayout =
            !isStyleDimDefined(currentRelativeChild, crossAxis) &&
            getAlignItem(node, currentRelativeChild) == CSSAlignStretch;

        // Recursively call the layout algorithm for this child with the updated
        // main size.
        layoutNodeInternal(currentRelativeChild,
                           childWidth,
                           childHeight,
                           direction,
                           childWidthMeasureMode,
                           childHeightMeasureMode,
                           performLayout && !requiresStretchLayout,
                           "flex");

        currentRelativeChild = currentRelativeChild->nextChild;
      }
    }

    remainingFreeSpace = originalRemainingFreeSpace + deltaFreeSpace;

    // STEP 6: MAIN-AXIS JUSTIFICATION & CROSS-AXIS SIZE DETERMINATION

    // At this point, all the children have their dimensions set in the main
    // axis.
    // Their dimensions are also set in the cross axis with the exception of
    // items
    // that are aligned "stretch". We need to compute these stretch values and
    // set the final positions.

    // If we are using "at most" rules in the main axis, we won't distribute
    // any remaining space at this point.
    if (measureModeMainDim == CSSMeasureModeAtMost) {
      remainingFreeSpace = 0;
    }

    switch (justifyContent) {
      case CSSJustifyCenter:
        leadingMainDim = remainingFreeSpace / 2;
        break;
      case CSSJustifyFlexEnd:
        leadingMainDim = remainingFreeSpace;
        break;
      case CSSJustifySpaceBetween:
        if (itemsOnLine > 1) {
          betweenMainDim = fmaxf(remainingFreeSpace, 0) / (itemsOnLine - 1);
        } else {
          betweenMainDim = 0;
        }
        break;
      case CSSJustifySpaceAround:
        // Space on the edges is half of the space between elements
        betweenMainDim = remainingFreeSpace / itemsOnLine;
        leadingMainDim = betweenMainDim / 2;
        break;
      default:
        break;
    }

    float mainDim = leadingPaddingAndBorderMain + leadingMainDim;
    float crossDim = 0;

    for (uint32_t i = startOfLineIndex; i < endOfLineIndex; i++) {
      const CSSNodeRef child = CSSNodeListGet(node->children, i);

      if (child->style.positionType == CSSPositionTypeAbsolute &&
          isLeadingPosDefined(child, mainAxis)) {
        if (performLayout) {
          // In case the child is position absolute and has left/top being
          // defined, we override the position to whatever the user said
          // (and margin/border).
          child->layout.position[pos[mainAxis]] = getLeadingPosition(child, mainAxis) +
                                                  getLeadingBorder(node, mainAxis) +
                                                  getLeadingMargin(child, mainAxis);
        }
      } else {
        if (performLayout) {
          // If the child is position absolute (without top/left) or relative,
          // we put it at the current accumulated offset.
          child->layout.position[pos[mainAxis]] += mainDim;
        }

        // Now that we placed the element, we need to update the variables.
        // We need to do that only for relative elements. Absolute elements
        // do not take part in that phase.
        if (child->style.positionType == CSSPositionTypeRelative) {
          if (canSkipFlex) {
            // If we skipped the flex step, then we can't rely on the
            // measuredDims because
            // they weren't computed. This means we can't call getDimWithMargin.
            mainDim +=
                betweenMainDim + getMarginAxis(child, mainAxis) + child->layout.computedFlexBasis;
            crossDim = availableInnerCrossDim;
          } else {
            // The main dimension is the sum of all the elements dimension plus
            // the spacing.
            mainDim += betweenMainDim + getDimWithMargin(child, mainAxis);

            // The cross dimension is the max of the elements dimension since
            // there
            // can only be one element in that cross dimension.
            crossDim = fmaxf(crossDim, getDimWithMargin(child, crossAxis));
          }
        }
      }
    }

    mainDim += trailingPaddingAndBorderMain;

    float containerCrossAxis = availableInnerCrossDim;
    if (measureModeCrossDim == CSSMeasureModeUndefined ||
        measureModeCrossDim == CSSMeasureModeAtMost) {
      // Compute the cross axis from the max cross dimension of the children.
      containerCrossAxis = boundAxis(node, crossAxis, crossDim + paddingAndBorderAxisCross) -
                           paddingAndBorderAxisCross;

      if (measureModeCrossDim == CSSMeasureModeAtMost) {
        containerCrossAxis = fminf(containerCrossAxis, availableInnerCrossDim);
      }
    }

    // If there's no flex wrap, the cross dimension is defined by the container.
    if (!isNodeFlexWrap && measureModeCrossDim == CSSMeasureModeExactly) {
      crossDim = availableInnerCrossDim;
    }

    // Clamp to the min/max size specified on the container.
    crossDim = boundAxis(node, crossAxis, crossDim + paddingAndBorderAxisCross) -
               paddingAndBorderAxisCross;

    // STEP 7: CROSS-AXIS ALIGNMENT
    // We can skip child alignment if we're just measuring the container.
    if (performLayout) {
      for (uint32_t i = startOfLineIndex; i < endOfLineIndex; i++) {
        const CSSNodeRef child = CSSNodeListGet(node->children, i);

        if (child->style.positionType == CSSPositionTypeAbsolute) {
          // If the child is absolutely positioned and has a
          // top/left/bottom/right
          // set, override all the previously computed positions to set it
          // correctly.
          if (isLeadingPosDefined(child, crossAxis)) {
            child->layout.position[pos[crossAxis]] = getLeadingPosition(child, crossAxis) +
                                                     getLeadingBorder(node, crossAxis) +
                                                     getLeadingMargin(child, crossAxis);
          } else {
            child->layout.position[pos[crossAxis]] =
                leadingPaddingAndBorderCross + getLeadingMargin(child, crossAxis);
          }
        } else {
          float leadingCrossDim = leadingPaddingAndBorderCross;

          // For a relative children, we're either using alignItems (parent) or
          // alignSelf (child) in order to determine the position in the cross
          // axis
          const CSSAlign alignItem = getAlignItem(node, child);

          // If the child uses align stretch, we need to lay it out one more
          // time, this time
          // forcing the cross-axis size to be the computed cross size for the
          // current line.
          if (alignItem == CSSAlignStretch) {
            const bool isCrossSizeDefinite =
                (isMainAxisRow && isStyleDimDefined(child, CSSFlexDirectionColumn)) ||
                (!isMainAxisRow && isStyleDimDefined(child, CSSFlexDirectionRow));

            if (isMainAxisRow) {
              childHeight = crossDim;
              childWidth = child->layout.measuredDimensions[CSSDimensionWidth] +
                           getMarginAxis(child, CSSFlexDirectionRow);
            } else {
              childWidth = crossDim;
              childHeight = child->layout.measuredDimensions[CSSDimensionHeight] +
                            getMarginAxis(child, CSSFlexDirectionColumn);
            }

            // If the child defines a definite size for its cross axis, there's
            // no need to stretch.
            if (!isCrossSizeDefinite) {
              childWidthMeasureMode =
                  CSSValueIsUndefined(childWidth) ? CSSMeasureModeUndefined : CSSMeasureModeExactly;
              childHeightMeasureMode = CSSValueIsUndefined(childHeight) ? CSSMeasureModeUndefined
                                                                        : CSSMeasureModeExactly;
              layoutNodeInternal(child,
                                 childWidth,
                                 childHeight,
                                 direction,
                                 childWidthMeasureMode,
                                 childHeightMeasureMode,
                                 true,
                                 "stretch");
            }
          } else if (alignItem != CSSAlignFlexStart) {
            const float remainingCrossDim = containerCrossAxis - getDimWithMargin(child, crossAxis);

            if (alignItem == CSSAlignCenter) {
              leadingCrossDim += remainingCrossDim / 2;
            } else { // CSSAlignFlexEnd
              leadingCrossDim += remainingCrossDim;
            }
          }

          // And we apply the position
          child->layout.position[pos[crossAxis]] += totalLineCrossDim + leadingCrossDim;
        }
      }
    }

    totalLineCrossDim += crossDim;
    maxLineMainDim = fmaxf(maxLineMainDim, mainDim);
  }

  // STEP 8: MULTI-LINE CONTENT ALIGNMENT
  if (lineCount > 1 && performLayout && !CSSValueIsUndefined(availableInnerCrossDim)) {
    const float remainingAlignContentDim = availableInnerCrossDim - totalLineCrossDim;

    float crossDimLead = 0;
    float currentLead = leadingPaddingAndBorderCross;

    const CSSAlign alignContent = node->style.alignContent;
    if (alignContent == CSSAlignFlexEnd) {
      currentLead += remainingAlignContentDim;
    } else if (alignContent == CSSAlignCenter) {
      currentLead += remainingAlignContentDim / 2;
    } else if (alignContent == CSSAlignStretch) {
      if (availableInnerCrossDim > totalLineCrossDim) {
        crossDimLead = (remainingAlignContentDim / lineCount);
      }
    }

    uint32_t endIndex = 0;
    for (uint32_t i = 0; i < lineCount; i++) {
      uint32_t startIndex = endIndex;
      uint32_t ii;

      // compute the line's height and find the endIndex
      float lineHeight = 0;
      for (ii = startIndex; ii < childCount; ii++) {
        const CSSNodeRef child = CSSNodeListGet(node->children, ii);

        if (child->style.positionType == CSSPositionTypeRelative) {
          if (child->lineIndex != i) {
            break;
          }

          if (isLayoutDimDefined(child, crossAxis)) {
            lineHeight = fmaxf(lineHeight,
                               child->layout.measuredDimensions[dim[crossAxis]] +
                                   getMarginAxis(child, crossAxis));
          }
        }
      }
      endIndex = ii;
      lineHeight += crossDimLead;

      if (performLayout) {
        for (ii = startIndex; ii < endIndex; ii++) {
          const CSSNodeRef child = CSSNodeListGet(node->children, ii);

          if (child->style.positionType == CSSPositionTypeRelative) {
            switch (getAlignItem(node, child)) {
              case CSSAlignFlexStart:
                child->layout.position[pos[crossAxis]] =
                    currentLead + getLeadingMargin(child, crossAxis);
                break;
              case CSSAlignFlexEnd:
                child->layout.position[pos[crossAxis]] =
                    currentLead + lineHeight - getTrailingMargin(child, crossAxis) -
                    child->layout.measuredDimensions[dim[crossAxis]];
                break;
              case CSSAlignCenter:
                childHeight = child->layout.measuredDimensions[dim[crossAxis]];
                child->layout.position[pos[crossAxis]] =
                    currentLead + (lineHeight - childHeight) / 2;
                break;
              case CSSAlignStretch:
                child->layout.position[pos[crossAxis]] =
                    currentLead + getLeadingMargin(child, crossAxis);
                // TODO(prenaux): Correctly set the height of items with indefinite
                //                (auto) crossAxis dimension.
                break;
              default:
                break;
            }
          }
        }
      }

      currentLead += lineHeight;
    }
  }

  // STEP 9: COMPUTING FINAL DIMENSIONS
  node->layout.measuredDimensions[CSSDimensionWidth] =
      boundAxis(node, CSSFlexDirectionRow, availableWidth - marginAxisRow);
  node->layout.measuredDimensions[CSSDimensionHeight] =
      boundAxis(node, CSSFlexDirectionColumn, availableHeight - marginAxisColumn);

  // If the user didn't specify a width or height for the node, set the
  // dimensions based on the children.
  if (measureModeMainDim == CSSMeasureModeUndefined) {
    // Clamp the size to the min/max size, if specified, and make sure it
    // doesn't go below the padding and border amount.
    node->layout.measuredDimensions[dim[mainAxis]] = boundAxis(node, mainAxis, maxLineMainDim);
  } else if (measureModeMainDim == CSSMeasureModeAtMost) {
    node->layout.measuredDimensions[dim[mainAxis]] =
        fmaxf(fminf(availableInnerMainDim + paddingAndBorderAxisMain,
                    boundAxisWithinMinAndMax(node, mainAxis, maxLineMainDim)),
              paddingAndBorderAxisMain);
  }

  if (measureModeCrossDim == CSSMeasureModeUndefined) {
    // Clamp the size to the min/max size, if specified, and make sure it
    // doesn't go below the padding and border amount.
    node->layout.measuredDimensions[dim[crossAxis]] =
        boundAxis(node, crossAxis, totalLineCrossDim + paddingAndBorderAxisCross);
  } else if (measureModeCrossDim == CSSMeasureModeAtMost) {
    node->layout.measuredDimensions[dim[crossAxis]] =
        fmaxf(fminf(availableInnerCrossDim + paddingAndBorderAxisCross,
                    boundAxisWithinMinAndMax(node,
                                             crossAxis,
                                             totalLineCrossDim + paddingAndBorderAxisCross)),
              paddingAndBorderAxisCross);
  }

  // STEP 10: SIZING AND POSITIONING ABSOLUTE CHILDREN
  currentAbsoluteChild = firstAbsoluteChild;
  while (currentAbsoluteChild != NULL) {
    // Now that we know the bounds of the container, perform layout again on the
    // absolutely-positioned children.
    if (performLayout) {
      childWidth = CSSUndefined;
      childHeight = CSSUndefined;

      if (isStyleDimDefined(currentAbsoluteChild, CSSFlexDirectionRow)) {
        childWidth = currentAbsoluteChild->style.dimensions[CSSDimensionWidth] +
                     getMarginAxis(currentAbsoluteChild, CSSFlexDirectionRow);
      } else {
        // If the child doesn't have a specified width, compute the width based
        // on the left/right
        // offsets if they're defined.
        if (isLeadingPosDefined(currentAbsoluteChild, CSSFlexDirectionRow) &&
            isTrailingPosDefined(currentAbsoluteChild, CSSFlexDirectionRow)) {
          childWidth = node->layout.measuredDimensions[CSSDimensionWidth] -
                       (getLeadingBorder(node, CSSFlexDirectionRow) +
                        getTrailingBorder(node, CSSFlexDirectionRow)) -
                       (getLeadingPosition(currentAbsoluteChild, CSSFlexDirectionRow) +
                        getTrailingPosition(currentAbsoluteChild, CSSFlexDirectionRow));
          childWidth = boundAxis(currentAbsoluteChild, CSSFlexDirectionRow, childWidth);
        }
      }

      if (isStyleDimDefined(currentAbsoluteChild, CSSFlexDirectionColumn)) {
        childHeight = currentAbsoluteChild->style.dimensions[CSSDimensionHeight] +
                      getMarginAxis(currentAbsoluteChild, CSSFlexDirectionColumn);
      } else {
        // If the child doesn't have a specified height, compute the height
        // based on the top/bottom
        // offsets if they're defined.
        if (isLeadingPosDefined(currentAbsoluteChild, CSSFlexDirectionColumn) &&
            isTrailingPosDefined(currentAbsoluteChild, CSSFlexDirectionColumn)) {
          childHeight = node->layout.measuredDimensions[CSSDimensionHeight] -
                        (getLeadingBorder(node, CSSFlexDirectionColumn) +
                         getTrailingBorder(node, CSSFlexDirectionColumn)) -
                        (getLeadingPosition(currentAbsoluteChild, CSSFlexDirectionColumn) +
                         getTrailingPosition(currentAbsoluteChild, CSSFlexDirectionColumn));
          childHeight = boundAxis(currentAbsoluteChild, CSSFlexDirectionColumn, childHeight);
        }
      }

      // If we're still missing one or the other dimension, measure the content.
      if (CSSValueIsUndefined(childWidth) || CSSValueIsUndefined(childHeight)) {
        childWidthMeasureMode =
            CSSValueIsUndefined(childWidth) ? CSSMeasureModeUndefined : CSSMeasureModeExactly;
        childHeightMeasureMode =
            CSSValueIsUndefined(childHeight) ? CSSMeasureModeUndefined : CSSMeasureModeExactly;

        // According to the spec, if the main size is not definite and the
        // child's inline axis is parallel to the main axis (i.e. it's
        // horizontal), the child should be sized using "UNDEFINED" in
        // the main size. Otherwise use "AT_MOST" in the cross axis.
        if (!isMainAxisRow && CSSValueIsUndefined(childWidth) &&
            !CSSValueIsUndefined(availableInnerWidth)) {
          childWidth = availableInnerWidth;
          childWidthMeasureMode = CSSMeasureModeAtMost;
        }

        layoutNodeInternal(currentAbsoluteChild,
                           childWidth,
                           childHeight,
                           direction,
                           childWidthMeasureMode,
                           childHeightMeasureMode,
                           false,
                           "abs-measure");
        childWidth = currentAbsoluteChild->layout.measuredDimensions[CSSDimensionWidth] +
                     getMarginAxis(currentAbsoluteChild, CSSFlexDirectionRow);
        childHeight = currentAbsoluteChild->layout.measuredDimensions[CSSDimensionHeight] +
                      getMarginAxis(currentAbsoluteChild, CSSFlexDirectionColumn);
      }

      layoutNodeInternal(currentAbsoluteChild,
                         childWidth,
                         childHeight,
                         direction,
                         CSSMeasureModeExactly,
                         CSSMeasureModeExactly,
                         true,
                         "abs-layout");

      if (isTrailingPosDefined(currentAbsoluteChild, mainAxis) &&
          !isLeadingPosDefined(currentAbsoluteChild, mainAxis)) {
        currentAbsoluteChild->layout.position[leading[mainAxis]] =
            node->layout.measuredDimensions[dim[mainAxis]] -
            currentAbsoluteChild->layout.measuredDimensions[dim[mainAxis]] -
            getTrailingPosition(currentAbsoluteChild, mainAxis);
      }

      if (isTrailingPosDefined(currentAbsoluteChild, crossAxis) &&
          !isLeadingPosDefined(currentAbsoluteChild, crossAxis)) {
        currentAbsoluteChild->layout.position[leading[crossAxis]] =
            node->layout.measuredDimensions[dim[crossAxis]] -
            currentAbsoluteChild->layout.measuredDimensions[dim[crossAxis]] -
            getTrailingPosition(currentAbsoluteChild, crossAxis);
      }
    }

    currentAbsoluteChild = currentAbsoluteChild->nextChild;
  }

  // STEP 11: SETTING TRAILING POSITIONS FOR CHILDREN
  if (performLayout) {
    const bool needsMainTrailingPos =
        mainAxis == CSSFlexDirectionRowReverse || mainAxis == CSSFlexDirectionColumnReverse;
    const bool needsCrossTrailingPos =
        CSSFlexDirectionRowReverse || crossAxis == CSSFlexDirectionColumnReverse;

    // Set trailing position if necessary.
    if (needsMainTrailingPos || needsCrossTrailingPos) {
      for (uint32_t i = 0; i < childCount; i++) {
        const CSSNodeRef child = CSSNodeListGet(node->children, i);

        if (needsMainTrailingPos) {
          setTrailingPosition(node, child, mainAxis);
        }

        if (needsCrossTrailingPos) {
          setTrailingPosition(node, child, crossAxis);
        }
      }
    }
  }
}

uint32_t gDepth = 0;
bool gPrintTree = false;
bool gPrintChanges = false;
bool gPrintSkips = false;

static const char *spacer = "                                                            ";

static const char *getSpacer(const unsigned long level) {
  const unsigned long spacerLen = strlen(spacer);
  if (level > spacerLen) {
    return &spacer[0];
  } else {
    return &spacer[spacerLen - level];
  }
}

static const char *getModeName(const CSSMeasureMode mode, const bool performLayout) {
  const char *kMeasureModeNames[CSSMeasureModeCount] = {"UNDEFINED", "EXACTLY", "AT_MOST"};
  const char *kLayoutModeNames[CSSMeasureModeCount] = {"LAY_UNDEFINED",
                                                       "LAY_EXACTLY",
                                                       "LAY_AT_"
                                                       "MOST"};

  if (mode >= CSSMeasureModeCount) {
    return "";
  }

  return performLayout ? kLayoutModeNames[mode] : kMeasureModeNames[mode];
}

static bool canUseCachedMeasurement(const bool isTextNode,
                                    const float availableWidth,
                                    const float availableHeight,
                                    const float marginRow,
                                    const float marginColumn,
                                    const CSSMeasureMode widthMeasureMode,
                                    const CSSMeasureMode heightMeasureMode,
                                    CSSCachedMeasurement cachedLayout) {
  const bool isHeightSame = (cachedLayout.heightMeasureMode == CSSMeasureModeUndefined &&
                             heightMeasureMode == CSSMeasureModeUndefined) ||
                            (cachedLayout.heightMeasureMode == heightMeasureMode &&
                             eq(cachedLayout.availableHeight, availableHeight));

  const bool isWidthSame = (cachedLayout.widthMeasureMode == CSSMeasureModeUndefined &&
                            widthMeasureMode == CSSMeasureModeUndefined) ||
                           (cachedLayout.widthMeasureMode == widthMeasureMode &&
                            eq(cachedLayout.availableWidth, availableWidth));

  if (isHeightSame && isWidthSame) {
    return true;
  }

  const bool isHeightValid = (cachedLayout.heightMeasureMode == CSSMeasureModeUndefined &&
                              heightMeasureMode == CSSMeasureModeAtMost &&
                              cachedLayout.computedHeight <= (availableHeight - marginColumn)) ||
                             (heightMeasureMode == CSSMeasureModeExactly &&
                              eq(cachedLayout.computedHeight, availableHeight - marginColumn));

  if (isWidthSame && isHeightValid) {
    return true;
  }

  const bool isWidthValid = (cachedLayout.widthMeasureMode == CSSMeasureModeUndefined &&
                             widthMeasureMode == CSSMeasureModeAtMost &&
                             cachedLayout.computedWidth <= (availableWidth - marginRow)) ||
                            (widthMeasureMode == CSSMeasureModeExactly &&
                             eq(cachedLayout.computedWidth, availableWidth - marginRow));

  if (isHeightSame && isWidthValid) {
    return true;
  }

  if (isHeightValid && isWidthValid) {
    return true;
  }

  // We know this to be text so we can apply some more specialized heuristics.
  if (isTextNode) {
    if (isWidthSame) {
      if (heightMeasureMode == CSSMeasureModeUndefined) {
        // Width is the same and height is not restricted. Re-use cahced value.
        return true;
      }

      if (heightMeasureMode == CSSMeasureModeAtMost &&
          cachedLayout.computedHeight < (availableHeight - marginColumn)) {
        // Width is the same and height restriction is greater than the cached
        // height. Re-use cached
        // value.
        return true;
      }

      // Width is the same but height restriction imposes smaller height than
      // previously measured.
      // Update the cached value to respect the new height restriction.
      cachedLayout.computedHeight = availableHeight - marginColumn;
      return true;
    }

    if (cachedLayout.widthMeasureMode == CSSMeasureModeUndefined) {
      if (widthMeasureMode == CSSMeasureModeUndefined ||
          (widthMeasureMode == CSSMeasureModeAtMost &&
           cachedLayout.computedWidth <= (availableWidth - marginRow))) {
        // Previsouly this text was measured with no width restriction, if width
        // is now restricted
        // but to a larger value than the previsouly measured width we can
        // re-use the measurement
        // as we know it will fit.
        return true;
      }
    }
  }

  return false;
}

//
// This is a wrapper around the layoutNodeImpl function. It determines
// whether the layout request is redundant and can be skipped.
//
// Parameters:
//  Input parameters are the same as layoutNodeImpl (see above)
//  Return parameter is true if layout was performed, false if skipped
//
bool layoutNodeInternal(const CSSNodeRef node,
                        const float availableWidth,
                        const float availableHeight,
                        const CSSDirection parentDirection,
                        const CSSMeasureMode widthMeasureMode,
                        const CSSMeasureMode heightMeasureMode,
                        const bool performLayout,
                        const char *reason) {
  CSSLayout *layout = &node->layout;

  gDepth++;

  const bool needToVisitNode =
      (node->isDirty && layout->generationCount != gCurrentGenerationCount) ||
      layout->lastParentDirection != parentDirection;

  if (needToVisitNode) {
    // Invalidate the cached results.
    layout->nextCachedMeasurementsIndex = 0;
    layout->cachedLayout.widthMeasureMode = (CSSMeasureMode) -1;
    layout->cachedLayout.heightMeasureMode = (CSSMeasureMode) -1;
  }

  CSSCachedMeasurement *cachedResults = NULL;

  // Determine whether the results are already cached. We maintain a separate
  // cache for layouts and measurements. A layout operation modifies the
  // positions
  // and dimensions for nodes in the subtree. The algorithm assumes that each
  // node
  // gets layed out a maximum of one time per tree layout, but multiple
  // measurements
  // may be required to resolve all of the flex dimensions.
  // We handle nodes with measure functions specially here because they are the
  // most
  // expensive to measure, so it's worth avoiding redundant measurements if at
  // all possible.
  if (node->measure) {
    const float marginAxisRow = getMarginAxis(node, CSSFlexDirectionRow);
    const float marginAxisColumn = getMarginAxis(node, CSSFlexDirectionColumn);

    // First, try to use the layout cache.
    if (canUseCachedMeasurement(node->isTextNode,
                                availableWidth,
                                availableHeight,
                                marginAxisRow,
                                marginAxisColumn,
                                widthMeasureMode,
                                heightMeasureMode,
                                layout->cachedLayout)) {
      cachedResults = &layout->cachedLayout;
    } else {
      // Try to use the measurement cache.
      for (uint32_t i = 0; i < layout->nextCachedMeasurementsIndex; i++) {
        if (canUseCachedMeasurement(node->isTextNode,
                                    availableWidth,
                                    availableHeight,
                                    marginAxisRow,
                                    marginAxisColumn,
                                    widthMeasureMode,
                                    heightMeasureMode,
                                    layout->cachedMeasurements[i])) {
          cachedResults = &layout->cachedMeasurements[i];
          break;
        }
      }
    }
  } else if (performLayout) {
    if (eq(layout->cachedLayout.availableWidth, availableWidth) &&
        eq(layout->cachedLayout.availableHeight, availableHeight) &&
        layout->cachedLayout.widthMeasureMode == widthMeasureMode &&
        layout->cachedLayout.heightMeasureMode == heightMeasureMode) {
      cachedResults = &layout->cachedLayout;
    }
  } else {
    for (uint32_t i = 0; i < layout->nextCachedMeasurementsIndex; i++) {
      if (eq(layout->cachedMeasurements[i].availableWidth, availableWidth) &&
          eq(layout->cachedMeasurements[i].availableHeight, availableHeight) &&
          layout->cachedMeasurements[i].widthMeasureMode == widthMeasureMode &&
          layout->cachedMeasurements[i].heightMeasureMode == heightMeasureMode) {
        cachedResults = &layout->cachedMeasurements[i];
        break;
      }
    }
  }

  if (!needToVisitNode && cachedResults != NULL) {
    layout->measuredDimensions[CSSDimensionWidth] = cachedResults->computedWidth;
    layout->measuredDimensions[CSSDimensionHeight] = cachedResults->computedHeight;

    if (gPrintChanges && gPrintSkips) {
      printf("%s%d.{[skipped] ", getSpacer(gDepth), gDepth);
      if (node->print) {
        node->print(node->context);
      }
      printf("wm: %s, hm: %s, aw: %f ah: %f => d: (%f, %f) %s\n",
             getModeName(widthMeasureMode, performLayout),
             getModeName(heightMeasureMode, performLayout),
             availableWidth,
             availableHeight,
             cachedResults->computedWidth,
             cachedResults->computedHeight,
             reason);
    }
  } else {
    if (gPrintChanges) {
      printf("%s%d.{%s", getSpacer(gDepth), gDepth, needToVisitNode ? "*" : "");
      if (node->print) {
        node->print(node->context);
      }
      printf("wm: %s, hm: %s, aw: %f ah: %f %s\n",
             getModeName(widthMeasureMode, performLayout),
             getModeName(heightMeasureMode, performLayout),
             availableWidth,
             availableHeight,
             reason);
    }

    layoutNodeImpl(node,
                   availableWidth,
                   availableHeight,
                   parentDirection,
                   widthMeasureMode,
                   heightMeasureMode,
                   performLayout);

    if (gPrintChanges) {
      printf("%s%d.}%s", getSpacer(gDepth), gDepth, needToVisitNode ? "*" : "");
      if (node->print) {
        node->print(node->context);
      }
      printf("wm: %s, hm: %s, d: (%f, %f) %s\n",
             getModeName(widthMeasureMode, performLayout),
             getModeName(heightMeasureMode, performLayout),
             layout->measuredDimensions[CSSDimensionWidth],
             layout->measuredDimensions[CSSDimensionHeight],
             reason);
    }

    layout->lastParentDirection = parentDirection;

    if (cachedResults == NULL) {
      if (layout->nextCachedMeasurementsIndex == CSS_MAX_CACHED_RESULT_COUNT) {
        if (gPrintChanges) {
          printf("Out of cache entries!\n");
        }
        layout->nextCachedMeasurementsIndex = 0;
      }

      CSSCachedMeasurement *newCacheEntry;
      if (performLayout) {
        // Use the single layout cache entry.
        newCacheEntry = &layout->cachedLayout;
      } else {
        // Allocate a new measurement cache entry.
        newCacheEntry = &layout->cachedMeasurements[layout->nextCachedMeasurementsIndex];
        layout->nextCachedMeasurementsIndex++;
      }

      newCacheEntry->availableWidth = availableWidth;
      newCacheEntry->availableHeight = availableHeight;
      newCacheEntry->widthMeasureMode = widthMeasureMode;
      newCacheEntry->heightMeasureMode = heightMeasureMode;
      newCacheEntry->computedWidth = layout->measuredDimensions[CSSDimensionWidth];
      newCacheEntry->computedHeight = layout->measuredDimensions[CSSDimensionHeight];
    }
  }

  if (performLayout) {
    node->layout.dimensions[CSSDimensionWidth] = node->layout.measuredDimensions[CSSDimensionWidth];
    node->layout.dimensions[CSSDimensionHeight] =
        node->layout.measuredDimensions[CSSDimensionHeight];
    node->hasNewLayout = true;
    node->isDirty = false;
  }

  gDepth--;
  layout->generationCount = gCurrentGenerationCount;
  return (needToVisitNode || cachedResults == NULL);
}

void CSSNodeCalculateLayout(const CSSNodeRef node,
                            const float availableWidth,
                            const float availableHeight,
                            const CSSDirection parentDirection) {
  // Increment the generation count. This will force the recursive routine to
  // visit
  // all dirty nodes at least once. Subsequent visits will be skipped if the
  // input
  // parameters don't change.
  gCurrentGenerationCount++;

  float width = availableWidth;
  float height = availableHeight;
  CSSMeasureMode widthMeasureMode = CSSMeasureModeUndefined;
  CSSMeasureMode heightMeasureMode = CSSMeasureModeUndefined;

  if (!CSSValueIsUndefined(width)) {
    widthMeasureMode = CSSMeasureModeExactly;
  } else if (isStyleDimDefined(node, CSSFlexDirectionRow)) {
    width =
        node->style.dimensions[dim[CSSFlexDirectionRow]] + getMarginAxis(node, CSSFlexDirectionRow);
    widthMeasureMode = CSSMeasureModeExactly;
  } else if (node->style.maxDimensions[CSSDimensionWidth] >= 0.0) {
    width = node->style.maxDimensions[CSSDimensionWidth];
    widthMeasureMode = CSSMeasureModeAtMost;
  }

  if (!CSSValueIsUndefined(height)) {
    heightMeasureMode = CSSMeasureModeExactly;
  } else if (isStyleDimDefined(node, CSSFlexDirectionColumn)) {
    height = node->style.dimensions[dim[CSSFlexDirectionColumn]] +
             getMarginAxis(node, CSSFlexDirectionColumn);
    heightMeasureMode = CSSMeasureModeExactly;
  } else if (node->style.maxDimensions[CSSDimensionHeight] >= 0.0) {
    height = node->style.maxDimensions[CSSDimensionHeight];
    heightMeasureMode = CSSMeasureModeAtMost;
  }

  if (layoutNodeInternal(node,
                         width,
                         height,
                         parentDirection,
                         widthMeasureMode,
                         heightMeasureMode,
                         true,
                         "initia"
                         "l")) {
    setPosition(node, node->layout.direction);

    if (gPrintTree) {
      CSSNodePrint(node, CSSPrintOptionsLayout | CSSPrintOptionsChildren | CSSPrintOptionsStyle);
    }
  }
}

#ifdef CSS_ASSERT_FAIL_ENABLED
static CSSAssertFailFunc gAssertFailFunc;

void CSSAssertSetFailFunc(CSSAssertFailFunc func) {
  gAssertFailFunc = func;
}

void CSSAssertFail(const char *message) {
  if (gAssertFailFunc) {
    (*gAssertFailFunc)(message);
  }
}
#endif
