from __future__ import annotations

from dataclasses import dataclass
from typing import Any
from typing import Callable
from typing import List
from typing import Optional

import pwndbg.color as color


@dataclass
class Property:
    """
    A (property name, property value) pair
    with optional extra information.

    Used by the PropertyPrinter.
    """

    name: str
    value: Any
    extra: str | List[str] = ""
    is_addr: bool = False
    use_hex: bool = True


class PropertyPrinter:
    """
    When you have (property name, property value) pairs
    that you want to print, each on a new line.
    """

    def __init__(
        self,
        *,
        name_color_func: Optional[Callable[[str], str]] = None,
        value_color_func: Optional[Callable[[str], str]] = None,
        section_color_func: Optional[Callable[[str], str]] = None,
        indent_size: int = 2,
    ):
        self.name_color_func = name_color_func
        if self.name_color_func is None:
            self.name_color_func = color.bold

        self.value_color_func = value_color_func
        if self.value_color_func is None:
            self.value_color_func = color.yellow

        self.section_color_func = section_color_func
        if self.section_color_func is None:
            self.section_color_func = color.green

        self.indent_size = indent_size

        self.indent_level = 0
        self.padding = 2
        self.text = ""

    def add(self, prop_group: List[Property]) -> None:
        """
        Add a group of properties that should be aligned.
        """
        # Transform prop values to string representation
        for prop in prop_group:
            if isinstance(prop.value, int):
                if prop.use_hex:
                    prop.value = hex(prop.value)
                else:
                    prop.value = str(prop.value)

        # Get max lengths to calculate proper ljust
        # + 1 to account for the ":"
        max_name_len = max(len(prop.name) for prop in prop_group) + 1
        # max_value_len = max(len(prop.value) for prop in prop_group)
        # Use constant so it works between different groups
        max_value_len = 16

        indentation_str = self.indent_level * self.indent_size * " "
        padding_str = self.padding * " "
        name_pad_str = max_name_len * " "
        val_pad_str = max_value_len * " "
        extra_list_pad_str = indentation_str + name_pad_str + padding_str + val_pad_str

        for prop in prop_group:
            self.text += (
                indentation_str
                + color.ljust_colored(self.name_color_func(prop.name) + ":", max_name_len)
                + padding_str
            )

            if prop.is_addr:
                base = 16 if prop.use_hex else 10
                colored_val = color.memory.get(int(prop.value, base))
            else:
                colored_val = self.value_color_func(prop.value)

            self.text += color.ljust_colored(colored_val, max_value_len)

            if isinstance(prop.extra, str):
                self.text += "  " + prop.extra
            else:
                # list of strings, we want each one under the other
                assert isinstance(prop.extra, list)
                assert len(prop.extra) > 1

                self.text += "  " + prop.extra[0]
                for i in range(1, len(prop.extra)):
                    self.text += "\n"
                    self.text += extra_list_pad_str
                    self.text += "  " + prop.extra[i]

            self.text += "\n"

    def dump(self) -> str:
        """
        Return the built up string.
        """
        return self.text

    def print(self) -> None:
        """
        Print the built up string.
        """
        print(self.text, end="")

    def clear(self) -> None:
        """
        Clear the built up string.
        """
        self.text = ""

    def indent(self) -> None:
        """
        Increase indentation level by one.
        """
        self.indent_level += 1

    def unindent(self) -> None:
        """
        Decrease indentation level by one.
        """
        self.indent_level -= 1
        assert self.indent_level >= 0

    def write(self, string: str) -> None:
        """
        Write raw string to the PropertyPrinter.
        """
        self.text += string

    def start_section(self, title: str, preamble: str = "") -> None:
        """
        Start a named section of properties that will have
        increased indentation.

        Don't forget to call end_section()!
        """
        self.text += " " * self.indent_level * self.indent_size
        self.text += self.section_color_func(title)

        if preamble:
            self.text += "\n"
            self.text += " " * (self.indent_level + 1) * self.indent_size
            self.text += preamble

        self.text += "\n"
        self.indent()

    def end_section(self) -> None:
        """
        End a section.
        """
        self.unindent()

    def set_padding(self, pad: int) -> None:
        """
        Set the distance between the end of the longest
        property name and the start of the value column.
        """
        self.padding = pad
