#  Copyright (c) 2014-2015 Maximilien Riehl <max@riehl.io>
#  This work is free. You can redistribute it and/or modify it under the
#  terms of the Do What The Fuck You Want To Public License, Version 2,
#  as published by Sam Hocevar. See the COPYING.wtfpl file for more details.
#

"""
A common set of capabilities for each command.
"""

from __future__ import print_function

from cmd2 import Cmd
import re

from isphere.connection import CachingVSphere
from isphere.interactive_wrapper import NotFound


try:
    _input = raw_input
except NameError:
    _input = input


class NoOutput(Exception):

    """
    To be raised inside an isphere `eval` command to avoid producing output.
    """
    pass


class CoreCommand(Cmd):

    """
    The core capabilities of isphere commands, independent of the item type.
    """

    def __init__(self):
        self.cache = CachingVSphere(self.hostname, self.username, self.password)
        Cmd.__init__(self)
        self.prompt = self.colorize("isphere > ", "green")

    def preloop(self):
        """
        Called by the `cmd.Cmd` base class before entering the REPL loop.
        Displays information about the cached items.
        """
        self.cache.fill()
        print(
            self.colorize("{0} VMs on {1} ESXis available.".format(self.cache.number_of_vms,
                                                                   self.cache.number_of_esxis),
                          "blue"))
        print(
            self.colorize("{0} Distributed Virtual Switches configured.".format(
                self.cache.number_of_dvses),
                "blue"))

    def do_reload(self, _):
        """Usage: reload
        Reload VM cache from the vSphere server.

        Sample usage: `reload`
        """
        self.preloop()

    def eval(self, line, item_name_generator, item_retriever, local_name):
        """
        Run an eval command. This will retrieve items based on given patterns
        and execute a python statement against each of these items. The item will
        be bound to the given name for each of these statements.

        - line (type `str`): The text line provided by the user. The format should
          be as follows: <patterns> ! <statement>
        - item_name_generator (type `callable`): A function that should generate
          an iterable that represents the available item names for pattern matching.
        - item_retriever (type `callable`): A function that takes an item name
          generated by `item_name_generator` as input and returns the actual item.
        - local_name (type `str`): The name to bind the item to when evaluating
          the statement.
        """
        try:
            patterns_and_statement = line.split("!", 1)
            patterns = patterns_and_statement[0]
            statement = patterns_and_statement[1]
        except IndexError:
            print(self.colorize("Looks like your input was malformed. Try `help eval_*`.", "red"))
            return

        for item_name in item_name_generator(patterns):
            def guard():
                raise NoOutput()
            _globals, _locals = {}, {}
            try:
                item = item_retriever(item_name)
            except NotFound:
                print(self.colorize("Skipping {item} since it could not be retrieved.".format(item=item_name), "red"))
                continue
            _locals[local_name] = item
            _locals["no_output"] = guard
            _globals[local_name] = item
            _globals["no_output"] = guard

            separator = "-"
            item_name_header = " {name} ".format(
                name=item_name[:78]).center(
                80, separator)

            try:
                result = eval(statement, _globals, _locals)
                print(self.colorize(item_name_header, "blue"))
                print(result)
            except NoOutput:
                pass
            except Exception as e:
                print(self.colorize(item_name_header, "red"))
                print(self.colorize("Eval failed for {0}: {1}".format(item_name, e), "red"))

    def compile_and_yield_generic_patterns(self, patterns, pattern_generator, item_count, risky=True, ask=False):
        """
        Compiles and returns regular expression patterns. Swallows the exception
        and complains if the patterns are invalid.
        Optionally (yes by default), warns and prompts in case no pattern was given.

        - patterns (type `str`): A space delimited sequence of regular expression patterns.
        - pattern_generator (type `callable`): A function that, given a list of patterns,
          will return matching item names.
        - item_count (type `int`): The number of available items before pattern
          matching.
        - risky (type `bool`, default `True`): Whether to prompt for confirmation when no patterns
          are given.
        - ask (type `bool`, default `False`): Whether to prompt for confirmation when the given patterns
          match more than 50 elements.
        """
        if not patterns and risky:
            unformatted_message = "No pattern specified - you're doing this to all {count} items. Proceed? (y/N) "
            message = unformatted_message.format(count=self.colorize(str(item_count), "red"))
            actual_patterns = patterns.strip().split(' ')
            if not _input(message).lower() == "y":
                return []
        else:
            actual_patterns = patterns.strip().split()

        if ask and len(actual_patterns) > 50:
            unformatted_message = "Doing this to {count} items. Proceed? (y/N) "
            message = unformatted_message.format(count=self.colorize(str(len(actual_patterns)), "red"))
            if not _input(message).lower() == "y":
                return []

        try:
            compiled_patterns = [re.compile(pattern) for pattern in actual_patterns]
        except Exception as e:
            print(self.colorize("Invalid regular expression patterns: {0}".format(e), "red"))
            return []

        return pattern_generator(compiled_patterns)

    @staticmethod
    def do_EOF(_):
        """
        Exit this prompt.

        Usage: `EOF` (Ctrl+D also works).
        """
        return True
