#!/usr/bin/env python
import sys
import locale

from os.path import expanduser
from optparse import OptionParser, make_option
from ConfigParser import RawConfigParser, Error

from hetzner.robot import Robot


class Options(object):
    command = None
    description = "The hetzner robot commandline interface."
    usage = "%prog [global options] command [options]"

    option_list = (
        make_option('-c', '--config', dest='configfile',
                    default='~/.hetznerrc',
                    help="The location of the configuration file"
                         " (Default: %default)."),
        make_option('-u', '--username', dest='user',
                    help="Username for the robot."),
        make_option('-p', '--password', dest='passwd',
                    help="Password for the robot."),
    )

    def __init__(self, argv=None, parent=None):
        self.parent = parent
        self.running = False
        kwargs = {
            'usage': self.usage,
            'option_list': self.option_list,
            'description': self.description,
        }

        if hasattr(self, 'epilog'):
            kwargs['epilog'] = self.epilog

        parser = OptionParser(**kwargs)

        if self.command is None:
            # root parser
            parser.disable_interspersed_args()
            self.options, self.subargs = parser.parse_args()
            self.args = None
        else:
            # subcommand parser
            self.options, args = parser.parse_args(argv)
            self.subargs = []
            self.args = args[1:]

        self.parser = parser
        self.running = True
        self.execute()

        if self.command is None:
            # the subcommand is the first arg of the current node
            if len(self.subargs) == 0:
                parser.error("No command specified.")

            cmd = self.subargs[0]
            matches = filter(lambda c: c.command == cmd, self.subcommands)

            if len(matches) == 0:
                parser.error("Command {0} not found.".format(cmd))

            map(lambda x: x(argv, self), matches)

    def putline(self, line):
        data = line + "\n"
        try:
            sys.stdout.write(data)
        except UnicodeEncodeError:
            preferred = locale.getpreferredencoding()
            sys.stdout.write(data.encode(preferred, 'replace'))

    def execute(self, *args):
        pass

    def close(self):
        pass


class Reboot(Options):
    command = 'reboot'
    description = "Reboot a server."
    usage = "%prog ip..."
    option_list = [
        make_option('-m', '--method', dest='method',
                    choices=['soft', 'hard', 'manual'], default='soft',
                    help="The method to use for the reboot"
                         " (Default: %default).")
    ]

    def execute(self):
        robot = self.parent.robot

        for ip in self.args:
            server = robot.servers.get(ip)
            if server:
                server.reboot(self.options.method)


class Rescue(Options):
    command = 'rescue'
    description = ("Reboot into rescue system, spawn a shell and"
                   " after the shell is closed, reboot back into"
                   " the normal system.")
    usage = "%prog [options] ip [ip ...]"
    option_list = [
        make_option('-p', '--patience', dest='patience', type='int',
                    default=300, help="The time to wait between subsequent"
                                      " reboot tries (Default: %default)."),
        make_option('-m', '--manual', dest='manual', action='store_true',
                    default=False, help="If all reboot tries fail,"
                                        " automatically send a support"
                                        " request."),
        make_option('-n', '--noshell', dest='noshell', action='store_true',
                    default=False, help="Don't drop into a shell, only print"
                                        " rescue password."),
    ]

    def execute(self):
        robot = self.parent.robot

        for ip in self.args:
            server = robot.servers.get(ip)
            if not server:
                continue

            kwargs = {
                'patience': self.options.patience,
                'manual': self.options.manual,
            }

            if self.options.noshell:
                server.rescue.observed_activate(**kwargs)
                msg = u"Password for {0}: {1}".format(server.ip,
                                                      server.rescue.password)
                self.putline(msg)
            else:
                server.rescue.shell(**kwargs)


class SetName(Options):
    command = "set-name"
    description = "Change the name of a server"
    usage = "%prog ip name"

    def execute(self):
        robot = self.parent.robot

        if len(self.args) < 2:
            self.parser.error("Too few arguments.")
        elif len(self.args) > 2:
            self.parser.error("Too many arguments.")

        ip, name = self.args
        robot.servers.get(ip).set_name(name)


class ListServers(Options):
    command = 'list'
    description = "List all servers."

    def execute(self):
        robot = self.parent.robot

        for server in robot.servers:
            info = {
                'id': server.number,
                'model': server.product
            }

            if server.name != "":
                info['name'] = server.name

            infolist = [u"{0}: {1}".format(key, val)
                        for key, val in info.iteritems()]

            self.putline(u"{0} ({1})".format(server.ip, u", ".join(infolist)))


class ShowServer(Options):
    command = 'show'
    description = "Show details about a server."
    usage = "%prog ip..."

    def execute(self):
        robot = self.parent.robot

        for ip in self.args:
            self.print_serverinfo(robot.servers.get(ip))

    def print_line(self, key, val):
        self.putline(u"{0:<15}{1}".format(key + u":", val))

    def print_serverinfo(self, server):
        info = [
            ("Number", server.number),
            ("Main IP", server.ip),
            ("Name", server.name),
            ("Product", server.product),
            ("Data center", server.datacenter),
            ("Traffic", server.traffic),
            ("Flatrate", server.flatrate),
            ("Status", server.status),
            ("Throttled", server.throttled),
            ("Cancelled", server.cancelled),
            ("Paid until", server.paid_until),
        ]

        for key, val in info:
            self.print_line(key, val)

        for ip in server.ips:
            self.print_line(u"IP address", ip.ip)

        for net in server.subnets:
            self.print_line(u"Subnet", u"{0}/{1}".format(net.net_ip,
                                                         net.mask))
            self.print_line(u"Gateway", net.gateway)


class Main(Options):
    subcommands = [
        Reboot,
        Rescue,
        SetName,
        ListServers,
        ShowServer,
    ]

    @property
    def epilog(self):
        usage = "The following subcommands are available: "
        usage += ", ".join([x.command for x in self.subcommands])
        return usage

    def execute(self):
        self.configfile = expanduser(self.options.configfile)

        config = RawConfigParser()
        config.read(self.configfile)

        try:
            if not self.options.user:
                user = config.get('login', 'username')
            else:
                user = self.options.user

            if not self.options.passwd:
                passwd = config.get('login', 'password')
            else:
                passwd = self.options.passwd
        except Error:
            self.parser.error("No config file available, specify -u and -p to"
                              " create a new one.")

        if not config.has_section('login'):
            config.add_section('login')
            config.set('login', 'username', user)
            config.set('login', 'password', passwd)

            config.write(open(self.configfile, 'wb'))

        self.config = config

        self.robot = Robot(user, passwd)

    def close(self):
        self.config.write(open(self.configfile, 'wb'))

if __name__ == '__main__':
    Main()
