from subprocess import Popen, PIPE
from os import listdir, environ
from os.path import isdir, islink
from posixpath import join
import sys

# The script is supplied with the followig command line arguments
# 1) the repository root - used only for file-joining to an absolute path
#    the actual working directory is set in emacs
# 2) `treemacs-max-git-entries`
# 3) `treemacs-git-command-pipe`
# 4) a list of expanded directories the script may recurse into to collect
#    an entry for every untracked/ignored file inside
#    this list is turned into a set since it is possible that it contains duplicates
#    when called for magit, see also `treemacs-magit--extended-git-mode-update`

GIT_ROOT     = str.encode(sys.argv[1])
LIMIT        = int(sys.argv[2])
GIT_CMD      = "git status --porcelain --ignored . " + sys.argv[3]
STDOUT       = sys.stdout.buffer
RECURSE_DIRS = set([str.encode(it[(len(GIT_ROOT)):]) + b"/" for it in sys.argv[4:]]) if len(sys.argv) > 4 else []
QUOTE        = b'"'
output       = []
ht_size      = 0

def find_recursive_entries(path, state):
    global output, ht_size
    for item in listdir(path):
        full_path = join(path, item)
        output.append(full_path + QUOTE + QUOTE + state)
        ht_size += 1
        if ht_size > LIMIT:
            break
        if isdir(full_path) and not islink(full_path):
            find_recursive_entries(full_path, state)

def main():
    global output, ht_size
    # Don't lock Git when updating status.
    environ["GIT_OPTIONAL_LOCKS"] = "0"
    proc = Popen(GIT_CMD, shell=True, stdout=PIPE, bufsize=100)
    dirs_added = {}

    for item in proc.stdout:
        # remove final newline
        item = item[:-1]

        # remove leading space if item was e.g. modified only in the worktree
        if item.startswith(b' '):
            item = item[1:]
        state, filename = item.split(b' ', 1)

        # reduce the state to a single-letter-string
        state = state[0:1]

        # sometimes git outputs quoted filesnames
        if filename.startswith(b'"'):
            filename = filename[1:-1]

        # find the absolute path for the current item
        # renames have the form STATE OLDNAME -> NEWNAME
        abs_path = None
        if state == b"R":
            abs_path = join(GIT_ROOT, filename.split(b' -> ')[1])
        else:
            abs_path = join(GIT_ROOT, filename.lstrip())

        # filename is a directory, final slash must be removed
        if abs_path.endswith(b'/'):
            abs_path = abs_path[:-1]
            dirs_added[abs_path] = True
        output.append(abs_path + QUOTE + QUOTE + state)
        ht_size += 1

        # for files deeper down in the file hierarchy also print all their directories
        # if /A/B/C/x is changed then /A and /A/B and /A/B/C must be shown as changed as well
        if b'/' in filename and state != b'!':
            name_parts = filename.split(b'/')[:-1]
            dirname = b''
            for name_part in name_parts:
                dirname = join(dirname, name_part)
                full_dirname = join(GIT_ROOT, dirname.lstrip())
                # directories should not be printed more than once, which would happen if
                # e.g. both /A/B/C/x and /A/B/C/y have changes
                if full_dirname not in dirs_added:
                    output.append(full_dirname + QUOTE + QUOTE + b'M')
                    ht_size += 1
                    dirs_added[full_dirname] = True
        # for untracked and ignored directories we need to find an entry for every single file
        # they contain
        # however this applies only for directories that are expanded and whose content is visible
        if state in [b'?', b'!'] and isdir(abs_path):
            if filename in RECURSE_DIRS:
                find_recursive_entries(abs_path, state)
        if ht_size >= LIMIT:
            break
    STDOUT.write(
        b"#s(hash-table size " + \
        bytes(str(ht_size), 'utf-8') + \
        b" test equal rehash-size 1.5 rehash-threshold 0.8125 data ("
    )
    if ht_size > 0:
      STDOUT.write(QUOTE + (QUOTE + QUOTE).join(output) + QUOTE)
    STDOUT.write( b"))")

    sys.exit(proc.poll())

main()
