#!/data/data/com.termux/files/usr/bin/python3 -u

import argparse
import hashlib
import os
import re
import sys
import time
import urllib.parse
import urllib.request
from pathlib import Path
from tqdm import tqdm

FREEDOS_DEFAULT_MIRROR = 'https://www.ibiblio.org/pub/micro/pc-stuff/freedos/files'
FREEDOS11_URL = FREEDOS_DEFAULT_MIRROR + '/repositories/1.1'
FREEDOS12_URL = FREEDOS_DEFAULT_MIRROR + '/repositories/1.2'
FREEDOS13_URL = FREEDOS_DEFAULT_MIRROR + '/repositories/1.3'
FREEDOS14_URL = FREEDOS_DEFAULT_MIRROR + '/repositories/1.4'

FREEDOS_USERSPACE_TOOLS = [ 'assign', 'attrib', 'choice', 'comp', 'debug', 'devload', 'display', 'edit', 'edlin', 'fc',
                    'fdxms', 'find', 'format', 'label', 'mem', 'mode', 'nansi', 'share', 'sort', 'swsubst', 'tree', 'xcopy']
# currently unused
FREEDOS_ARCHIVES_EXTRA = ['defrag', 'deltree', 'diskcomp', 'diskcopy', 'exe2bin', 'more', 'move', 'replace', 'shsucdx']

FREEDOS_APPS = ['dn2']
FREEDOS_BOOT = ['kernel', 'freecom']
FREEDOS_HELP = ['htmlhelp']
FREEDOS_UNIXLIKE_ARCHIVES = ['touch', 'gnused', 'grep', 'head', 'less', 'tee', 'which']
FREEDOS_UTIL = ['wcd', 'dos32a']
FREEDOS_NET = ['curl', 'gopherus', 'links', 'ping', 'terminal']
FREEDOS_SOUND_ARCHIVES = ['dosmid', 'adplay', 'opencp']
FREEDOS14_SOUND_ARCHIVES = ['sbpmixer', 'playcd', 'sjgplay']
FREEDOS_DEVEL_ARCHIVES = ['bwbasic']

freedos12_boot = [ FREEDOS12_URL + '/base/' + x + '.zip' for x in FREEDOS_BOOT + FREEDOS_HELP ]
freedos12_userspace = [ FREEDOS12_URL + '/apps/' + x + '.zip' for x in FREEDOS_APPS ]
freedos12_userspace += [ FREEDOS12_URL + '/base/' + x + '.zip' for x in FREEDOS_USERSPACE_TOOLS + FREEDOS_HELP ]
freedos12_userspace += [ FREEDOS12_URL + '/unix/' + x + '.zip' for x in FREEDOS_UNIXLIKE_ARCHIVES ]
freedos12_userspace += [ FREEDOS12_URL + '/util/' + x + '.zip' for x in FREEDOS_UTIL ]
freedos12_userspace += [ FREEDOS12_URL + '/net/' + x + '.zip' for x in FREEDOS_NET ]
freedos12_userspace += [ FREEDOS12_URL + '/sound/' + x + '.zip' for x in FREEDOS_SOUND_ARCHIVES ]
freedos12_userspace += [ FREEDOS12_URL + '/devel/' + x + '.zip' for x in FREEDOS_DEVEL_ARCHIVES ]
freedos12_pkginfo = [ FREEDOS12_URL + '/pkg-info/' + x.split('/')[-1].removesuffix('zip') + 'txt'
        for x in freedos12_boot + freedos12_userspace ]

freedos13_boot = [ FREEDOS13_URL + '/base/' + x + '.zip' for x in FREEDOS_BOOT ]
freedos13_userspace = [ FREEDOS13_URL + '/apps/' + x + '.zip' for x in FREEDOS_APPS ]
freedos13_userspace += [ FREEDOS13_URL + '/base/' + x + '.zip' for x in FREEDOS_USERSPACE_TOOLS + FREEDOS_HELP]
freedos13_userspace += [ FREEDOS13_URL + '/unix/' + x + '.zip' for x in FREEDOS_UNIXLIKE_ARCHIVES ]
freedos13_userspace += [ FREEDOS13_URL + '/util/' + x + '.zip' for x in FREEDOS_UTIL ]
freedos13_userspace += [ FREEDOS13_URL + '/net/' + x + '.zip' for x in FREEDOS_NET ]
freedos13_userspace += [ FREEDOS13_URL + '/sound/' + x + '.zip' for x in FREEDOS_SOUND_ARCHIVES ]
freedos13_userspace += [ FREEDOS13_URL + '/devel/' + x + '.zip' for x in FREEDOS_DEVEL_ARCHIVES ]
freedos13_pkginfo = [ FREEDOS13_URL + '/pkg-info/' + x.split('/')[-1].removesuffix('zip') + 'txt'
        for x in freedos13_boot + freedos13_userspace ]

