# Copyright 2021 Offensive Security
# SPDX-license-identifier: GPL-3.0-only

import logging
import shutil

from snack import (
    ButtonBar,
    Checkbox,
    CheckboxTree,
    Grid,
    GridForm,
    Listbox,
    RadioBar,
    Textbox,
    TextboxReflowed,
    reflow,
)

logger = logging.getLogger(__name__)


MIN_WIDTH = 80
MAX_WIDTH = 120


def get_ui_width():
    size = shutil.get_terminal_size()
    if size.columns < MIN_WIDTH:
        return MIN_WIDTH - 6
    if size.columns > MAX_WIDTH:
        return MAX_WIDTH - 6
    return size.columns - 6


def join_with_padding(items, numbered=False):
    """
    Take a list of tuples        (text1, text2),
    and return a list of strings "text1<padding> text2",
    so that all the text2 are lined up.

    Optionally, one can ask to number each line, so that
    the ouput is "N text1<padding> text2"
    """

    space_padding = 2

    # How much to pad N
    nfill = 1
    if len(items) > 10:
        nfill = 2

    # How much to pad text1
    maxlen = 0
    for text, _ in items:
        maxlen = max(maxlen, len(text))
    tfill = maxlen + space_padding

    # Build the result
    res = []
    count = 1
    for t1, t2 in items:
        t1 = f"{t1:<{tfill}}"
        if numbered:
            n = f"{count:>{nfill}}"
            res.append(f"{n} {t1} {t2}")
        else:
            res.append(f"{t1} {t2}")
        count += 1

    return res


# -------- WIDGETS -------- #


class KaliTextboxFullSize(Textbox):
    def __init__(self, height, text):
        super().__init__(get_ui_width(), height, text)


class KaliAnyBar(Grid):
    """Base class for KaliCheckboxBar and KaliRadioBar"""

    def __init__(self, title, n_items):
        # create the grid
        super().__init__(1, n_items)

        # pack title (allow it to span on 2 lines)
        width = get_ui_width() + 5  # don't ask
        t = TextboxReflowed(width, title, maxHeight=2)
        self.setField(t, 0, 0)

        # expose expected title height - a bit of a hack,
        # 5 and 10 are the default values, cf. snack.py
        _, _, height = reflow(title, width, 5, 10)
        self.title_height = height

    def zip_items_descriptions(self, items):
        """items are tuples: (short-desc, long-desc, key)

        The returned items are tuples: (text-to-display, key)
        """
        short_long = [x[:-1] for x in items]
        texts = join_with_padding(short_long)
        new_items = []
        for i, t in zip(items, texts):
            new_items += [(t, i[2])]
        return new_items

    def getSelection(self):
        return NotImplementedError


class KaliCheckboxBar(KaliAnyBar):
    def __init__(self, title, items, selection):
        """items are tuples: (short-desc, long-desc, key)"""
        # init superclass
        super().__init__(title, len(items) + 1)

        # rebuild items with texts to display
        items = self.zip_items_descriptions(items)

        # pack checkboxes
        self.cbs = []
        self.keys = []
        for idx, item in enumerate(items):
            text, key = item
            cb = Checkbox(text, isOn=selection[key])
            self.setField(cb, 0, idx + 1, growx=1, anchorLeft=1)
            self.cbs.append(cb)
            self.keys.append(key)

    def getSelection(self):
        data = {}
        for cb, key in zip(self.cbs, self.keys):
            data[key] = cb.selected()
        return data


class KaliRadioBar(KaliAnyBar):
    def __init__(self, title, items, selected):
        """items are tuples: (short-desc, long-desc, key)"""
        # init superclass
        super().__init__(title, 2)

        # rebuild items with texts to display
        items = self.zip_items_descriptions(items)

        # add the 'default' value to items
        new_items = [i + (i[1] == selected,) for i in items]
        items = new_items

        # pack the radiobar - first arg is screen, actually unused
        self.rb = RadioBar(None, items)
        self.setField(self.rb, 0, 1, growx=1, anchorLeft=1)

    def getSelection(self):
        return self.rb.getSelection()


class KaliListbox(Listbox):
    def __init__(self, height, items):
        """items are: (short-desc, long-desc)"""

        # build text to display
        texts = join_with_padding(items)

        # do we need scrolling?
        scroll = 1 if len(items) > height else 0

        super().__init__(height, width=get_ui_width(), scroll=scroll, returnExit=1)

        for idx, text in enumerate(texts):
            self.append(text, idx)


