#!/usr/bin/env python3
import os
import sys
import shutil
import subprocess
import urllib.request
from pathlib import Path, PurePath, PureWindowsPath
from zipfile import ZipFile
from argparse import ArgumentParser

import yaml
from joblib import Parallel, delayed

EXTRACT_DIR = 'extract'
EXPECTED_DIR = 'expected'
OUTPUT_DIR = 'output'
CMD_BASE = ['-iwad', 'miniwad.wad', '-nodraw', '-noblit', '-nosound', '-nogui']

def download(url):
    response = urllib.request.urlopen(url)
    with open('tmp', 'wb') as stream:
        stream.write(response.read())
    print('downloaded ' + url)
    return 'tmp'

def extract(zipname, filename):
    with ZipFile(zipname, 'r') as zf:
        with zf.open(filename) as stream:
            with open(Path(EXTRACT_DIR, filename), 'wb') as out:
                out.write(stream.read())

def download_and_extract(record):
    if not Path(EXTRACT_DIR, record['wad']).exists():
        zipname = download(record['wad_url'])
        extract(zipname, record['wad'])
        if 'deh' in record:
            extract(zipname, record['deh'])
        os.remove(zipname)

    if not Path(EXTRACT_DIR, record['demo']).exists():
        zipname = download(record['demo_url'])
        extract(zipname, record['demo'])
        os.remove(zipname)

def build_command_line(record):
    cmd = []
    cmd += CMD_BASE
    cmd += ['-file', record['wad']]
    if 'deh' in record:
        cmd += ['-deh', record['deh']]
    if 'gameversion' in record:
        cmd += ['-gameversion', record['gameversion']]
    cmd += ['-timedemo', record['demo']]
    if 'statdump' in record:
        cmd += ['-statdump', Path(OUTPUT_DIR, record['statdump'])]
    if 'levelstat' in record:
        cmd += ['-levelstat']
    return cmd

def call_port(source_port, record):
    cmd = [source_port] + build_command_line(record)

    if 'levelstat' in record:
        base_dir = record['levelstat']
        Path(base_dir).mkdir(exist_ok=True)

        subprocess.run(cmd, cwd=base_dir, capture_output=True)

        shutil.copyfile(Path(base_dir, 'levelstat.txt'),
                        Path(OUTPUT_DIR, record['levelstat']))
        shutil.rmtree(base_dir)
    else:
        subprocess.run(cmd, capture_output=True)

def compare_output(record):
    if 'levelstat' in record:
        name = record['levelstat']
    else:
        name = record['statdump']

    cmd = ['diff', '-w', Path(EXPECTED_DIR, name), Path(OUTPUT_DIR, name)]
    proc = subprocess.run(cmd, capture_output=True, text=True)
    if proc.stdout == '':
        return False
    print("name: " + name)
    sys.stdout.writelines(proc.stdout)
    return True

def run_program(args):
    source_port = PurePath(args.source_port)
    if type(source_port) is PureWindowsPath:
        source_port = source_port.with_suffix('.exe')
    source_port = Path(source_port).resolve()
    if not source_port.exists():
        sys.exit("Doom port is not found.")

    with open('config.yml', 'r') as stream:
        config = yaml.safe_load(stream)

    extract('miniwad.zip', 'miniwad.wad')

    for record in config:
        download_and_extract(record)

    os.environ['SDL_VIDEODRIVER'] = 'dummy'
    os.environ['DOOMWADDIR'] = str(Path(Path().resolve(), EXTRACT_DIR))

    Parallel(n_jobs=args.jobs)(delayed(call_port)(source_port, record) for record in config)

    differecies = False

    for record in config:
        if compare_output(record):
            differecies = True

    if differecies:
        sys.exit(1)
    else:
        print("Success.")

if __name__ == "__main__":
    parser = ArgumentParser(description="Execute demos for Doom port in a batch.")
    parser.add_argument('--jobs', dest='jobs', default=1, type=int, help="Set the number of jobs.")
    parser.add_argument('--port', dest='source_port', default="doom", type=str, help="Path to Doom port.")
    args = parser.parse_args()
    run_program(args)
