'''Helper for adding source files:

Allows user to explicitly add systemverilog files that don't end in .sv using

sv@my_module.txt
v@my_verilog_module.txt
vhdl@my_vhdl_code.log

Otherwise eda.py can't know if a .txt (or .pcap, etc) file is a source file
to be part of compilation, or a file needed for simulation (.txt, .pcap, .mem
as part of a verilog $readmemh, etc)

'''

import os
from pathlib import Path
import shutil

# Ways to force files not ending in .sv to be systemverilog (for tools
# that require -sv vs Verilog-2001'''
FORCE_PREFIX_DICT = {
    # The values must match what's expected by eda.CommandDesign.add_file,
    # which are named in eda_config_defaults.yml - file_extensions:
    'sv@': 'systemverilog',
    'v@': 'verilog',
    'vhdl@': 'vhdl',
    'cpp@': 'cpp',
    'sdc@': 'synth_constraints',
    'f@': 'dotf',
    'py@' : 'python',
    'makefile@': 'makefile'
}

ALL_FORCED_PREFIXES = set(list(FORCE_PREFIX_DICT.keys()))

def get_source_file(target: str) -> (bool, str, str):
    '''Returns tuple: bool if file exists, filepath str, and optional forced file type str'''

    if '$' in target:
        target = os.path.expandvars(target)

    if os.path.isfile(target):
        # target exists as a file, return True w/ original target:
        return True, target, ''

    if '@' in target:
        for p in ALL_FORCED_PREFIXES:
            if p in target:
                # essentially removing the leading "sv@" or whatever prefix.
                fpath = ''.join(target.split(p))
                if os.path.isfile(fpath):
                    return True, fpath, FORCE_PREFIX_DICT.get(p)

    # target or fpath didn't exist, return False with the original target:
    return False, target, ''


def safe_shutil_which(path: str) -> str:
    '''Windows/WSL compatible Wrapper for shutil.which that checks for 'path':

    - path
    - path.exe
    - path.bat

    Returns full path str returned by shutil.which
    '''
    for ext in ('', '.exe', 'bat'):
        if found := shutil.which(f'{path}{ext}'):
            return found
    return ''


def get_source_files_paths(
        cmd_des_obj, minimal_root_paths: bool = False
) -> list:
    '''Given a CommandDesign object, returns list of Path objs for all source files

    If minimal_root_paths=True, attempts to return the minimal set of root paths for
    docker read-only volume mapping

    will look at members for all source files that (any member list type in cmd_des_obj.files_)
    '''

    # Iterate over lists and add to our set, we'll include cmd_des_obj.incdirs (list)
    # here as well.
    file_dirs = set()
    for name in [x for x in dir(cmd_des_obj) if x.startswith('files_')]:
        member = getattr(cmd_des_obj, name, None)
        if callable(member) or not isinstance(member, list):
            continue
        _ = [file_dirs.add(Path(x).resolve().parent) for x in member]

    # also incdirs, a path not a file, so don't use .parent
    _ = [file_dirs.add(Path(incdir).resolve()) for incdir in getattr(cmd_des_obj, 'incdirs', [])]

    if not minimal_root_paths:
        # return them all:
        return list(file_dirs)

    for x in list(file_dirs):
        # - Using list(file_dirs) as iterator as cheap copy b/c we're altering set file_dirs
        # - We don't want to add '/' as a root path, so make sure dirs have at least
        #   2 parts if we're going to trim items from file_dirs.
        # - Also this is a n^2 traversal, make sure we aren't matching ourselves (x != y)
        #   in check. Using childPath.is_relative_to(parentPath).
        if any(x != y and x.is_relative_to(y) and len(y.parts) >= 2 for y in file_dirs):
            file_dirs.remove(x)

    return list(file_dirs)


# Note that in Windows, 'python' will return the venv or uv version, 'python3' will
# return the installed version (which may not be what you want), so we'll prefer
# 'python':
PY_EXE = safe_shutil_which('python') or safe_shutil_which('python3')


def safe_output_file(work_dir: str, default_fname: str, user_fpath: str = '') -> str:
    '''Wrapper for getting a safe'ish abspath output file name, often from user supplied args

    For example:
      - user_fpath='', will use (abspath) work_dir / default_fname
      - user_fpath='results.txt', will use (abspath) work_dir prepended
      - user_fpath='./results.txt', will use (abspath) ./results.txt
           -- with safely creating the user supplied directory
    '''

    if user_fpath:
        outpath, outleaf = os.path.split(user_fpath)
    else:
        outpath, outleaf = '', ''

    assert default_fname

    if not outpath and not outleaf:
        # Use default.
        output_dir = os.path.abspath(work_dir)
        output_file = os.path.join(output_dir, default_fname)
    elif not outpath:
        output_dir = os.path.abspath(work_dir)
        output_file = os.path.join(output_dir, outleaf)
    else:
        # User supplied path + file
        output_dir = os.path.abspath(outpath)
        os.makedirs(output_dir, exist_ok=True)
        output_file = os.path.join(output_dir, outleaf)

    return output_file
