"""Verify the downloaded source package, optionally prepare a new Linux build tree.
No dependencies are installed and no compiler/build command is run.
Usage: python prepare_sources.py --verify-only
       python prepare_sources.py NEW_OUTPUT_DIRECTORY
"""
import argparse, hashlib, json, pathlib, shutil, tarfile, zipfile

here = pathlib.Path(__file__).resolve().parent
manifest = json.loads((here/'distribution-manifest.json').read_text())
prefix = pathlib.PurePosixPath('sources/ffmpeg/core-0.12.10-9f57947a5bd530d8')
parser=argparse.ArgumentParser(description=__doc__)
parser.add_argument('output',nargs='?');parser.add_argument('--verify-only',action='store_true')
args=parser.parse_args()
if not args.verify_only and not args.output: parser.error('Choose --verify-only or a new output directory')

archives=[]
for component in manifest['components']:
    parts=[];combined=hashlib.sha256()
    for source in component['source']:
        rel=pathlib.PurePosixPath(source['file']).relative_to(prefix)
        if '..' in rel.parts:raise ValueError('Unsafe source path')
        file=here/pathlib.Path(*rel.parts);data=file.read_bytes()
        if len(data)!=source['bytes'] or hashlib.sha256(data).hexdigest()!=source['sha256']:raise ValueError('Source file integrity mismatch: '+str(rel))
        combined.update(data);parts.append(file)
    if combined.hexdigest()!=component['archive']['sha256']:raise ValueError('Archive reassembly mismatch')
    archives.append((component,parts))
print('Verified '+str(len(archives))+' complete source archives and every distributed part.')
if args.verify_only:raise SystemExit(0)

output=pathlib.Path(args.output).resolve()
if output.exists():raise ValueError('Output must not exist; no files will be overwritten')
output.mkdir(parents=True)
cache=output/'verified-archives';cache.mkdir()
unpack=output/'unpacked';unpack.mkdir()
roots={}
for component,parts in archives:
    archive=cache/component['archive']['name']
    with archive.open('xb') as dest:
        for part in parts:
            with part.open('rb') as source:shutil.copyfileobj(source,dest)
    destination=unpack/component['repository'].replace('/','-');destination.mkdir()
    if archive.suffix=='.zip':
        with zipfile.ZipFile(archive) as z:
            for info in z.infolist():
                p=pathlib.PurePosixPath(info.filename)
                if p.is_absolute() or '..' in p.parts:raise ValueError('Unsafe zip path')
                if info.is_dir():continue
                target=destination/pathlib.Path(*p.parts);target.parent.mkdir(parents=True,exist_ok=True)
                target.write_bytes(z.read(info.filename))
    else:
        with tarfile.open(archive,'r:gz') as tar:tar.extractall(destination,filter='data')
    children=list(destination.iterdir())
    if len(children)!=1 or not children[0].is_dir():raise ValueError('Expected one source root')
    roots[component['repository']]=children[0]

build=output/'build';shutil.copytree(roots['ffmpegwasm/ffmpeg.wasm'],build,symlinks=True)
for repository,root in roots.items():
    if repository!='ffmpegwasm/ffmpeg.wasm':shutil.copytree(root,build/'source-inputs'/repository.replace('/','-'),symlinks=True)
shutil.copyfile(here/'Dockerfile.locked',build/'Dockerfile')
shutil.copyfile(here/'Dockerfile.locked.CHANGES.txt',build/'Dockerfile.locked.CHANGES.txt')
print('Prepared source tree: '+str(build))
print('No build executed. In a suitable Linux/Docker environment, cd to that tree and run make prd.')
