#============================================================================
# This file is part of Pwman3.
#
# Pwman3 is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License, version 2
# as published by the Free Software Foundation;
#
# Pwman3 is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Pwman3; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
#============================================================================
# Copyright (C) 2013 Oz Nahum <nahumoz@gmail.com>
#============================================================================
"""
Define the CLI interface for pwman3 and the helper functions
"""
from __future__ import print_function
import subprocess as sp
import getpass
import sys
import struct
import shlex
import platform
import colorama
import os
#from pwman.util.config import get_pass_conf
from pwman.util.callback import Callback


if sys.version_info.major > 2:  # pragma: no cover
    raw_input = input

if sys.platform != 'win32':
    import termios
    import fcntl
    import readline
    _readline_available = True
else:  # pragma: no cover
    try:
        import readline
        _readline_available = True
    except ImportError as e:
        _readline_available = False

_defaultwidth = 10


class ANSI(object):

    """
    ANSI Colors
    """
    Reset = 0
    Bold = 1
    Underscore = 2

    Black = 30
    Red = 31
    Green = 32
    Yellow = 33
    Blue = 34
    Magenta = 35
    Cyan = 36
    White = 37


def typeset(text, color, bold=False, underline=False,
            has_colorama=True):  # pragma: no cover
    """
    print colored strings using colorama
    """
    if not has_colorama:
        return text
    if bold:
        text = colorama.Style.BRIGHT + text
    if underline and not 'win32' in sys.platform:
        text = ANSI.Underscore + text
    return color + text + colorama.Style.RESET_ALL


def text_to_clipboards(text):  # pragma: no cover
    """
    copy text to clipboard
    credit:
    https://pythonadventures.wordpress.com/tag/xclip/
    """
    # "primary":
    try:
        xsel_proc = sp.Popen(['xsel', '-pi'], stdin=sp.PIPE)
        xsel_proc.communicate(text)
        # "clipboard":
        xsel_proc = sp.Popen(['xsel', '-bi'], stdin=sp.PIPE)
        xsel_proc.communicate(text)
    except OSError as e:
        print (e, "\nExecuting xsel failed, is it installed ?\n \
               please check your configuration file ... ")


def text_to_mcclipboard(text):  # pragma: no cover
    """
    copy text to mac os x clip board
    credit:
    https://pythonadventures.wordpress.com/tag/xclip/
    """
    # "primary":
    try:
        pbcopy_proc = sp.Popen(['pbcopy'], stdin=sp.PIPE)
        pbcopy_proc.communicate(text)
    except OSError as e:
        print (e, "\nExecuting pbcoy failed...")


def open_url(link, macosx=False):  # pragma: no cover
    """
    launch xdg-open or open in MacOSX with url
    """
    uopen = "xdg-open"
    if macosx:
        uopen = "open"
    try:
        sp.Popen([uopen, link], stdin=sp.PIPE)
    except OSError as e:
        print("Executing open_url failed with:\n", e)


def get_password(question, config):
    # TODO: enable old functionallity, with password generator.
    if sys.stdin.isatty():  # pragma: no cover
        p = getpass.getpass()
    else:
        p = sys.stdin.readline().rstrip()
    return p


def getpassword(question, argsgiven=None,
                width=_defaultwidth, echo=False,
                reader=getpass.getpass, numerics=False, leetify=False,
                symbols=False, special_signs=False,
                length=None, config=None):  # pragma: no cover
    if argsgiven == 1 or length:
        while not length:
            try:
                default_length = config.get_value(
                    'Generator', 'default_pw_length') or '8'
                length = getinput(
                    "Password length (default %s): " % default_length,
                    default=default_length)
                length = int(length)
            except ValueError:
                print("please enter a proper integer")

        #password, dumpme = generator.generate_password(
        #    length, length, True, symbols=leetify, numerics=numerics,
        #    special_chars=special_signs)
        #print ("New password: %s" % (password))
        #return password
    # no args given
    while True:
        a1 = reader(question.ljust(width))
        if not a1:
            return getpassword(
                '', argsgiven=1, width=width, echo=echo, reader=reader,
                numerics=numerics, leetify=leetify, symbols=symbols,
                special_signs=special_signs, length=length, config=config)
        a2 = reader("[Repeat] %s" % (question.ljust(width)))
        if a1 == a2:
            if leetify:
                pass  # return generator.leetify(a1)
            else:
                return a1
        else:
            print ("Passwords don't match. Try again.")


def get_terminal_size():
    """ getTerminalSize()
     - get width and height of console
     - works on linux,os x,windows,cygwin(windows)
     originally retrieved from:
     http://stackoverflow.com/questions/566746/how-to-get-\
         console-window-width-in-python
    """
    current_os = platform.system()
    tuple_xy = None
    if current_os == 'Windows':
        tuple_xy = _get_terminal_size_windows()
        if tuple_xy is None:
            tuple_xy = _get_terminal_size_tput()
            # needed for window's python in cygwin's xterm!
    if current_os in ['Linux', 'Darwin'] or current_os.startswith('CYGWIN'):
        tuple_xy = _get_terminal_size_linux()
    if tuple_xy is None:
        tuple_xy = (80, 25)      # default value
    return tuple_xy


