#!/usr/bin/env python

import argparse
import os
import shutil
import sys


def err(msg, die=None):
    """Print an error message and exits if an exit code is given"""
    sys.stderr.write(msg + "\n")
    if die:
        sys.exit(die if isinstance(die, int) else 1)


try:
    import pelican
except ImportError:
    err(
        "Cannot import pelican.\nYou must "
        "install Pelican in order to run this script.",
        -1,
    )


global _THEMES_PATH
_THEMES_PATH = os.path.join(
    os.path.dirname(os.path.abspath(pelican.__file__)), "themes"
)

__version__ = "0.2"
_BUILTIN_THEMES = ["simple", "notmyidea"]


def main():
    """Main function"""

    parser = argparse.ArgumentParser(description="""Install themes for Pelican""")

    excl = parser.add_mutually_exclusive_group()
    excl.add_argument(
        "-l",
        "--list",
        dest="action",
        action="store_const",
        const="list",
        help="Show the themes already installed and exit",
    )
    excl.add_argument(
        "-p",
        "--path",
        dest="action",
        action="store_const",
        const="path",
        help="Show the themes path and exit",
    )
    excl.add_argument(
        "-V",
        "--version",
        action="version",
        version=f"pelican-themes v{__version__}",
        help="Print the version of this script",
    )

    parser.add_argument(
        "-i",
        "--install",
        dest="to_install",
        nargs="+",
        metavar="theme path",
        help="The themes to install",
    )
    parser.add_argument(
        "-r",
        "--remove",
        dest="to_remove",
        nargs="+",
        metavar="theme name",
        help="The themes to remove",
    )
    parser.add_argument(
        "-U",
        "--upgrade",
        dest="to_upgrade",
        nargs="+",
        metavar="theme path",
        help="The themes to upgrade",
    )
    parser.add_argument(
        "-s",
        "--symlink",
        dest="to_symlink",
        nargs="+",
        metavar="theme path",
        help="Same as `--install', but create a symbolic link instead of "
        "copying the theme. Useful for theme development",
    )
    parser.add_argument(
        "-c",
        "--clean",
        dest="clean",
        action="store_true",
        help="Remove the broken symbolic links of the theme path",
    )

    parser.add_argument(
        "-v", "--verbose", dest="verbose", action="store_true", help="Verbose output"
    )

    args = parser.parse_args()

    to_install = args.to_install or args.to_upgrade
    to_sym = args.to_symlink or args.clean

    if args.action:
        if args.action == "list":
            list_themes(args.verbose)
        elif args.action == "path":
            print(_THEMES_PATH)
    elif to_install or args.to_remove or to_sym:
        if args.to_remove:
            if args.verbose:
                print("Removing themes...")

            for i in args.to_remove:
                remove(i, v=args.verbose)

        if args.to_install:
            if args.verbose:
                print("Installing themes...")

            for i in args.to_install:
                install(i, v=args.verbose)

        if args.to_upgrade:
            if args.verbose:
                print("Upgrading themes...")

            for i in args.to_upgrade:
                install(i, v=args.verbose, u=True)

        if args.to_symlink:
            if args.verbose:
                print("Linking themes...")

            for i in args.to_symlink:
                symlink(i, v=args.verbose)

        if args.clean:
            if args.verbose:
                print("Cleaning the themes directory...")

            clean(v=args.verbose)
    else:
        print("No argument given... exiting.")


def themes():
    """Returns the list of the themes"""
    for i in os.listdir(_THEMES_PATH):
        e = os.path.join(_THEMES_PATH, i)

        if os.path.isdir(e):
            if os.path.islink(e):
                yield (e, os.readlink(e))
            else:
                yield (e, None)


def list_themes(v=False):
    """Display the list of the themes"""
    for theme_path, link_target in themes():
        if not v:
            theme_path = os.path.basename(theme_path)
        if link_target:
            if v:
                print(theme_path + (" (symbolic link to `" + link_target + "')"))
            else:
                print(theme_path + "@")
        else:
            print(theme_path)


def remove(theme_name, v=False):
    """Removes a theme"""

    theme_name = theme_name.replace("/", "")
    target = os.path.join(_THEMES_PATH, theme_name)

    if theme_name in _BUILTIN_THEMES:
        err(
            theme_name + " is a builtin theme.\n"
            "You cannot remove a builtin theme with this script, "
            "remove it by hand if you want."
        )
    elif os.path.islink(target):
        if v:
            print("Removing link `" + target + "'")
        os.remove(target)
    elif os.path.isdir(target):
        if v:
            print("Removing directory `" + target + "'")
        shutil.rmtree(target)
    elif os.path.exists(target):
        err(target + " : not a valid theme")
    else:
        err(target + " : no such file or directory")


def install(path, v=False, u=False):
    """Installs a theme"""
    if not os.path.exists(path):
        err(path + " : no such file or directory")
    elif not os.path.isdir(path):
        err(path + " : not a directory")
    else:
        theme_name = os.path.basename(os.path.normpath(path))
        theme_path = os.path.join(_THEMES_PATH, theme_name)
        exists = os.path.exists(theme_path)
        if exists and not u:
            err(path + " : already exists")
        elif exists:
            remove(theme_name, v)
            install(path, v)
        else:
            if v:
                print(f"Copying '{path}' to '{theme_path}' ...")
            try:
                shutil.copytree(path, theme_path)

                try:
                    if os.name == "posix":
                        for root, dirs, files in os.walk(theme_path):
                            for d in dirs:
                                dname = os.path.join(root, d)
                                os.chmod(dname, 493)  # 0o755
                            for f in files:
                                fname = os.path.join(root, f)
                                os.chmod(fname, 420)  # 0o644
                except OSError as e:
                    err(
                        "Cannot change permissions of files "
                        "or directory in `{r}':\n{e}".format(r=theme_path, e=str(e)),
                        die=False,
                    )
            except Exception as e:
                err(
                    "Cannot copy `{p}' to `{t}':\n{e}".format(
                        p=path, t=theme_path, e=str(e)
                    )
                )


def symlink(path, v=False):
    """Symbolically link a theme"""
    if not os.path.exists(path):
        err(path + " : no such file or directory")
    elif not os.path.isdir(path):
        err(path + " : not a directory")
    else:
        theme_name = os.path.basename(os.path.normpath(path))
        theme_path = os.path.join(_THEMES_PATH, theme_name)
        if os.path.exists(theme_path):
            err(path + " : already exists")
        else:
            if v:
                print(f"Linking `{path}' to `{theme_path}' ...")
            try:
                os.symlink(path, theme_path)
            except Exception as e:
                err(
                    "Cannot link `{p}' to `{t}':\n{e}".format(
                        p=path, t=theme_path, e=str(e)
                    )
                )


def is_broken_link(path):
    """Returns True if the path given as is a broken symlink"""
    path = os.readlink(path)
    return not os.path.exists(path)


def clean(v=False):
    """Removes the broken symbolic links"""
    c = 0
    for path in os.listdir(_THEMES_PATH):
        path = os.path.join(_THEMES_PATH, path)
        if os.path.islink(path) and is_broken_link(path):
            if v:
                print(f"Removing {path}")
            try:
                os.remove(path)
            except OSError:
                print(f"Error: cannot remove {path}")
            else:
                c += 1

    print(f"\nRemoved {c} broken links")
