#! /usr/bin/env python

import yaml
import json
import subprocess

# One "tick" is 20 units
NODE_WIDTH = 560
NODE_HEIGHT = 960
NODE_SPACING_W = 560
NODE_SPACING_H = 40

def FindNodeWithId(y, id) -> dict:
    for node in y['nodes']:
        if node['id'] == id:
            return node
    raise Exception(f"Could not find node with id {id}")

def Preprocess() -> dict:
    y = yaml.safe_load(open('source.yaml'))

    for node in y['nodes']:
        if 'id' not in node:
            node['id'] = node['name']
        node['id'] = node['id'].lower()
        node['parents'] = []
        node['children'] = []
    
    for link in y['links']:
        id_from = link['from']
        id_to = link['to']
        if 'text' not in link: link['text'] = ""
        parent = FindNodeWithId(y, id_from)
        child = FindNodeWithId(y, id_to)
        child['parents'].append(parent)
        parent['children'].append(child)

    return y


def MakeMarkdown(y):

    nodes = y['nodes']
    for node in nodes:
        with open(node['name'] + '.md', 'w') as f:

            # Write tags
            f.write("---\n")
            for k, v in node.items():
                if k in ['name', 'image', 'header', 'footer']:
                    continue
                f.write(f"{k}: {v}\n")
            f.write("---\n")

            # Write body
            if 'header' in node:
                f.write(node['header'] + "\n")
            if 'image' in node:
                f.write("![[pictures/" + node['image'] + "]]\n")
            if 'footer' in node:
                f.write(node['footer'] + "\n")


def FindGenerationUp(y, id) -> int:
    highest = 0
    node = FindNodeWithId(y, id)

    if len(node['parents']) == 0:
        return None

    for parent in node['parents']:
        pgen = FindGenerationUp(y, parent['id'])
        if pgen != None:
            highest = max(pgen, highest)

    return highest + 1
    # if node['name'] in ["Takarazuka Revue"]:
    #     return 0
    # else:
    #     return highest + 1

def FindGenerationDown(y, id) -> int:
    n = FindNodeWithId(y, id)
    smallestChild = 6969
    for child in n['children']:
        smallestChild = min(smallestChild, child['generation'])
    if smallestChild == 6969:
        return 0
    return smallestChild - 1

# Count intersections between parent_gen and child_gen
def CountParentGenOverlaps(parent_gen, child_gen):
    count = 0

    # Look at every parent i
    for i in range(len(parent_gen) - 1):
        i_child_indices = []
        for child in parent_gen[i]['children']:
            for child_index, tent_child in enumerate(child_gen):
                if child['id'] == tent_child['id']:
                    i_child_indices.append(child_index)
                    break
        # Look at every after-sibling j
        for j in range(i+1, len(parent_gen)):
            j_child_indices = []
            for child in parent_gen[j]['children']:
                for child_index, tent_child in enumerate(child_gen):
                    if child['id'] == tent_child['id']:
                        j_child_indices.append(child_index)
                        break
            # Get list of children for each
            # Check pairs of children
            for i_child_idx in i_child_indices:
                for j_child_idx in j_child_indices:
                    # If afterchild has index less than child, increment count
                    if j_child_idx < i_child_idx:
                        count+=1
    return count

def CountTentativeParentGenOverlaps(parent_gen, child_gen, i1, i2):
    count = 0

    # Look at every parent i
    for i in range(len(parent_gen) - 1):
        i_child_indices = []
        for child in parent_gen[i]['children']:
            for child_index, tent_child in enumerate(child_gen):
                if child['id'] == tent_child['id']:
                    i_child_indices.append(child_index)
                    break
        # Look at every after-sibling j
        for j in range(i+1, len(parent_gen)):
            j_child_indices = []
            for child in parent_gen[j]['children']:
                for child_index, tent_child in enumerate(child_gen):
                    if child['id'] == tent_child['id']:
                        j_child_indices.append(child_index)
                        break
            # Get list of children for each
            # Check pairs of children
            for i_child_idx in i_child_indices:
                if i1 == i_child_idx: i_child_idx = i2
                for j_child_idx in j_child_indices:
                    if i2 == j_child_idx: j_child_idx = i1
                    # If afterchild has index less than child, increment count
                    if j_child_idx < i_child_idx:
                        count+=1
    return count

