#!/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):
        self.conf = ruamel.yaml.safe_load(  # type: ignore
            Path("configs.yml").text()
        )
        self.base_dir = Path.getcwd()
        self.home = Path("~").expanduser()
        self.force = force

    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()
            else:
                ui.info_2("Skipping", pretty_dest)
                return

        ui.info_2("Cloning", url, "->", pretty_dest)
        git_command = ["git", "clone", url, p_dest, "--branch", branch]
        subprocess.check_call(git_command)

    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, args: List[str]) -> None:
        """Run a command."""

        command = [x.format(home=self.home) for x in args]
        ui.info_2("Running", "`{}`".format(" ".join(command)))
        subprocess.check_call(command)

    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(self.home):
            src_full = p_src
        else:
            src_full = self.base_dir / "dotfiles" / p_src

        src_full.symlink(p_dest)

    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",
    )

    args = parser.parse_args()

    force = args.force
    programs = args.programs

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


if __name__ == "__main__":
    main()