#!/usr/bin/env python3

import subprocess
import argparse
from os import path

class Command:
    def __init__(self, args, post=lambda x: x):
        self.args = args
        self.post = post

    def run(self):
        self.post(subprocess.call(self.args))

    def shell_command(self):
        """Render command as input for the shell."""
        # TODO: fix this naive version by adding quotes where appropriate
        return " ".join(self.args)

class CommandGroup:
    def __init__(self, cmds):
        self.cmds = cmds

    def run(self):
        for cmd in self.cmds:
            cmd.run()

    def shell_command(self):
        return "\n".join(cmd.shell_command() for cmd in self.cmds)

class PostprocessCommand(Command):
    def __init__(self, args, post):
        super(PostprocessCommand, self).__init__(args)
        self.post = post

    def run(self):
        out = subprocess.check_output(self.args)
        print(self.post(out))

class Cantor:
    @classmethod
    def start(cls, _args):
        return Command(["cantor-start"])

    @classmethod
    def stop(cls, _args):
        return Command(["VBoxManage", "controlvm", "Arch",
                        "savestate"])

    @classmethod
    def poweroff(cls, _args):
        return Command(["VBoxManage", "controlvm", "Arch",
                        "poweroff"])

    @classmethod
    def checkpoint(cls, _args):
        return Command(["arch-checkpoint"])

    @classmethod
    def update(cls, _args):
        return Command(["update-cantor.sh"])

    @classmethod
    def running(cls, _args):
        def find_arch(status):
            if status.find(b"Arch") > 0:
                return "hamilton is running"
            else:
                return "hamilton is stopped"
        return PostprocessCommand(["VBoxManage", "list", "runningvms"], find_arch)

    @classmethod
    def setup_parser(cls, parser: argparse.ArgumentParser):
        subparsers = parser.add_subparsers()
        subparsers.add_parser('start').set_defaults(func=cls.start)
        subparsers.add_parser('stop').set_defaults(func=cls.stop)
        subparsers.add_parser('poweroff').set_defaults(func=cls.poweroff)
        subparsers.add_parser('running').set_defaults(func=cls.running)
        subparsers.add_parser('checkpoint').set_defaults(func=cls.checkpoint)
        subparsers.add_parser('update').set_defaults(func=cls.update)


class Book:
    @classmethod
    def search(cls, args):
        url = "https://bpl.bibliocommons.com/search?custom_query=title:({}) formatcode:(EBOOK)&suppress=true&custom_edit=true&search_scope=BPL".format(args.title)
        return Command(["open", url])

    @classmethod
    def setup_parser(cls, parser: argparse.ArgumentParser):
        parser.add_argument("title")
        parser.set_defaults(func=cls.search)


def remove_prefix(prefix, text):
    if text.startswith(prefix):
        return text[len(prefix):]
    return text


class MitBook:
    @classmethod
    def search(cls, args):
        title = args.title.lower()
        #title = remove_prefix("the ", title)
        #title = remove_prefix("a ", title)
        url = "http://library.mit.edu/F/?func=find-a&find_code=WTI&request={}&local_base=PBOOKS".format(title)
        return Command(["open", url])

    @classmethod
    def setup_parser(cls, parser: argparse.ArgumentParser):
        parser.add_argument("title")
        parser.set_defaults(func=cls.search)


class Audiobook:
    @classmethod
    def search(cls, args):
        url = "https://bpl.bibliocommons.com/search?custom_query=title:({}) (formatcode:(AB_ONLINE) OR formatcode:(EBOOK) OR formatcode:(AB))&suppress=true&custom_edit=true&search_scope=BPL".format(args.title)
        return Command(["open", url])

    @classmethod
    def setup_parser(cls, parser: argparse.ArgumentParser):
        parser.add_argument("title")
        parser.set_defaults(func=cls.search)


class Timer:
    @classmethod
    def open_timer(cls, _args):
        url = "https://jon.thesquareplanet.com/time/"
        return Command(["open", "-a", "Safari", url])

    @classmethod
    def setup_parser(cls, parser: argparse.ArgumentParser):
        parser.set_defaults(func=cls.open_timer)


