#!/usr/bin/env python3

import os
import stat
import sys

import gitignore_parser


paths = sys.argv[1:] or ["."]

colors = dict(
    [
        entry.split("=", maxsplit=1)
        for entry in (
            os.environ.get("_3_COLORS")
            or os.environ.get("TREE_COLORS")
            or os.environ.get("LS_COLORS")
            or ":no=00:rs=0:fi=00:di=01;34:ln=01;36:pi=40;33:so=01;35:bd=40;33;01:cd=40;33;01:or=40;31;01:ex=01;32:*.bat=01;32:*.BAT=01;32:*.btm=01;32:*.BTM=01;32:*.cmd=01;32:*.CMD=01;32:*.com=01;32:*.COM=01;32:*.dll=01;32:*.DLL=01;32:*.exe=01;32:*.EXE=01;32:*.arj=01;31:*.bz2=01;31:*.deb=01;31:*.gz=01;31:*.lzh=01;31:*.rpm=01;31:*.tar=01;31:*.taz=01;31:*.tb2=01;31:*.tbz2=01;31:*.tbz=01;31:*.tgz=01;31:*.tz2=01;31:*.z=01;31:*.Z=01;31:*.zip=01;31:*.ZIP=01;31:*.zoo=01;31:*.asf=01;35:*.ASF=01;35:*.avi=01;35:*.AVI=01;35:*.bmp=01;35:*.BMP=01;35:*.flac=01;35:*.FLAC=01;35:*.gif=01;35:*.GIF=01;35:*.jpg=01;35:*.JPG=01;35:*.jpeg=01;35:*.JPEG=01;35:*.m2a=01;35:*.M2a=01;35:*.m2v=01;35:*.M2V=01;35:*.mov=01;35:*.MOV=01;35:*.mp3=01;35:*.MP3=01;35:*.mpeg=01;35:*.MPEG=01;35:*.mpg=01;35:*.MPG=01;35:*.ogg=01;35:*.OGG=01;35:*.ppm=01;35:*.rm=01;35:*.RM=01;35:*.tga=01;35:*.TGA=01;35:*.tif=01;35:*.TIF=01;35:*.wav=01;35:*.WAV=01;35:*.wmv=01;35:*.WMV=01;35:*.xbm=01;35:*.xpm=01;35:"
        ).split(":")[1:-1]
    ]
)


def color(path, is_link):
    try:
        mode = os.stat(path, follow_symlinks=False).st_mode
        type = stat.S_IFMT(mode)

    except FileNotFoundError:
        if is_link:
            return "or"
        else:
            return "mi"

    if type == stat.S_IFIFO:
        return "fi"
    elif type == stat.S_IFCHR:
        return "cd"
    elif type == stat.S_IFDIR:
        if mode & stat.S_ISVTX:
            return "tw" if mode & stat.S_IWOTH else "st"
        elif mode & stat.S_IWOTH:
            return "ow"
        else:
            return "di"
    elif type == stat.S_IFBLK:
        return "bd"
    elif type == stat.S_IFLNK:
        return "ln"
    elif type == stat.S_IFDOOR:
        return "do"
    elif type == stat.S_IFSOCK:
        return "so"
    elif type == stat.S_IFREG:
        ext = f"*{os.path.splitext(path)[1]}"
        if mode & stat.S_ISUID:
            return "su"
        elif mode & stat.S_ISGID:
            return "sg"
        elif mode & (stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH):
            return "ex"
        elif ext in colors:
            return ext
        else:
            return "fi"
    else:
        return "no"


def apply_color(name, path, is_link):
    name_color = color(path, is_link)
    if name_color in colors:
        return f"\033[{colors[name_color]}m{name}\033[0m"
    else:
        return name


def process_path(path, prefix="", gitignores=[]):
    children = sorted(os.listdir(path), key=lambda name: (name.lower(), name))

    if ".gitignore" in children:
        gitignores = gitignores + [gitignore_parser.parse_gitignore(os.path.join(path, ".gitignore"))]

    for i, name in enumerate(children):
        sub_path = os.path.join(path, name)

        is_link = os.path.islink(sub_path)
        if is_link:
            link_destination = os.readlink(sub_path)
            link_path = os.path.join(path, link_destination)
            link_suffix = f" -> {apply_color(link_destination, link_path, False)}"

        else:
            link_path = sub_path
            link_suffix = ""

        if name != ".git" and ("FIXME" and is_link or all((not gitignore(sub_path) for gitignore in gitignores))):
            sticks = "├──" if i != len(children) - 1 else "└──"
            print(f"{prefix}{sticks}", f"{apply_color(name, link_path, is_link)}{link_suffix}")

            if not is_link and os.path.isdir(sub_path):
                sticks = "│   " if i != len(children) - 1 else "    "
                process_path(sub_path, prefix=f"{prefix}{sticks}", gitignores=gitignores)
                statistics["directories"] += 1

            else:
                statistics["files"] += 1


def get_parents(path):
    while True:
        new_path = os.path.join(path, "..")
        if not os.path.samefile(path, new_path):
            yield new_path
            path = new_path
        else:
            break


statistics = {"directories": 0, "files": 0}
for path in paths:
    gitignores = []
    try:
        for parent in get_parents(path):
            gitignore_path = os.path.join(parent, ".gitignore")
            if os.path.isfile(gitignore_path):
                gitignore = gitignore_parser.parse_gitignore(gitignore_path)
                if not gitignore(path):
                    gitignores.append(gitignore)
                else:
                    break

    except FileNotFoundError:
        """FIXME gitignore_parser shall not fail on wrong link destinations"""
        print(apply_color(path, path, False), "[error opening dir]")

    else:
        print(apply_color(path, path, False))
        process_path(path, gitignores=gitignores)

print()
print(", ".join((f"{v} {k}" for k, v in statistics.items())))
