def run(mcd, indent = '  '):
  i = indent
  block_hdr = [
    "/*",
    f"{i}This file was generated by mcd2cpp.py",
    f"{i}It should not be edited by hand.",
    "*/",
    "",
    f"// For MCD version {mcd.version['minecraftVersion']}",
    "",
    "#ifndef BLOCKDATA_HPP",
    "#define BLOCKDATA_HPP",
    "",
    "#include <cstdint>",
    "#include <string>",
    "#include <vector>",
    "#include <optional>",
    "#include <functional>",
    "#include \"shape_data.hpp\"",
    "",
    "namespace mcd {",
    "",
    "struct MCBlockPropertyData {",
    f"{i}std::string name;",
    f"{i}std::uint8_t num_values;",
    "};",
    "",
    "struct MCBlockData {",
    f"{i}std::uint64_t id;",
    f"{i}std::uint64_t min_state_id;",
    f"{i}std::uint64_t max_state_id;",
    f"{i}std::uint64_t default_state;",
    f"{i}std::uint16_t stack_size;",
    f"{i}std::uint8_t emit_light;",
    f"{i}std::uint8_t filter_light;",
    "",
    f"{i}std::string name;",
    f"{i}std::string display_name;",
    "",
    f"{i}bool diggable;",
    f"{i}bool collidable;",
    f"{i}bool transparent;",
    "",
    f"{i}std::vector<std::reference_wrapper<const std::vector<MCBBoxData>>> shapes;",
    f"{i}std::vector<std::uint64_t> drops;",
    f"{i}std::vector<MCBlockPropertyData> props;",
    "",
    f"{i}std::optional<std::string> material;",
    f"{i}std::optional<float> hardness;",
    "};",
    "",
    f"extern const MCBlockData block_data[{len(mcd.blocks_list)}];",
    "",
    "const MCBlockData& from_stateID(std::uint64_t state_id);"
    "",
    "} // namespace mcd",
    "#endif // BLOCKDATA_HPP",
  ]

  with open(f"block_data.hpp", "w") as f:
    f.write('\n'.join(block_hdr))

  block_cpp = [
    "/*",
    f"{i}This file was generated by mcd2cpp.py",
    f"{i}It should not be edited by hand.",
    "*/",
    "",
    "#include <map>",
    "#include \"block_data.hpp\"",
    "",
    "namespace mcd {",
    ""
    "const MCBlockData block_data[] = {",
  ]

  for block in mcd.blocks_list:
    shapes = mcd.blockCollisionShapes['blocks'][block['name']]
    if isinstance(shapes, int):
      shapes = [shapes]
    block_cpp.extend((
      f"{i}{{",
      f"{i*2}.id = {block['id']},",
      f"{i*2}.min_state_id = {block['minStateId']},",
      f"{i*2}.max_state_id = {block['maxStateId']},",
      f"{i*2}.default_state = {block['defaultState']},",
      f"{i*2}.stack_size = {block['stackSize']},",
      f"{i*2}.emit_light = {block['emitLight']},",
      f"{i*2}.filter_light = {block['filterLight']},",
      "",
      f"{i*2}.name = \"{block['name']}\",",
      f"{i*2}.display_name = \"{block['displayName']}\",",
      "",
      f"{i*2}.diggable = {str(block['diggable']).lower()},",
      f"{i*2}.collidable = {str(block['boundingBox'] == 'block').lower()},",
      f"{i*2}.transparent = {str(block['transparent']).lower()},",
      "",
      f"{i*2}.shapes = {{{', '.join(f'shapes[{x}]' for x in shapes)}}},",
      f"{i*2}.drops = {{{', '.join(str(x) for x in block['drops'])}}},",
    ))

    if block['states']:
      block_cpp.append(f"{i*2}.props = {{")
      block_cpp.extend(f"{i*3}{{\"{prop['name']}\", {prop['num_values']}}},"
          for prop in block['states'])
      block_cpp[-1] = block_cpp[-1][:-1]
      block_cpp.append(f"{i*2}}}")
    else:
      block_cpp.append(f"{i*2}.props = {{}}")

    if 'material' in block:
      block_cpp[-1] += ','
      block_cpp.append(f"{i*2}.material = \"{block['material']}\"")

    if block['hardness'] is not None:
      block_cpp[-1] += ','
      block_cpp.append(f"{i*2}.hardness = {block['hardness']}")

    block_cpp.append(f"{i}}},")

  block_cpp[-1] = block_cpp[-1][:-1]
  block_cpp.extend((
    "};",
    "",
    "const std::map<std::uint64_t, const MCBlockData&> state_id_map = {"
  ))

  block_cpp.append(',\n'.join(f"{i}{{{block['maxStateId']}, block_data[{block['id']}]}}" for block in mcd.blocks_list))

  block_cpp.extend((
    "};",
    "",
    "const MCBlockData& from_stateID(std::uint64_t state_id) {",
    f"{i}return state_id_map.lower_bound(state_id)->second;",
    "}",
    "",
    "} // namespace mcd",
    ""
  ))

  with open(f"block_data.cpp", "w") as f:
    f.write('\n'.join(block_cpp))