class YoutubeDl:
    @classmethod
    def youtube_dl(cls, _args):
        return Command(["/Users/tchajed/Downloads/youtube-dl/download-song.sh"])

    @classmethod
    def setup_parser(cls, parser: argparse.ArgumentParser):
        parser.set_defaults(func=cls.youtube_dl)


class Comics:
    @classmethod
    def save_smbc(cls, args):
        return Command(["/Users/tchajed/dropbox/save/comics/smbc/add-comic.py", args.name])

    @classmethod
    def setup_parser(cls, parser: argparse.ArgumentParser):
        parser.add_argument("name")
        parser.set_defaults(func=cls.save_smbc)


class Unmount:
    @classmethod
    def unmount_command(cls, volume):
        volume_path = path.join("/Volumes",  volume)
        if not path.isdir(volume_path):
            return Command(["echo", f"{volume} is not mounted"])
        else:
            return Command(["diskutil", "unmount", volume_path])

    @classmethod
    def unmount_volume(cls, args):
        return cls.unmount_command(args.volume)

    @classmethod
    def setup_parser(cls, parser: argparse.ArgumentParser):
        parser.add_argument("volume")
        parser.set_defaults(func=cls.unmount_volume)


class Fermat:
    @classmethod
    def unmount(cls, args):
        return Unmount.unmount_command("fermat")

    @classmethod
    def setup_parser(cls, parser: argparse.ArgumentParser):
        subparsers = parser.add_subparsers()
        subparsers.add_parser('unmount').set_defaults(func=cls.unmount)


class Brouwer:
    @classmethod
    def unmount(cls, args):
        return CommandGroup([
            Unmount.unmount_command("brouwer"),
            Unmount.unmount_command("poincare"),
            Unmount.unmount_command("borel"),
            ])

    @classmethod
    def setup_parser(cls, parser: argparse.ArgumentParser):
        subparsers = parser.add_subparsers()
        subparsers.add_parser('unmount').set_defaults(func=cls.unmount)


class Noether:
    @classmethod
    def connect(cls, args):
        return Command(["osascript", path.expanduser("~/.dotfiles/applescripts/connect-noether.scpt")])

    @classmethod
    def disconnect(cls, args):
        return Command(["osascript", path.expanduser("~/.dotfiles/applescripts/disconnect-noether.scpt")])

    @classmethod
    def setup_parser(cls, parser: argparse.ArgumentParser):
        subparsers = parser.add_subparsers()
        subparsers.add_parser('connect').set_defaults(func=cls.connect)
        subparsers.add_parser('disconnect').set_defaults(func=cls.disconnect)


if __name__ == "__main__":
    parser = argparse.ArgumentParser(
        formatter_class=argparse.ArgumentDefaultsHelpFormatter)
    parser.add_argument("-n", "--dry-run",
                        action='store_true',
                        help="print commands without executing them")
    subparsers = parser.add_subparsers(help="sub-commands")

    Cantor.setup_parser(
        subparsers.add_parser('cantor', help="manage cantor VM"))

    Cantor.setup_parser(
        subparsers.add_parser('hamilton', help="manage hamilton VM"))

    Book.setup_parser(
        subparsers.add_parser('book', help="search for books at the BPL"))

    MitBook.setup_parser(
        subparsers.add_parser('mitlib', help="search for books in the MIT libraries"))

    Audiobook.setup_parser(
        subparsers.add_parser('audiobook', help="search for streaming audiobooks at the BPL"))

    Timer.setup_parser(
        subparsers.add_parser('timer', help="open a Safari timer"))

    YoutubeDl.setup_parser(
        subparsers.add_parser('youtube-dl', help="download YouTube URL song from clipboard"))

    Comics.setup_parser(
        subparsers.add_parser('smbc', help="save SMBC comic"))

    Unmount.setup_parser(
        subparsers.add_parser('unmount', help="unmount disks"))

    Fermat.setup_parser(
        subparsers.add_parser('fermat', help="manage Fermat disk"))

    Brouwer.setup_parser(
        subparsers.add_parser('brouwer', help="manage Brouwer disk"))

    Noether.setup_parser(
        subparsers.add_parser('noether', help="manage noether headphones"))

    args = parser.parse_args()
    cmd = args.func(args)
    if args.dry_run:
        print(cmd.shell_command())
    else:
        cmd.run()
