class RootImage(DockerImage):
    """
    this is essentially "scratch" but you cannot inspect it anymore
    """
    def __init__(self, docker_backend):
        self.image_name = "scratch"
        super().__init__(None, docker_backend, object_id="")

    @property
    def parent_id(self):
        return None

    @property
    def parent_image(self):
        return None

    def get_next_sibling(self):
        return None

    def get_prev_sibling(self):
        return None

    @property
    def names(self):
        return [ImageNameStruct.parse(self.image_name)]

    def __str__(self):
        return self.image_name class ContainerOneLinerWidget(SelectableText):
    def __init__(self, ui, docker_container):
        self.ui = ui
        self.docker_container = docker_container
        super().__init__(get_basic_container_markup(docker_container)) class LayerWidget(SelectableText):
    def __init__(self, ui, docker_image, index=None):
        self.ui = ui
        self.docker_image = docker_image
        label = []
        logger.debug("creating layer widget for %s", docker_image)
        if index is not None:
            separator = "└─"
            if index == 0:
                label = [separator]
            else:
                label = [2 * index * " " + separator]
        super().__init__(label + get_basic_image_markup(docker_image, with_size=True)) class View:
    # TODO: views should have API for searching and filtering
    #       there should be a map between widgets and their searchable content
    def refresh(self):
        raise NotImplementedError() class WidgetBase(urwid.ListBox):
    """
    common class for widgets
    """

    def __init__(self, ui, *args, **kwargs):
        self.ui = ui
        self.search_string = None
        self.filter_query = ""
        super().__init__(*args, **kwargs)
        self.ro_content = self.body[:]  # unfiltered content of a widget
        self.body_change_lock = threading.Lock()

    def set_body(self, widgets):
        with self.body_change_lock:
            self.body[:] = widgets

    def reload_widget(self):
        # this is the easiest way to refresh body
        with self.body_change_lock:
            self.body[:] = self.body

    def _search(self, reverse_search=False):
        if self.search_string is None:
            raise NotifyError("No search pattern specified.")
        if not self.search_string:
            self.search_string = None
            return
        pos = self.focus_position
        original_position = pos
        wrapped = False
        while True:
            if reverse_search:
                obj, pos = self.body.get_prev(pos)
            else:
                obj, pos = self.body.get_next(pos)
            if obj is None:
                # wrap
                wrapped = True
                if reverse_search:
                    obj, pos = self.body[-1], len(self.body)
                else:
                    obj, pos = self.body[0], 0
            if wrapped and (
                        (pos > original_position and not reverse_search) or
                        (pos < original_position and reverse_search)
            ):
                raise NotifyError("Pattern not found: %r." % self.search_string)
            # FIXME: figure out nicer search api
            if hasattr(obj, "matches_search"):
                condition = obj.matches_search(self.search_string)
            else:
                if hasattr(obj, "original_widget"):
                    text = obj.original_widget.text
                else:
                    text = obj.text
                condition = self.search_string in text
            if condition:
                self.set_focus(pos)
                self.reload_widget()
                break

    def filter(self, s, widgets_to_filter=None):
        s = s.strip()

        if not s:
            self.filter_query = None
            self.set_body(self.ro_content)
            return

        widgets = []
        for obj in widgets_to_filter or self.ro_content:

            # FIXME: figure out nicer search api
            if hasattr(obj, "matches_search"):
                condition = obj.matches_search(s)
            else:
                if hasattr(obj, "original_widget"):
                    text = obj.original_widget.text
                else:
                    text = obj.text
                condition = s in text

            if condition:
                widgets.append(obj)
        if not widgets_to_filter:
            self.filter_query = s
        self.set_body(widgets)

    def find_previous(self, search_pattern=None):
        if search_pattern is not None:
            self.search_string = search_pattern
        self._search(reverse_search=True)

    def find_next(self, search_pattern=None):
        if search_pattern is not None:
            self.search_string = search_pattern
        self._search()

    def status_bar(self):
        columns_list = []

        def add_subwidget(markup, color_attr=None):
            if color_attr is None:
                w = urwid.AttrMap(urwid.Text(markup), "status_text")
            else:
                w = urwid.AttrMap(urwid.Text(markup), color_attr)
            columns_list.append((len(markup), w))

        if self.search_string:
            add_subwidget("Search: ")
            add_subwidget(repr(self.search_string))

        if self.search_string and self.filter_query:
            add_subwidget(", ")

        if self.filter_query:
            add_subwidget("Filter: ")
            add_subwidget(repr(self.filter_query))

        return columns_list

    @property
    def focused_docker_object(self):
        return self.get_focus()[0].docker_object class RowWidget(UnselectableRowWidget):
    def selectable(self):
        return True def assemble_rows(data, max_allowed_lengths=None, dividechars=1,
                  ignore_columns=None):
    """
    :param data: list of lists:
    [["row 1 column 1", "row 1 column 2"],
     ["row 2 column 1", "row 2 column 2"]]
    each item consists of instance of urwid.Text

    :param max_allowed_lengths: dict:
        {col_index: maximum_allowed_length}
    :param ignore_columns: list of ints, indexes which should not be calculated
    """
    rows = []
    max_lengths = {}
    ignore_columns = ignore_columns or []

    # shitty performance, here we go
    # it would be way better to do a single double loop and provide mutable variable
    # FIXME: merge this code with calculate() from above
    for row in data:
        col_index = 0
        for widget in row:
            if col_index in ignore_columns:
                continue
            l = len(widget.text)
            if max_allowed_lengths:
                if col_index in max_allowed_lengths and max_allowed_lengths[col_index] < l:
                    # l is bigger then what is allowed
                    l = max_allowed_lengths[col_index]

            max_lengths.setdefault(col_index, l)
            max_lengths[col_index] = max(l, max_lengths[col_index])
            col_index += 1

    for row in data:
        row_widgets = []
        for idx, item in enumerate(row):
            if idx in ignore_columns:
                row_widgets.append(item)
            else:
                row_widgets.append((max_lengths[idx], item))
        rows.append(
            RowWidget(row_widgets, dividechars=dividechars)
        )

    return rows class SelectableText(ColorTextMixin, AdHocAttrMap):
    def __init__(self, text, maps=None):
        maps = maps or get_map()
        super().__init__(urwid.Text(text, align="left", wrap="clip"), maps) def get_map(defult="main_list_dg"):
    return {"normal": defult, "focus": MAIN_LIST_FOCUS} def humanize_bytes(bytesize, precision=2):
    """
    Humanize byte size figures

    https://gist.github.com/moird/3684595
    """
    abbrevs = (
        (1 << 50, 'PB'),
        (1 << 40, 'TB'),
        (1 << 30, 'GB'),
        (1 << 20, 'MB'),
        (1 << 10, 'kB'),
        (1, 'bytes')
    )
    if bytesize == 1:
        return '1 byte'
    for factor, suffix in abbrevs:
        if bytesize >= factor:
            break
    if factor == 1:
        precision = 0
    return '%.*f %s' % (precision, bytesize / float(factor), suffix)