class KaliSimpleCheckboxTree(CheckboxTree):
    def __init__(self, height, items):
        """items are: (short-desc, long-desc, selected)"""

        # note: short desc is used as key as well

        # build text to display
        short_long = [x[:-1] for x in items]
        texts = join_with_padding(short_long)
        new_items = []
        for i, t in zip(items, texts):
            new_items += [(i[0], t, i[2])]
        items = new_items

        # do we need scrolling?
        scroll = 1 if len(items) > height else 0

        super().__init__(height, width=get_ui_width(), scroll=scroll)

        for key, text, selected in items:
            self.append(text, item=key, selected=selected)


# -------- WINDOWS -------- #


def KaliInfoWindow(screen, height, what, message):
    if what == "info":
        title = "Information"
    elif what == "warning":
        title = "Warning"
    elif what == "error":
        title = "Error"
    else:
        title = "GIVE ME A TITLE"

    t = Textbox(get_ui_width(), height, message, scroll=0, wrap=1)
    bb = ButtonBar(screen, [("Ok", "ok")], compact=1)

    g = GridForm(screen, title, 1, 2)
    g.add(t, 0, 0)
    g.add(bb, 0, 1, padding=(0, 1, 0, 0), growx=1)

    g.runOnce()


def KaliConfirmWindow(
    screen, height, message, title=None, ok_label=None, cancel_label=None
):
    if not title:
        title = "Confirmation"
    if not ok_label:
        ok_label = "OK"
    if not cancel_label:
        cancel_label = "Cancel"
    buttons = [
        (ok_label, "ok"),
        (cancel_label, "cancel"),
    ]

    t = Textbox(get_ui_width(), height, message, scroll=0, wrap=1)
    bb = ButtonBar(screen, buttons, compact=1)

    g = GridForm(screen, title, 1, 2)
    g.add(t, 0, 0)
    g.add(bb, 0, 1, padding=(0, 1, 0, 0), growx=1)

    rc = g.runOnce()
    logger.debug("run once: %s", str(rc))

    if rc == "ESC":
        # '<Esc>' was pressed
        btn = "cancel"
    else:
        # a button from the button bar
        btn = bb.buttonPressed(rc)
    logger.debug("btn: %s", str(rc))

    return btn


class KaliWindow(GridForm):
    def __init__(self, screen, title, content, buttons):
        super().__init__(screen, title, 1, 2)
        self.content = content
        self.bb = ButtonBar(screen, buttons, compact=1)
        self.add(self.content, 0, 0)
        self.add(self.bb, 0, 1, padding=(0, 1, 0, 0), growx=1)

    def run_once(self):
        rc = self.runOnce()
        logger.debug("runOnce: %s", str(rc))

        if rc == "ESC":
            # '<Esc>' was pressed
            btn = "cancel"
        elif isinstance(rc, Listbox):
            # '<Enter>' was pressed on a Listbox item,
            # not a very pretty way to check...
            btn = "ok"
        else:
            # a button from the button bar was pressed
            btn = self.bb.buttonPressed(rc)
        logger.debug("btn: %s", str(btn))

        return btn


def KaliListboxWindow(screen, height, title, items, buttons):
    listbox = KaliListbox(height, items)
    window = KaliWindow(screen, title, listbox, buttons)
    btn = window.run_once()
    line = listbox.current()
    return btn, line


# def KaliListboxWindow(screen, title, items, buttons):
#
#     entries = join_with_padding(items)
#
#     if len(entries) > CONTENT_HEIGHT:
#         scroll=1
#     else:
#         scroll=0
#
#     l = Listbox(CONTENT_HEIGHT, width=get_ui_width(), scroll=scroll, returnExit=1)
#     for i in range(0, len(entries)):
#         l.append(entries[i], i)
#
#     bb = ButtonBar(screen, buttons, compact=1)
#
#     g = GridForm(screen, title, 1, 2)
#     g.add(l, 0, 0)
#     g.add(bb, 0, 1, padding=(0,1,0,0), growx=1)
#
#     rc = g.runOnce()
#     logger.debug("run once: %s", str(rc))
#
#     if rc == 'ESC':
#         # '<Esc>' was pressed
#         btn = 'cancel'
#     elif isinstance(rc, Listbox):
#         # '<Enter>' was pressed on a Listbox item
#         btn = 'ok'
#     else:
#         # a button from the button bar was pressed
#         btn = bb.buttonPressed(rc)
#     logger.debug("btn: %s, l.current(): %s", btn, l.current())
#
#     return (btn, l.current())
