#!/usr/bin/python3

"""Main installer file.

Shamelessly copied from https://github.com/dmerejkowsky/dotfiles.
"""

import argparse
import subprocess
from typing import List, Optional
from urllib.request import urlretrieve

import cli_ui as ui  # type: ignore
from path import Path  # type: ignore
import ruamel.yaml


class Installer:
    """Regroups all the installation methods listed in the yaml conf file."""

    def __init__(self, force: bool = False, update: bool = False):
        self.conf = ruamel.yaml.safe_load(  # type: ignore
            Path("configs.yml").text()
        )
        self.base_dir = Path.getcwd()
        self.home = Path("~").expanduser()
        self.force = force
        self.update = update

    def pretty_path(self, path: Path) -> str:
        """Put the ~/ back in the path."""

        relpath: str = path.relpath(self.home)
        return "~/" + relpath

    def do_clone(self, url: str, dest: str, branch: str = "master") -> None:
        """Do the git clone."""

        p_dest = Path(dest).expanduser()
        pretty_dest = self.pretty_path(p_dest)
        p_dest.parent.makedirs_p()

        if p_dest.exists():
            if self.force:
                ui.info_2("Removing", pretty_dest)
                p_dest.rmtree()
            elif self.update:
                ui.info_2("Updating", pretty_dest)
                self.do_run("cd {} && git pull".format(p_dest))
                return
            else:
                ui.info_2("Skipping", pretty_dest)
                return

        ui.info_2("Cloning", url, "->", pretty_dest)
        git_command = "git clone {} {} --branch {}".format(url, p_dest, branch)
        subprocess.run(git_command, check=True)

    def do_copy(self, src: str, dest: str) -> None:
        """Copy two files."""

        p_dest = Path(dest).expanduser()
        pretty_dest = self.pretty_path(p_dest)
        p_dest.parent.makedirs_p()

        if p_dest.exists() and not self.force:
            ui.info_2("Skipping", pretty_dest)
            return

        p_src: Path = self.base_dir / "dotfiles" / src
        ui.info_2("Copy", p_src, "->", self.pretty_path(p_src))
        p_src.copy(p_dest)

    def do_download(
            self, *, url: str, dest: str, executable: bool = False
    ) -> None:
        """Retrieve a file from a url."""

        p_dest = Path(dest).expanduser()
        pretty_dest = self.pretty_path(p_dest)
        p_dest.parent.makedirs_p()

        if p_dest.exists() and not self.force:
            ui.info_2("Skipping", pretty_dest)
        else:
            ui.info_2("Fetching", url, "->", pretty_dest)
            urlretrieve(url, p_dest)

        if executable:
            p_dest.chmod(0o755)

    def do_run(self, command: str) -> None:
        """Run a command."""

        ui.info_2("Running", "`{}`".format(command))
        subprocess.run(command, check=True, shell=True)
        self.base_dir.chdir()

    def do_symlink(self, src: str, dest: str) -> None:
        """Make a symlink to a file."""

        self._do_symlink(src, dest, is_dir=False)

    def do_symlink_dir(self, src: str, dest: str) -> None:
        """Make a symlink to a dir."""

        self._do_symlink(src, dest, is_dir=True)

    def _do_symlink(self, src: str, dest: str, *, is_dir: bool) -> None:
        p_src = Path(src).expanduser()
        pretty_src = self.pretty_path(p_src)
        p_dest = Path(dest).expanduser()
        pretty_dest = self.pretty_path(p_dest)

        if is_dir:
            p_dest.parent.parent.makedirs_p()
        else:
            p_dest.parent.makedirs_p()

        if p_dest.exists() and not self.force:
            ui.info_2("Skipping", pretty_dest)
            return

        if p_dest.islink():
            p_dest.remove()

        ui.info_2("Symlink", pretty_dest, "->", pretty_src)

        if p_src.startswith("/"):
            src_full = p_src
        else:
            src_full = self.base_dir / "dotfiles" / p_src

        src_full.symlink(p_dest)

    def do_update(self, command: str) -> None:
        """Run a command only if self.update is True."""

        if self.update:
            self.do_run(command)
        else:
            ui.info_2("Skipping", "`{}`".format(command))

    def do_write(self, dest: str, content: str) -> None:
        """Write into a file."""

        p_dest = Path(dest).expanduser()
        pretty_dest = self.pretty_path(p_dest)

        if p_dest.exists() and not self.force:
            ui.info_2("Skipping", pretty_dest)
            return

        ui.info_2("Creating", pretty_dest)
        p_dest.parent.makedirs_p()
        content = content.format(base_dir=self.base_dir, home=self.home)

        if not content.endswith("\n"):
            content += "\n"

        p_dest.write_text(content)

    def install(self, programs: Optional[List[str]] = None) -> None:
        """Install the programs provided.

        If no program is provided, use the conf file to find the programs.
        """

        if not programs:
            programs = sorted(self.conf.keys())
        for program in programs:
            self.install_program(program)

    def install_program(self, program: str) -> None:
        """Install one program (called by self.install()).

        Call indivdual methods from the conf file.
        """

        ui.info(ui.green, program)
        ui.info("-" * len(program))
        todo = self.conf[program]
        for action in todo:
            name = list(action.keys())[0]
            params = action[name]
            func = getattr(self, "do_{}".format(name))
            if isinstance(params, dict):
                func(**params)
            else:
                func(*params)
        ui.info()


def main() -> None:
    """Parse args and instantiate the Installer."""

    parser = argparse.ArgumentParser()
    parser.add_argument(
        "programs",
        nargs="*",
    )
    parser.add_argument(
        "--force",
        action="store_true",
        help="Overwrite existing files",
    )
    parser.add_argument(
        "--update",
        action="store_true",
        help="Update git repos",
    )

    args = parser.parse_args()

    programs = args.programs
    force = args.force
    update = args.update

    installer = Installer(force=force, update=update)
    installer.install(programs=programs)


if __name__ == "__main__":
    main()