import logging
import urwid
from sen.docker_backend import RootImage
from sen.tui.chunks.container import ContainerOneLinerWidget
from sen.tui.chunks.image import LayerWidget
from sen.tui.views.base import View
from sen.tui.widgets.list.base import WidgetBase
from sen.tui.widgets.list.util import RowWidget
from sen.tui.widgets.table import assemble_rows
from sen.tui.widgets.util import SelectableText, get_map
from sen.util import humanize_bytes




logger = logging.getLogger(__name__)


class TagWidget(SelectableText):
    """
    so we can easily access image and tag
    """
    def __init__(self, docker_image, tag):
        self.docker_image = docker_image
        self.tag = tag
        super().__init__(str(self.tag))


class ImageInfoWidget(WidgetBase, View):
    """
    display info about image
    """
    def __init__(self, ui, docker_image):
        self.walker = urwid.SimpleFocusListWalker([])
        super().__init__(ui, self.walker)

        self.docker_image = docker_image

    def refresh(self):
        self.docker_image.refresh()
        self.walker.clear()
        self._basic_data()
        self._containers()
        self._image_names()
        self._layers()
        self._labels()
        self.set_focus(0)

    @property
    def focused_docker_object(self):
        # TODO: enable removing image names
        try:
            return self.focus.columns.widget_list[0].docker_container
        except AttributeError:
            try:
                return self.focus.columns.widget_list[0].docker_image
            except AttributeError:
                return None

    def _basic_data(self):
        data = [
            [SelectableText("Id", maps=get_map("main_list_green")),
             SelectableText(self.docker_image.image_id)],
            [SelectableText("Created", maps=get_map("main_list_green")),
             SelectableText("{0}, {1}".format(self.docker_image.display_formal_time_created(),
                                              self.docker_image.display_time_created()))],
            [SelectableText("Size", maps=get_map("main_list_green")),
             SelectableText(humanize_bytes(self.docker_image.total_size))],
        ]
        if self.docker_image.unique_size:
            data.append(
                [SelectableText("Unique Size", maps=get_map("main_list_green")),
                 SelectableText(humanize_bytes(self.docker_image.unique_size))])
        if self.docker_image.shared_size:
            data.append(
                [SelectableText("Shared Size", maps=get_map("main_list_green")),
                 SelectableText(humanize_bytes(self.docker_image.shared_size))])
        data.append([SelectableText("Command", maps=get_map("main_list_green")),
                     SelectableText(self.docker_image.container_command)])
        self.walker.extend(assemble_rows(data, ignore_columns=[1]))

    def _image_names(self):
        if not self.docker_image.names:
            return
        self.walker.append(RowWidget([SelectableText("")]))
        self.walker.append(RowWidget([SelectableText("Image Names", maps=get_map("main_list_white"))]))
        for n in self.docker_image.names:
            self.walker.append(RowWidget([TagWidget(self.docker_image, n)]))

    def _layers(self):
        self.walker.append(RowWidget([SelectableText("")]))
        self.walker.append(RowWidget([SelectableText("Layers", maps=get_map("main_list_white"))]))

        i = self.docker_image
        parent = i.parent_image
        layers = self.docker_image.layers
        index = 0

        if isinstance(parent, RootImage) and len(layers) > 0:  # pulled image, docker 1.10+
            for image in layers:
                self.walker.append(
                    RowWidget([LayerWidget(self.ui, image, index=index)])
                )
                index += 1
        else:

            self.walker.append(RowWidget([LayerWidget(self.ui, self.docker_image, index=index)]))