import io
import os
import re
import sys

from ._core import Process


STAT_PPID = 3
STAT_TTY = 6

STAT_PATTERN = re.compile(r"\(.+\)|\S+")


def detect_proc():
    """Detect /proc filesystem style.

    This checks the /proc/{pid} directory for possible formats. Returns one of
    the following as str:

    * `stat`: Linux-style, i.e. ``/proc/{pid}/stat``.
    * `status`: BSD-style, i.e. ``/proc/{pid}/status``.
    """
    pid = os.getpid()
    for name in ("stat", "status"):
        if os.path.exists(os.path.join("/proc", str(pid), name)):
            return name
    raise ProcFormatError("unsupported proc format")


def _get_stat(pid, name):
    path = os.path.join("/proc", str(pid), name)
    with io.open(path, encoding="ascii", errors="replace") as f:
        # We only care about TTY and PPID -- all numbers.
        parts = STAT_PATTERN.findall(f.read())
        return parts[STAT_TTY], parts[STAT_PPID]


def _get_cmdline(pid):
    path = os.path.join("/proc", str(pid), "cmdline")
    encoding = sys.getfilesystemencoding() or "utf-8"
    with io.open(path, encoding=encoding, errors="replace") as f:
        # XXX: Command line arguments can be arbitrary byte sequences, not
        # necessarily decodable. For Shellingham's purpose, however, we don't
        # care. (pypa/pipenv#2820)
        # cmdline appends an extra NULL at the end, hence the [:-1].
        return tuple(f.read().split("\0")[:-1])


class ProcFormatError(EnvironmentError):
    pass


def get_process_mapping():
    """Try to look up the process tree via the /proc interface."""
    stat_name = detect_proc()
    self_tty = _get_stat(os.getpid(), stat_name)[0]
    processes = {}
    for pid in os.listdir("/proc"):
        if not pid.isdigit():
            continue
        try:
            tty, ppid = _get_stat(pid, stat_name)
            if tty != self_tty:
                continue
            args = _get_cmdline(pid)
            processes[pid] = Process(args=args, pid=pid, ppid=ppid)
        except IOError:
            # Process has disappeared - just ignore it.
            continue
    return processes
