import math
import sys
from volatility import renderers
from volatility.fmtspec import FormatSpec
from volatility.renderers import ColumnSortKey
from volatility.renderers.basic import Address, Address64, Hex, Renderer

__author__ = 'mike'

class CellRenderer(object):
    """Class to handle rendering of a particular cell in a text grid"""
    # The minimum width that the renderer will produce for a value
    width = 0

    def render(self, value):
        """Returns the rendering of an individual value"""
        return value


class FormatCellRenderer(CellRenderer):
    """Class to handle rendering each cell of a grid"""

    def __init__(self, format_spec):
        if not isinstance(format_spec, FormatSpec):
            fs = FormatSpec()
            fs.from_string(format_spec)
            format_spec = fs
        self._format_spec = format_spec

    def render(self, value):
        """Render an individual cell"""
        return ("{0:" + str(self._format_spec) + "}").format(value)

    @property
    def width(self):
        return self._format_spec.minwidth

    @width.setter
    def width(self, value):
        self._format_spec.minwidth = max(value, self._format_spec.minwidth)

    def __repr__(self):
        return "<FormatCellRenderer (" + repr(self._format_spec) + ")>"


class TextRenderer(Renderer):
    min_column_width = 5

    def __init__(self, cell_renderers_func, max_width = 200, sort_column = None, config = None):
        """Accepts a cell_renderer function, an optional maximum width and optional sort column.

        The signature of the cell_renderers_function is:

          def cell_renderers(self, TreeGridcolumns):
            return [cell_renderer, cell_renderer, ...]
        """
        self._cell_renderers_func = cell_renderers_func
        self._cell_renderers = None
        self.max_width = max_width
        self.sort_column = sort_column
        self._config = config

    def partition_width(self, widths):
        """Determines if the widths are over the maximum available space, and if so shrinks them"""
        if math.fsum(widths) + (len(widths) - 1) > self.max_width:
            remainder = (int(math.fsum(widths)) + (len(widths) - 1)) - self.max_width
            # Take from the largest column first, eventually evening out
            for i in range(remainder):
                col_index = widths.index(max(widths))
                widths[col_index] -= 1
        return widths

    def _elide(self, string, length):
        """Ensures that strings passed as value are returned no longer than max_width characters long, elided if necessary"""
        if length == -1:
            return string
        if len(string) < length:
            return (" " * (length - len(string))) + string
        elif len(string) == length:
            return string
        else:
            if length < self.min_column_width:
                return string
            even = ((length + 1) % 2)
            length = (length - 3) / 2
            return string[:length + even] + "..." + string[-length:]


    def _validate_grid(self, grid):
        if not isinstance(grid, renderers.TreeGrid):
            raise TypeError("Grid must be of type TreeGrid")
        self._cell_renderers = self._cell_renderers_func(grid.columns)
        if not isinstance(self._cell_renderers, list):
            raise TypeError("cell_renderers must be of type list")
        for item in self._cell_renderers:
            if not isinstance(item, CellRenderer):
                raise TypeError("Items within the cell_renderers list must be of type CellRenderer")

    def render(self, outfd, grid):
        """Renders a text grid based on the contents of each element"""
        sort_key = None
        if self.sort_column:
            sort_key = ColumnSortKey(grid, self.sort_column).key

        self._validate_grid(grid)# Determine number of columns

        # if self._config and self._config.VERBOSE:
        #     qtr = QuickTextRenderer(self._cell_renderers_func)
        #     output = sys.stdout
        #     output.write("Immediate (verbose) output:\n")
        #     qtr.render(output, grid)
        #     output.write("\n")
        #     output.flush()

        grid_depth = grid.visit(None, lambda x, y: max(y, grid.path_depth(x)), 0)

        # Determine max width of each column
        grid_max_widths = [0] * len(grid.columns)

        def gridwidth(node, accumulator = None):
            for vindex in range(len(node.values)):
                entry = self._cell_renderers[vindex].render(node.values[vindex])
                accumulator[vindex] = max(len(entry), accumulator[vindex])
            return accumulator

        grid.visit(None, gridwidth, grid_max_widths)
        if grid_depth > 1:
            grid_max_widths = [grid_depth * 1] + grid_max_widths

        # Figure out how to partition the available widths
        new_grid_widths = self.partition_width(grid_max_widths)

        # If the grid_max_widths have not been limited,
        if new_grid_widths == grid_max_widths:
            for i in range(len(grid.columns)):
                index = i + (1 if grid_depth > 1 else 0)
                grid_max_widths[index] = max(grid_max_widths[index], len(grid.columns[i].name))

        for i in range(len(grid.columns)):
            index = i + (1 if grid_depth > 1 else 0)
            self._cell_renderers[i].width = grid_max_widths[index]
            grid_max_widths[index] = self._cell_renderers[i].width

        cols = []
        for index in range(len(grid_max_widths)):
            if grid_depth > 1:
                if index == 0:
                    cols += [" " * grid_max_widths[index]]
                    continue
                else:
                    column = grid.columns[index - 1]
            else:
                column = grid.columns[index]
            cols += [
                self._elide(("{0:<" + str(grid_max_widths[index]) + "}").format(column.name), grid_max_widths[index])]
        outfd.write(" ".join(cols) + "\r\n")

        def print_row(node, accumulator):
            row = []
            for index in range(len(grid_max_widths)):
                if grid_depth > 1:
                    if index == 0:
                        row += [(" " * (grid.path_depth(node) - 1)) + ">" + (
                            " " * (grid_max_widths[0] - grid.path_depth(node)))]
                        continue
                    else:
                        column = grid.columns[index - 1]
                else:
                    column = grid.columns[index]

                column_text = self._cell_renderers[column.index].render(node.values[column.index])
                row += [self._elide(column_text, grid_max_widths[index])]
            accumulator += [" ".join(row)]
            return accumulator

        output = []
        grid.visit(None, print_row, output, sort_key = sort_key)
        outfd.write("\r\n".join(output) + "\r\n")

class QuickTextRenderer(TextRenderer):
    def render(self, outfd, grid):
        self._validate_grid(grid)

        # Determine max width of each column
        grid_max_widths = [0] * len(grid.columns)

        # If the grid_max_widths have not been limited,
        headers = []
        for i in range(len(grid.columns)):
            grid_max_widths[i] = max(grid_max_widths[i], len(grid.columns[i].name))
            headers += [grid.columns[i].name]
        outfd.write("|".join(headers) + "\n")

        def print_row(node, outfd):
            outfd.write(">" * grid.path_depth(node))
            for column in grid.columns:
                outfd.write("|" + self._cell_renderers[column.index].render(node.values[column.index]))
            outfd.write("\n")
            outfd.flush()
            return outfd

        grid.populate(print_row, outfd)
