#!/usr/bin/env python3

import sys, os, random, string, datetime
from pathlib import Path

# Get dotfiles path (fallback: ~/.dotfiles)
DOTFILES = Path(os.environ.get(
    "DOTFILES",
    os.environ["HOME"] + "/.dotfiles",
)).absolute()

# Get the path for the configuration files
DOTS_CONFIG = DOTFILES / "config/dots/sysmrc.py"
LOCAL_CONFIG = Path(os.environ["HOME"] + "/storage") / "config/sysmrc.py"

class Manager:
    def __init__(self):
        # initial platform checks
        self.is_android = False
        self.is_linux = sys.platform.startswith("linux")

        # check if platform is android
        if self.is_linux:
            for folder in os.environ["PATH"].split(":"):
                if len(list(Path(folder).glob("termux-*"))) > 0:
                    self.is_android = True

        # constantly attempt to generate backup paths until it works
        backup_path = self._generate_backup_path()
        while backup_path.exists():
            backup_path = self._generate_backup_path()

        self.backup_path = backup_path

    @staticmethod
    def _generate_backup_path():
        """Generates a backup path."""
        return (
            Path(
                os.environ.get(
                    "XDG_CACHE_HOME",
                    os.environ["HOME"] + "/.cache",
                )
            )
            / "sysm-backups"
            / "{}-{}".format(
                datetime.datetime.now().strftime("%Y-%m-%d"),
                generate_random_string(5),
            )
        )

    def make_dir(self, dir_path):
        assert not dir_path.is_file()
        Path(dir_path).mkdir(parents=True, exist_ok=True)

    def link_glob(self, origin, new):
        """Creates a folder called `new`, where it is created one symlink for each of the files inside the folder `origin`"""
        origin_path = Path(origin).expanduser()
        new_path = Path(new).expanduser()

        assert origin_path.is_dir()

        # backup new_path if it's already a file or broken symlink
        if new_path.is_file() or (new_path.is_symlink() and not new_path.resolve().exists()):
            self.backup(new_path)

        # symlink everything from origin_path to new_path
        for origin_file in origin_path.glob("*"):
            origin_head = origin_file.relative_to(origin_path)
            self.link_file(origin_file, new_path / origin_head)

    def link_file(self, origin, new):
        """Creates a symlink file called `new`, pointing to the already-existing file `origin`."""
        origin_path = Path(origin).expanduser()
        new_path = Path(new).expanduser()

        assert origin_path.exists()

        # cover existing files and symlinks (broken or not)
        if new_path.is_symlink() and new_path.resolve() == origin_path:
            # new_path already points to origin_path - do nothing
            do_backup = False
            do_symlink = False
        elif new_path.is_symlink() and new_path.resolve() != origin_path:
            # new_path is a symlink and points elsewhere - backup it and then make the symlink
            do_backup = True
            do_symlink = True
        elif new_path.exists():
            # new_path already exists - backup it and then make the symlink
            do_backup = True
            do_symlink = True
        else:
            # new_path doesn't exist - make the symlink
            do_backup = False
            do_symlink = True

        if do_backup:
            eprint(f"sysm: {new_path} already exists; backing up...")
            self.backup(new_path)

        if do_symlink:
            self.make_dir(new_path.parent)
            eprint(f"sysm: making symlink at: {new_path}")
            eprint(f"      points to: {origin_path}")
            new_path.symlink_to(origin_path)


    def link_conf(self, origin, new):
        """An alias to `self.link_file`, which adds a DOTFILES/config prefix to `origin`."""
        self.link_file(DOTFILES / "config" / origin, new)

    def backup(self, path):
        print(self.backup_path)
        to_backup = self.backup_path / str(path.absolute())[1:]
        self.make_dir(to_backup.parent)
        eprint(f"sysm: backing up file: {path}")
        eprint(f"      moved to: {to_backup}")
        path.rename(to_backup)

def usage(args):
    eprint(f"Usage: {args[0]}")
    exit(1)

def main(args):
    argc = len(args) - 1

    if argc != 0:
        eprint("too many arguments")
        usage(args)

    m = Manager()

    for config in {DOTS_CONFIG, LOCAL_CONFIG}:
        if config.is_file():
            eprint(f"sysm: sourcing: {config}")
            with config.open() as f:
                exec(f.read(), {}, {
                    "m": m,
                    "DOTFILES": DOTFILES,
                })
        else:
            eprint(f"sysm: skipping (not found): {config}")

def generate_random_string(size):
    return str.join("", [random.choice(string.ascii_lowercase) for i in range(size)])

def eprint(*args, **kwargs):
    print(*args, file=sys.stderr, **kwargs)

if __name__ == "__main__":
    main(sys.argv)
