#!/usr/bin/python3

# pylint: disable=bad-continuation
# pylint and pycodestyle appear to have different ideas of what identation should be.

"""
Assemble a *ix process hierarchy from ps and output with indentation.

Today, most folks will just use Linux' ps axf.
Also, the BSD's don't appear to support something like ps -o.
"""

import os
import pwd
import typing
import collections

# this time it's O(n) :)


class Psentry:
    """Class to hold one ps entry."""

    def __init__(self, euid: str, pid: str, ppid: str, command: str) -> None:
        # pylint: disable=too-many-arguments
        """Initialize."""
        self.pid = int(pid)
        self.ppid = int(ppid)
        try:
            pwent = pwd.getpwuid(int(euid))
        except KeyError:
            self.user = str(euid)
        self.user = pwent[0]
        self.command = command
        self.children = []  # type: typing.List[Psentry]

    def __str__(self) -> str:
        """Convert to string."""
        return '%s %s %s %s' % (self.pid, self.ppid, self.user, self.command)

    __repr__ = __str__

    def fill_out_tree(self,
                      ps_entries: 'typing.Dict[int, Psentry]',
                      pid_to_ppid: typing.Dict[int, int],
                      ppid_to_pids: typing.Dict[int, typing.List[int]]
                      ) -> None:
        """Build the tree."""
        for child_pid in ppid_to_pids[self.pid]:
            child_psentry = ps_entries[child_pid]
            self.children.append(child_psentry)
            child_psentry.fill_out_tree(ps_entries, pid_to_ppid, ppid_to_pids)

    def hierarchy_as_string(self, depth: int = 0) -> str:
        """Format the tree as a string for output."""
        result = '%s%s\n' % (' ' * depth * 2, str(self))
        for child in self.children:
            result += child.hierarchy_as_string(depth+1)
        return result


def get_least_pid_psentry(psentries: typing.List[Psentry]) -> Psentry:
    """Find the lowest pid in psentries."""
    min_tuple = min((psentry.pid, psentry) for psentry in psentries)
    return min_tuple[1]


def main() -> None:
    """Assemble the tree and print it."""
    pipe = os.popen('ps -eo euid,pid,ppid,command', 'r')
    psentries_list = []
    for line in pipe:
        fields = line.split()
        if fields[0] == 'EUID':
            continue
        euid = fields[0]
        pid = fields[1]
        ppid = fields[2]
        # This truncates commands at 100 characters - it's much more readable that way.
        command = ' '.join(fields[3:])[:100]
        psentry = Psentry(euid, pid, ppid, command)
        psentries_list.append(psentry)
    pipe.close()

    least_entry = get_least_pid_psentry(psentries_list)

    pid_to_ppid = {}
    ppid_to_pids = collections.defaultdict(list)  # type: typing.DefaultDict[int, typing.List[int]]
    ps_entries = {}
    for psentry in psentries_list:
        pid_to_ppid[psentry.pid] = psentry.ppid
        ppid_to_pids[psentry.ppid].append(psentry.pid)
        ps_entries[psentry.pid] = psentry

    psentries_tree = least_entry
    psentries_tree.fill_out_tree(ps_entries, pid_to_ppid, ppid_to_pids)

    print(psentries_tree.hierarchy_as_string())


main()