freedos14_boot = [ FREEDOS14_URL + '/base/' + x + '.zip' for x in FREEDOS_BOOT ]
freedos14_userspace = [ FREEDOS14_URL + '/apps/' + x + '.zip' for x in FREEDOS_APPS ]
freedos14_userspace += [ FREEDOS14_URL + '/base/' + x + '.zip' for x in FREEDOS_USERSPACE_TOOLS + FREEDOS_HELP]
freedos14_userspace += [ FREEDOS14_URL + '/unix/' + x + '.zip' for x in FREEDOS_UNIXLIKE_ARCHIVES ]
freedos14_userspace += [ FREEDOS14_URL + '/util/' + x + '.zip' for x in FREEDOS_UTIL ]
freedos14_userspace += [ FREEDOS14_URL + '/net/' + x + '.zip' for x in FREEDOS_NET ]
freedos14_userspace += [ FREEDOS14_URL + '/sound/' + x + '.zip' for x in FREEDOS_SOUND_ARCHIVES + FREEDOS14_SOUND_ARCHIVES]
freedos14_userspace += [ FREEDOS14_URL + '/devel/' + x + '.zip' for x in FREEDOS_DEVEL_ARCHIVES ]
freedos14_pkginfo = [ FREEDOS14_URL + '/pkg-info/' + x.split('/')[-1].removesuffix('zip') + 'txt'
        for x in freedos14_boot + freedos14_userspace ]

tmp = os.getenv("TMPDIR")
if tmp is None:
    tmp = '/tmp'
TMP_DIR = os.path.join(tmp, os.path.basename(sys.argv[0]) + '-' + str(os.getpid()))

def check_sum_pkg_info(destination_file):
    pkg_info_file = open(destination_file[:-3] + 'txt', 'r')
    unchecked = True
    for line in pkg_info_file.readlines():
        if line.startswith('SHA'):
            assert_sha256sum(destination_file, line[4:].strip())
            unchecked = False
            break
    if unchecked:
        print('Warning no SHA checksum in pkg-info. Could not verify checksum of ' + destination_file)

def check_sum(destination_file):
    if Path(destination_file[:-3] + 'txt').is_file():
        check_sum_pkg_info(destination_file)
    else:
        print('Warning could not verify checksum of ' + destination_file)

def download_file(source, destination_directory):
    destination_file = os.path.join(destination_directory, urllib.parse.unquote(source.split("/")[-1]))
    if Path(destination_file).is_file():
        if not destination_file.endswith('.txt'):
            check_sum(destination_file)
    else:
        print('Downloading ' + source + '...')
        with urllib.request.urlopen(urllib.parse.quote_plus(source, r'\./_-:')) as response, open(destination_file, 'wb') as f:
            if hasattr(response, 'headers'):
                length = response.headers['Content-length']
            elif hasattr(response, 'getheader'):
                length = response.getheader('content-length')
            else:
                length = None
            if type(length) is str:
                with tqdm(total=int(length), unit='B', maxinterval=0.5) as pbar:
                    while True:
                        chunk = response.read(16384)
                        if not chunk:
                            break
                        f.write(chunk)
                        pbar.update(len(chunk))
            else:
                f.write(response.read())
    return destination_file

def calculate_sha256sum(filename):
    with open(filename,"rb") as f:
        return hashlib.sha256(f.read()).hexdigest();

def assert_sha256sum(file, sha256sum):
    val = calculate_sha256sum(file)
    if val == sha256sum:
        print('Verified checksum ' + str(file))
    else:
        print('The downloaded file ' + str(file) + ' could not be verified.')
        print('Actual   SHA256SUM: ' + val)
        print('Expected SHA256SUM: ' + sha256sum)
        print('Please remove the file and rerun the script.')
        sys.exit(1)

def download_files(imgurls, destination):
    destination_files = []
    for imgurl in imgurls:
        destination_files.append(download_file(imgurl, destination))
    return destination_files

def verify_noexistinginstall(destination):
    if os.listdir(destination) != []:
        print('There is a already an existing set of DOS installation files in ' + destination + '.')
        sys.exit(1)

def derive_url_list(imgurl):
    imgurls = []
    filename = urllib.parse.unquote(imgurl.split("/")[-1])
    if filename.casefold().find("disk") != -1:
        if filename.casefold().find("of") != -1:
            imgcnt = int(re.findall(r'(?i)disk\s?\d+\s?of\s(\d+)', filename)[0])
            for i in range(1, imgcnt+1):
                imgurls.append(imgurl.rsplit("/", 1)[0] + "/" + re.sub(r'(?i)(?P<one>disk\s?)\d+(?P<two>\s?of\s\d+)', r'\g<one>'+str(i) + r'\g<two>', filename))
        else: # if the filenames have just one number, assume the last disk from the set was provided
            imgcnt = int(re.findall(r'(?i)disk\s?(\d+)', filename)[0])
            for i in range(1, imgcnt+1):
                imgurls.append(imgurl.rsplit("/", 1)[0] + "/" + re.sub(r'(?i)(?P<one>disk\s?)\d+', r'\g<one>'+str(i), filename))
    else:
        imgurls.append(imgurl)
    return imgurls;