def FindAllY(nodes, largest_gen):
    generations = [[] for _ in range(largest_gen+1)]
    for node in nodes:
        gen = node['generation']
        generations[gen].append(node)

    offset = 1
    for passes in range(100):
        offset *= -1
        print("--")
        subject = list(enumerate(generations))
        if offset < 0:
            subject = subject[1:]
        else:
            subject = reversed(subject)
        for gen_index, _ in subject:
            print(f"{gen_index=}")
            i = 0
            for i in     range(0,   len(_) - 1):
                for j in range(i+1, len(_)):
                    foo = max(gen_index, gen_index + offset)
                    this_gen  = generations[foo]
                    bar = min(gen_index, gen_index + offset)
                    print(f"{foo=}, {bar=}")
                    other_gen = generations[bar]
                    overlaps  = CountParentGenOverlaps         (other_gen, this_gen)
                    tentative = CountTentativeParentGenOverlaps(other_gen, this_gen, i, j)
                    if tentative <= overlaps:
                        generations[gen_index][i], generations[gen_index][j] = generations[gen_index][j], generations[gen_index][i]
                        # print("swapped")
    
    AssignSlots(generations)

def AssignSlots(generations):
    for gen in generations:
        for i, node in enumerate(gen):
            node['slot'] = i

def MakeNodes(y) -> list:
    new_nodes = []
    gen_to_sibling_count = {}
    for node in y['nodes']:
        # gen = FindGenerationUp(y, node['id'])
        node["type"] = "file"
        node["file"] = node["name"] + ".md"
        node["width"] = NODE_WIDTH
        node["height"] = NODE_HEIGHT
        # node["generation"] = gen

        new_nodes.append(node)

    

    # largest_gen = 0
    # for node in new_nodes:
    #     if node['generation'] == None:
    #             node['generation'] = FindGenerationDown(y, node['id'])
    #     gen = node['generation']
    #     largest_gen = max(gen, largest_gen)
    #     # print('gen', gen, node['name'])

    #     if gen not in gen_to_sibling_count:
    #         gen_to_sibling_count[gen] = -1
    #     gen_to_sibling_count[gen] += 1
        
    #     node["slot"] = gen_to_sibling_count[gen]
    #     node["x"] = gen * (NODE_WIDTH + NODE_SPACING_W)

    return new_nodes#, largest_gen

def MakeEdges(y) -> list:
    edges = []

    for link in y['links']:
        new_edge = {
            "id": hex(abs(hash(str(link))))[2:],
            "fromNode": FindNodeWithId(y, link['from'])['id'],
            "fromSide": "right",
            "toNode": FindNodeWithId(y, link['to'])['id'],
            "toSide": "left",
            "toEnd": "none", # ?
            "label": link['text'] # Optional
        }
        edges.append(new_edge)

    return edges

def ApplyAllY(nodes):
    for node in nodes:
        node["y"] = node['slot'] * (NODE_HEIGHT + NODE_SPACING_H)

def MakeCanvas(y):
    nodes = MakeNodes(y)
    edges = MakeEdges(y)
    # FindAllY(nodes, largest_gen)
    # ApplyAllY(nodes)

    for node in nodes:
        if "children" in node: node.pop("children")
        if "parents" in node: node.pop("parents")
        if "name" in node: node.pop("name")
        if "generation" in node: node.pop("generation")
        if "slot" in node: node.pop("slot")
        if "header" in node: node.pop("header")
        if "footer" in node: node.pop("footer")
        if "year" in node: node.pop("year")
        if "image" in node: node.pop("image")

        # if "Berserk" in node['name']:
        #     print("poop")
        #     node["name"] = "poop"

    json.dump(
        {
            'nodes' : nodes,
            'edges' : edges
        },
        open('auto.canvas', 'w')
    )
    print("Done")

def GetDotInput(y) -> str:
    s = 'digraph G{overlap=false;splines=true;rank="LR";concentrate=true;'

    for node in y['nodes']:
        s += f'{node["id"]};'

    for link in y['links']:
        f = link['from']
        t = link['to']
        s += f'{f}->{t};'

    s += "}"
    return bytes(s, 'utf8')

if __name__ == "__main__":
    yam = Preprocess()
    pipe = GetDotInput(yam)
    pipe = subprocess.run(
        "tred",
        input=pipe,
        capture_output=True
    ).stdout
    pipe = subprocess.run(
        ["dot", "-Tjson"],
        input=pipe,
        capture_output=True
    ).stdout

    positions = {}
    objects = json.loads(pipe)['objects']
    
    UNIT_SCALE = 7000/470
    X_SCALE = 4400/4240
    Y_SCALE = 1/1
    for o in objects:
        id = o['name']
        y, x = o['pos'].split(',')
        x, y = -400*int(X_SCALE*UNIT_SCALE*float(x)/400), 400*int(Y_SCALE*UNIT_SCALE*float(y)/400)
        # print(name, x, y, sep=',')
        for node in yam['nodes']:
            if node['id'] == id:
                node['x'] = x
                node['y'] = y

    # cat gv.txt | tred | dot -Tjson | code -
    # MakeMarkdown(yam)
    MakeCanvas(yam)
