import subprocess
import sys
import os
import hashlib
import argparse
import common as c

# List of Ninja build targets
NINJA_BUILD_TARGETS = [ ['src/data/bin1', 'out/forest_1st.arc', 'dump/forest_1st.arc'], ['src/data/bin2', 'out/forest_2nd.arc', 'dump/forest_2nd.arc'] ]

def calculate_directory_hash(path, hash_func):
    if not os.path.isdir(path):
        raise NotADirectoryError(f"{path} is not a directory")
    
    hasher = hash_func()
    for root, dirs, files in os.walk(path):
        for names in files:
            filepath = os.path.join(root, names)
            try:
                with open(filepath, 'rb') as f:
                    while True:
                        data = f.read(65536)  # Read in chunks to handle large files
                        if not data:
                            break
                        hasher.update(data)
            except IOError:
                # Handle errors as needed
                pass
    return hasher.hexdigest()

def directory_changed(path, build_dir, hash_func=hashlib.md5):
    hash_file = os.path.join(build_dir, f'{os.path.basename(os.path.normpath(path))}.dirhash')
    current_hash = calculate_directory_hash(path, hash_func)
    try:
        with open(hash_file, 'r') as f:
            stored_hash = f.read()
    except FileNotFoundError:
        stored_hash = None

    if current_hash != stored_hash:
        os.makedirs(build_dir, exist_ok=True)
        with open(hash_file, 'w') as f:
            f.write(current_hash)
        return True
    return False

def run_ninja_build(target):
    try:
        subprocess.run(['ninja', '-v', target], check=True)
    except subprocess.CalledProcessError as e:
        print(f"Error running Ninja build for target {target}: {e}")
        sys.exit(1)

def check_and_dump_arc(target, dump, verbose=False):
    if not os.path.exists(target):
        assert os.path.exists(dump), f"Please add missing file: {dump}"
        print(f'Dumping {dump}')

        try:
            if verbose:
                subprocess.run(['python3', 'tools/arc_tool.py', dump, os.path.dirname(target)])
            else:
                subprocess.run(['python3', 'tools/arc_tool.py', '-v', dump, os.path.dirname(target)])
        except subprocess.CalledProcessError as e:
            print(f"Error running arc_tool")
            sys.exit(1)

if __name__ == "__main__":
    parser = argparse.ArgumentParser(description='Build Animal Crossing')
    parser.add_argument('--clean', help='Cleans all build artifacts', required=False, action='store_true')
    parser.add_argument('-v', help='Enable verbose logging', required=False, action='store_true')

    args = parser.parse_args()

    if args.clean:
        for target in NINJA_BUILD_TARGETS:
            rel_path = f'{os.path.basename(os.path.normpath(target[0]))}.dirhash'
            path = os.path.join('build', rel_path)
            if os.path.exists(path):
                os.remove(path)
        try:
            subprocess.run(['ninja', '-t', 'clean'], check=True)
        except subprocess.CalledProcessError as e:
            print(f"Error running ninja -t clean")
            sys.exit(1)
    else:
        for target in NINJA_BUILD_TARGETS:
            check_and_dump_arc(target[0], target[2], args.v)
            if directory_changed(target[0], 'build'):
                if os.path.exists(target[1]):
                    os.remove(target[1])
                run_ninja_build(target[1])
            else:
                print(f"No changes in {target[0]}, skipping build.")
        try:
            if args.v:
                subprocess.run(['ninja', '-v'], check=True)
            else:
                subprocess.run(['ninja'], check=True)
                # Compress foresta.rel
                if os.path.exists(c.REL_OUT):
                    print("Compressing foresta.rel to foresta.rel.szs")
                    subprocess.call([f'./{c.ORTHRUS}', 'ncompress', 'yaz0', '-c', c.REL_OUT, c.REL_SZS_OUT])
        except subprocess.CalledProcessError as e:
            print(f"Error running Ninja build")
            sys.exit(1)