def download_dos(dos_flavour, destination):
    os.makedirs(destination, exist_ok=True)

    print('Downloading checksums')
    info = []
    if dos_flavour.startswith("freedos14"):
        info = freedos14_pkginfo
    elif dos_flavour.startswith("freedos13"):
        info = freedos13_pkginfo
    elif dos_flavour.startswith("freedos12"):
        info = freedos12_pkginfo
    for pkg in info:
        try:
            download_file(pkg, destination)
        except urllib.error.HTTPError:
            pass

    print('Downloading FreeDOS...')
    pkgs = []
    if dos_flavour in ['freedos14userspace']:
        pkgs += freedos14_userspace
    if dos_flavour in ['freedos14']:
        pkgs += freedos14_boot
        pkgs += freedos14_userspace
    if dos_flavour in ['freedos13userspace']:
        pkgs += freedos13_userspace
    if dos_flavour in ['freedos13']:
        pkgs += freedos13_boot
        pkgs += freedos13_userspace
    if dos_flavour in ['freedos12userspace']:
        pkgs += freedos12_userspace
    if dos_flavour in ['freedos12']:
        pkgs += freedos12_boot
        pkgs += freedos12_userspace
    for pkg in pkgs:
        try:
            download_file(pkg, destination)
        except urllib.error.HTTPError:
            print('WARNING: The chosen version of FreeDOS does not contain ' + pkg + '!')

    print('Downloading done')

def urls_ok(urls, verbose=False, timeout=10, retries=3, retry_delay=1):
    failures = 0

    def test_url(url):
        for attempt in range(1, retries + 1):
            try:
                req = urllib.request.Request(url, method="HEAD")
                resp = urllib.request.urlopen(req, timeout=timeout)
                resp.read()
                if verbose:
                    print("INFO: Checked (%s)" % url)
                return True
            except urllib.error.HTTPError:
                print("FAIL: Does not exist (%s)" % url)
                return False
            except Exception as e:
                if attempt == retries:
                    print("FAIL: Error on attempt %d: %s (%s)" % (attempt, e, url))
                    return False
                if verbose:
                    print("WARN: Attempt %d failed for %s, retrying..." % (attempt, url))
                time.sleep(retry_delay)

    for url in urls:
        if not test_url(url):
            failures += 1

    return failures == 0

parser = argparse.ArgumentParser(description="""Script to download either FreeDOS or a set of disk images.
Multiple versions of FreeDOS are preconfigured.
When a custom URL to a disk image is provided, based on known filename patterns, all related images are downloaded and extracted.
Either a name should contain \"Disk ? of x\" or \"diskx\" where 1 is the first disk and x is the last disk.
The destination directory will contain all files from all disk images.""")
group = parser.add_mutually_exclusive_group()
group.add_argument("-l", "--list", help="List available DOS variants", action="store_true")
group.add_argument("-t", "--test", help="Test that URLs exist", action="store_true")
group.add_argument("-o", "--os", help="Download the specified DOS", type=str)
parser.add_argument("-d", "--destination", help="Specify/override the destination directory", type=str)
parser.add_argument("-s", "--sha256sum", help="Specify one or more sha256sum(s)", nargs='+', type=str)

args = parser.parse_args()
if args.list:
    print('freedos14 FreeDOS 1.4 (2025)')
    print('freedos14userspace FreeDOS 1.4 userspace (2025)')
    print('freedos13 FreeDOS 1.3 (2022)')
    print('freedos13userspace FreeDOS 1.3 userspace (2022)')
    print('freedos12 FreeDOS 1.2 (2016)')
    print('freedos12userspace FreeDOS 1.2 userspace (2016)')
    sys.exit(0)

if args.test:
    urls = (
            freedos12_boot + freedos12_userspace +
            freedos13_boot + freedos13_userspace +
            freedos14_boot + freedos14_userspace
           )
    if not urls_ok(urls, verbose=True, timeout=20):
        sys.exit(1)
    sys.exit(0)

if args.os:
    dos_to_download = args.os
    if args.destination:
        destination = args.destination
    else:
        destination = os.path.join(os.environ['HOME'], '.cache', 'dosemu', args.os.replace('userspace', ''))
    download_dos(dos_to_download, destination)
