#!/usr/bin/python3
# Copyright 2024 Gentoo Authors
# Distributed under the terms of the GNU General Public License v2
#
# Loosely inspired by Ubuntu/Debian's command-not-found
# (c) Zygmunt Krynicki 2005, 2006, 2007, 2008


import os
import shutil
import subprocess
import sys


def similar_words(word):
    """
    return a set with spelling1 distance alternative spellings

    based on http://norvig.com/spell-correct.html
    """
    alphabet = 'abcdefghijklmnopqrstuvwxyz-_0123456789'
    s = [(word[:i], word[i:]) for i in range(len(word) + 1)]
    deletes = [a + b[1:] for a, b in s if b]
    transposes = [a + b[1] + b[0] + b[2:] for a, b in s if len(b) > 1]
    replaces = [a + c + b[1:] for a, b in s for c in alphabet if b]
    inserts = [a + c + b for a, b in s for c in alphabet]
    return set(deletes + transposes + replaces + inserts)


def main():
    if len(sys.argv) < 2:
        print("Need at least one argument")
        return

    cmd = sys.argv[1]

    # Sanity check to avoid spamming pfl with nonsense. A valid command
    # contains letters and may contain numbers, but it may not contain only
    # numbers. ".-_" in commands are allowed, but not at the begining or end.
    allow_list = ".-_"
    allowed_char_at_illegal_pos = False
    cmd_cleaned = cmd
    for char in allow_list:
        cmd_cleaned = cmd_cleaned.replace(char, "")
        if cmd.startswith(char) or cmd.endswith(char):
            allowed_char_at_illegal_pos = True

    if (
            not cmd_cleaned.isalnum() or
            cmd_cleaned.isnumeric() or
            allowed_char_at_illegal_pos
            ):
        print(f"\nCommand \"{cmd}\" does not look like a valid command\n")
        return

    snap = "/usr/bin/snap"
    if os.path.exists(snap):
        try:
            output = subprocess.check_output(
                    [snap, "advise-snap", "--command", cmd],
                    stderr=subprocess.DEVNULL,
                    universal_newlines=True)
        except subprocess.CalledProcessError:
            print(f"\nCommand \"{cmd}\" not found\n\n")
        else:
            print(output)
    else:
        print(f"\nCommand \"{cmd}\" not found\n\n")

    spelling_candidates = []
    for word in similar_words(cmd):
        if shutil.which(word) is not None:
            spelling_candidates += [word]

    if len(spelling_candidates) > 0:
        print("Maybe you meant one of these commands:")
        for candidate in spelling_candidates:
            print("\t- " + candidate)
        print("\n")

    paths = [
            "/bin",
            "/usr/bin",
            "/sbin",
            "/usr/sbin",
            "/opt/bin"
        ]

    try:
        # Trying using e_file as module directly (app-portage/pfl>3.4)
        import pfl.e_file
    except ModuleNotFoundError:
        # If that doesn't work call it via subprocess instead (app-portage/pfl<=3.4)
        e_file = "/usr/bin/e-file"
        if os.path.exists(e_file):
            print(f"Searching the Portage File List for packages installing {cmd}...\n")
            found = False
            for path in paths:
                x = path + "/" + cmd
                try:
                    output = subprocess.check_output(
                            [e_file, x],
                            stderr=subprocess.DEVNULL,
                            universal_newlines=True)
                except subprocess.CalledProcessError:
                    # Unfortunatly there is no distinction in error code between fail and not found
                    pass
                else:
                    found = True
                    print(output)
            if found:
                print("")
            else:
                print("No candidates found\n\n")
    else:
        print(f"Searching the Portage File List for packages installing {cmd}...\n")
        found = False
        for path in paths:
            x = path + "/" + cmd
            try:
                options = {
                    'file': x,
                    'outputPlain': False,
                }
                ret, out = pfl.e_file.run(options)
            except SystemExit:
                # If something is not working just quit
                break
            else:
                if ret == 0:
                    found = True
                    sys.stdout.write(out)
        if found:
            print("")
        else:
            print("No candidates found\n\n")

if __name__ == "__main__":
    try:
        main()
    except KeyboardInterrupt as e:
        pass

        