def _get_terminal_size_windows():  # pragma: no cover
    try:
        from ctypes import windll, create_string_buffer
        # stdin handle is -10
        # stdout handle is -11
        # stderr handle is -12
        h = windll.kernel32.GetStdHandle(-12)
        csbi = create_string_buffer(22)
        res = windll.kernel32.GetConsoleScreenBufferInfo(h, csbi)
        if res:
            (bufx, bufy, curx, cury, wattr,
             left, top, right, bottom,
             maxx, maxy) = struct.unpack("hhhhHhhhhhh", csbi.raw)
            sizex = right - left + 1
            sizey = bottom - top + 1
            return sizex, sizey
    except:
        pass


def _get_terminal_size_tput():  # pragma: no cover
    # get terminal width
    # src: http://stackoverflow.com/questions/263890/how-do-i-\
    #    find-the-width-height-of-a-terminal-window
    try:
        cols = int(sp.check_call(shlex.split('tput cols')))
        rows = int(sp.check_call(shlex.split('tput lines')))
        return (cols, rows)
    except:
        pass


def _get_terminal_size_linux():  # pragma: no cover
    def ioctl_GWINSZ(fd):
        try:
            import fcntl
            import termios
            cr = struct.unpack('hh',
                               fcntl.ioctl(fd, termios.TIOCGWINSZ, '1234'))
            return cr
        except:
            pass
    cr = ioctl_GWINSZ(0) or ioctl_GWINSZ(1) or ioctl_GWINSZ(2)
    if not cr:
        try:
            fd = os.open(os.ctermid(), os.O_RDONLY)
            cr = ioctl_GWINSZ(fd)
            os.close(fd)
        except:
            pass
    if not cr:
        try:
            cr = (os.environ['LINES'], os.environ['COLUMNS'])
        except:
            return None
    return int(cr[1]), int(cr[0])


def gettermsize():  # pragma: no cover
    if sys.stdout.isatty():
        s = struct.pack("HHHH", 0, 0, 0, 0)
        f = sys.stdout.fileno()
        x = fcntl.ioctl(f, termios.TIOCGWINSZ, s)
        rows, cols, width, height = struct.unpack("HHHH", x)
        return rows, cols
    else:
        return 40, 80


def getinput(question, default="", reader=raw_input,
             completer=None, width=_defaultwidth):  # pragma: no cover
    """
    http://stackoverflow.com/questions/2617057/\
            supply-inputs-to-python-unittests
    """
    if reader == raw_input:
        if not _readline_available:
            val = raw_input(question.ljust(width))
            if val:
                return val
            else:
                return default
        else:
            def defaulter():
                """define default behavior startup"""
                if _readline_available:
                    readline.insert_text(default)
                readline.set_startup_hook(defaulter)
                readline.get_completer()
                readline.set_completer(completer)

            x = raw_input(question.ljust(width))
            readline.set_completer(completer)
            readline.set_startup_hook()
            if not x:
                return default
            return x
    else:
        return reader()


class CMDLoop(object):  # pragma: no cover

    """
    The menu that drives editing of a node
    """

    def __init__(self, config):
        self.items = []
        self.config = config

    def add(self, item):
        if (isinstance(item, CliMenuItem)):
            self.items.append(item)
        else:
            print (item.__class__)

    def run(self, new_node=None, reader=raw_input):
        while True:
            for i, x in enumerate(self.items):
                current = x.getter
                print ("%s - %s: %s" % (i + 1, x.name, current))

            print("X - Finish editing")
            option = reader("Enter your choice:")[0]
            try:
                print ("Selection, ", option)
                # substract 1 because array subscripts start at 0
                selection = int(option) - 1
                # new value is created by calling the editor with the
                # previous value as a parameter
                # TODO: enable overriding password policy as if new node
                # is created.
                if selection == 0:
                    new_node.username = getinput("Username:")
                    self.items[0].getter = new_node.username
                    self.items[0].setter = new_node.username
                elif selection == 1:  # for password
                    new_node.password = get_password("Password:", self.config)
                    self.items[1].getter = new_node.password
                    self.items[1].setter = new_node.password
                elif selection == 2:
                    new_node.url = getinput("Url:")
                    self.items[2].getter = new_node.url
                    self.items[2].setter = new_node.url
                elif selection == 3:  # for notes
                    # new_node.notes = getinput("Notes:")
                    new_node.notes = reader("Notes:")
                    self.items[3].getter = new_node.notes
                    self.items[3].setter = new_node.notes
                elif selection == 4:
                    taglist = getinput("Tags:")
                    tagstrings = taglist.split()
                    tags = [tn for tn in tagstrings]
                    #tags = ''
                    new_node.tags = tags
                    self.items[4].setter = new_node.tags
                    self.items[4].getter = new_node.tags
            except (ValueError, IndexError):
                if (option.upper() == 'X'):
                    break
                print("Invalid selection")


class CliMenuItem(object):  # pragma: no cover

    def __init__(self, name, editor, getter, setter):
        self.name = name
        self.editor = editor
        self.getter = getter
        self.setter = setter


class CLICallback(Callback):  # pragma: no cover

    def getinput(self, question):
        return raw_input(question)

    def getsecret(self, question):
        return getpass.getpass(question + ":")

    def getnewsecret(self, question):
        return getpass.getpass(question + ":")