#!/usr/bin/python3

import argparse
import json
import os
import shutil

from xdg.BaseDirectory import save_data_path

STATEFILE = 'dflinked.json'
COPYFILES = [
    'data/index'
]
LIBFILES = {
    'df': 'df',
    'Dwarf_Fortress': 'libs/Dwarf_Fortress',
}

def parse_arguments():
    parser = argparse.ArgumentParser(
        description='Create a dwarf fortress runtime directory'
    )
    parser.add_argument('target',
                        metavar='target',
                        help='The directory to fill with df content',
    )
    parser.add_argument('libdir',
                        metavar='libdir',
                        help='The directory to take game executables from',
    )
    parser.add_argument('datadirs',
                        metavar='datadir',
                        help='The directories to take data from',
                        nargs='+',
    )
    args = parser.parse_args()
    return args

def link_libfiles(libdir, rundir):
    for source_name, target_local in LIBFILES.items():
        source_path = os.path.join(libdir, source_name)
        target_full = os.path.join(rundir, target_local)
        target_dir = os.path.dirname(target_full)
        os.makedirs(target_dir, exist_ok=True)
        source_abs = os.path.abspath(source_path)
        try:
            os.symlink(source_abs, target_full)
        except FileExistsError:
            # If the file already exists, its either a the symlink
            # left by dflink or was probably left in another state
            # deliberately, so we just do nothing.
            print("Not overwriting {}".format(target_full))
        else:
            print("Symlink: {} => {}".format(source_abs, target_full))

def compile_links(datadirs):
    link_map = {}
    for datadir in datadirs:
        for (curdir, dirs, files) in os.walk(datadir, followlinks=True):
            assert curdir.startswith(datadir)
            for file in files:
                source_file = os.path.join(curdir, file)
                target = os.path.relpath(source_file, datadir)
                if target not in link_map:
                    link_map[target] = os.path.abspath(source_file)
    return link_map

def link_data(link_map, rundir):
    for target, source in link_map.items():
        target_full = os.path.join(rundir, target)
        target_dir = os.path.dirname(target_full)
        os.makedirs(target_dir, exist_ok=True)
        source_abs = os.path.abspath(source)
        try:
            os.symlink(source_abs, target_full)
        except FileExistsError:
            if os.path.islink(target_full):
                current_link = os.readlink(target_full)
                if current_link == source_abs:
                    print("{} is already pointing to {}".format(target_full, source_abs))
                else:
                    os.unlink(target_full)
                    os.symlink(source_abs, target_full)
                    print("Changed symlink at {} from {} to {}".format(target_full, current_link, source_abs))
            else:
                print("Not touching {}, because it is not a symlink.".format(target_full))
        else:
            print("Symlink: {} => {}".format(source_abs, target_full))

def clean_data(old_link_map, link_map, rundir):
    for link, target in old_link_map.items():
        link_path = os.path.join(rundir, link)
        if link not in link_map and os.path.islink(link_path) and os.readlink(link_path) == target:
            os.unlink(link_path)
            print("rm: {}".format(link_path))

def copy_data(datadirs, rundir):
    for copyfile in COPYFILES:
        target = os.path.join(rundir, copyfile)
        if not os.path.exists(target):
            os.makedirs(os.path.dirname(target), exist_ok=True)
            found = False
            for datadir in datadirs:
                source = os.path.join(datadir, copyfile)
                if os.path.exists(source):
                    shutil.copyfile(source, target)
                    print('Copy: {} -> {}'.format(source, target))
                    found = True
                    break
            if not found:
                raise RuntimeError('No candidate for copyfile {} found'.format(copyfile))
        else:
            print('{} already exists'.format(target))

def update_data(datadirs, target):
    state_path = os.path.join(target, STATEFILE)
    link_map = compile_links(datadirs)
    try:
        with open(state_path, 'r') as file:
            old_link_map = json.load(file)
    except FileNotFoundError:
        old_link_map = {}
    copy_data(datadirs, target)
    clean_data(old_link_map, link_map, target)
    link_data(link_map, target)
    with open(state_path, 'w') as file:
        json.dump(link_map, file)

def dflink(datadirs, libdir, target):
    link_libfiles(libdir, target)
    update_data(datadirs, target)

def main():
    args = parse_arguments()
    dflink(args.datadirs,
           args.libdir,
           args.target)

if __name__ == '__main__':
    main()
