import sys
import json
import shared
import textwrap

TEMPLATE = """
// generated code! be nice
#include <stdbool.h>
#include "sys/sys.h"

sys_maptile map_{{map_name}}_data[{{width * height}}] = { {{ tiles|join(",") }} };
sys_map map_{{map_name}} = {
    .tiles=map_{{map_name}}_data,
    .width={{width}},
    .height={{height}},
};

{% for entity_type in entity_types %}
void map_{{map_name}}_{{entity_type}}_create(sys_i32 x, sys_i32 y);
{% endfor %}

{% for entity_name, field_name, c_type, render_mode in entity_fields %}
{% if render_mode == "scalar" %}
void map_{{map_name}}_{{entity_name}}_set_{{field_name}}({{c_type}} value);
{% elif render_mode == "array" %}
void map_{{map_name}}_{{entity_name}}_add_{{field_name}}({{c_type}} value);
{% else %}
wtf; // {{ render_mode }}
{% endif %}
{% endfor %}

void map_{{map_name}}_create_entities() {
    {% for entity in entities %}
    map_{{map_name}}_{{entity.type}}_create({{entity.x}}, {{entity.y}});

        {% for field in entity.fields %}
        {% if field.renderMode == "scalar" %}
            map_{{map_name}}_{{entity.type}}_set_{{field.name}}({{field.value|safe}});
        {% elif field.renderMode == "array" %}
            {% for s in field.value %}
                map_{{map_name}}_{{entity.type}}_add_{{field.name}}({{s|safe}});
            {% endfor %}
        {% endif %}
        {% endfor %}

    {% endfor %}
}
""".lstrip()

def main(map_name, fname_ldtk, fname_c):
    width, height, tiles, entities, entity_types, entity_fields = load_mapdata(fname_ldtk)
    print(entity_fields)

    with open(fname_c, "wt") as output:
        output.write(
            shared.templates.from_string(TEMPLATE).render(
                map_name=map_name,
                tiles=tiles,
                width=width,
                height=height,
                entities=entities,
                entity_types=entity_types,
                entity_fields=entity_fields,
            )
        )


def load_mapdata(fname_ldtk):
    sparse_tiles = {}
    entities = []
    entity_types = set()
    entity_fields = set()

    with open(fname_ldtk, "rt") as f:
        data = json.load(f)
        for level in data["levels"]:
            level_x = level["worldX"] // 8
            level_y = level["worldY"] // 8
            for layer in level["layerInstances"]:
                if layer["__identifier"] == "conceptual":
                    # for right now I use the vague layer to assign tiles
                    for tile in layer["autoLayerTiles"]:
                        x, y = tile["px"]
                        x //= 8
                        y //= 8
                        ix = tile["t"]
                        sparse_tiles[level_x + x, level_y + y] = ix
                
                if layer["__identifier"] == "entities":
                    for e in layer["entityInstances"]:
                        # TODO: Other fields?
                        entity = {
                            "type": e["__identifier"],
                            "x": e["__worldX"] // 8,
                            "y": e["__worldY"] // 8,
                            "fields": []
                        }
                        for f in e["fieldInstances"]:
                            field = {
                                "name": f["__identifier"],
                            }
                            field["value"], field["renderMode"], rendered_type = format_field_value(f["__type"], f["__value"])
                            entity["fields"].append(field)
                            entity_fields.add((
                                entity["type"],
                                field["name"],
                                rendered_type,
                                field["renderMode"]
                            ))

                        entities.append(entity)
                        entity_types.add(entity["type"])
    
    x_min = 0
    y_min = 0
    assert not any(x for (x, _) in sparse_tiles if x < x_min), "level can't be left of (0, 0)"
    assert not any(y for (_, y) in sparse_tiles if y < y_min), "level can't be up from (0, 0)"
    width = max(x for (x, _) in sparse_tiles) + 1
    height = max(y for (_, y) in sparse_tiles) + 1
    dense_tiles = []
    for y in range(height):
        for x in range(width):
            k = (x, y)
            if k in sparse_tiles:
                dense_tiles.append(sparse_tiles[k])
            else:
                dense_tiles.append(255)

    return width, height, dense_tiles, entities, entity_types, entity_fields


def format_field_value(ty, val):
    if ty == "Bool":
        return "true" if val else "false", "scalar", "bool"
    elif ty == "Int":
        return str(val), "scalar", "sys_i32"
    elif ty == "String":
        if val is None: 
            return "NULL", "scalar", "const char*"
        return json.dumps(wrap(val)), "scalar"  # this is close enough to being right in C
    elif ty == "Array<Int>":
        return [format_field_value("Int", i)[0] for i in val], "array", "sys_i32"
    elif ty == "Array<String>":
        return [format_field_value("String", i)[0] for i in val], "array", "const char*"
    else:
        assert False, f"unknown type: {ty}"


def wrap(s):
    return textwrap.fill(s, width=28)

def annot_xy(lst, w, h):
    assert len(lst) == w * h
    for y in range(h):
        for x in range(w):
            yield x, y, lst[y * w + x]


if __name__ == "__main__":
    assert len(sys.argv) == 4, \
        "there must be three args (map name, src ldtk, out c)"
    main(sys.argv[1], sys.argv[2], sys.argv[3])