# -*- coding: utf-8 -*-

#   Copyright (C) 2013, 2015, 2017, 2019, 2020, 2023-2025
#   Rocky Bernstein <rocky@gnu.org>
#
#   This program is free software: you can redistribute it and/or modify
#   it under the terms of the GNU General Public License as published by
#   the Free Software Foundation, either version 3 of the License, or
#   (at your option) any later version.
#
#   This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.
"""Pygments-related terminal formatting"""


import pyficache  # type: ignore
import re
import sys

from pygments import format, highlight, lex
from pygments.console import ansiformat
from pygments.filter import Filter
from pygments.formatter import Formatter
from pygments.formatters import Terminal256Formatter, TerminalFormatter


from pygments.formatters.terminal import TERMINAL_COLORS  # type: ignore
from pygments.lexers import PythonLexer, RstLexer
from pygments.token import (
    Comment,
    Generic,
    Keyword,
    Name,
    Number,
    Operator,
    String,
    Token,
)

from typing import Optional

from trepan.lib.default import DEBUGGER_SETTINGS

# Set up my own color scheme with some additional definitions.
color_scheme = TERMINAL_COLORS.copy()
# color_scheme = {
#     Token:              ('',            ''),
#     Comment:            ('gray',   'brightblack'),
#     Comment.Preproc:    ('cyan',        'brightcyan'),
#     Keyword:            ('blue',    'brightblue'),
#     Keyword.Type:       ('cyan',        'brightcyan'),
#     Operator.Word:      ('magenta',      'brightmagenta'),
#     Name.Builtin:       ('cyan',        'brightcyan'),
#     Name.Function:      ('green',   'brightgreen'),
#     Name.Namespace:     ('_cyan_',      '_brightcyan_'),
#     Name.Class:         ('_green_', '_brightgreen_'),
#     Name.Exception:     ('cyan',        'brightcyan'),
#     Name.Decorator:     ('brightblack',    'gray'),
#     Name.Variable:      ('red',     'brightred'),
#     Name.Constant:      ('red',     'brightred'),
#     Name.Attribute:     ('cyan',        'brightcyan'),
#     Name.Tag:           ('brightblue',        'brightblue'),
#     String:             ('gray',       'yellow'),
#     Number:             ('blue',    'brightblue'),
#     Generic.Deleted:    ('brightred',        'brightred'),
#     Generic.Inserted:   ('green',  'brightgreen'),
#     Generic.Heading:    ('**',         '**'),
#     Generic.Subheading: ('*magenta*',   '*brightmagenta*'),
#     Generic.Prompt:     ('**',         '**'),
#     Generic.Error:      ('brightred',        'brightred'),
# }


color_scheme[Generic.Strong] = ("*black*", "*white*")
color_scheme[Name.Variable] = ("_black_", "_white_")

color_scheme[Generic.Strong] = ("*black*", "*white*")
color_scheme[Name.Variable] = ("_black_", "_white_")
color_scheme[Generic.Emph] = ("blue", "brightcyan")
color_scheme[Token.Literal.String] = ("magenta", "yellow")

# Assume pygments has fixed up the horrible atom colors
# FIXME: change some horrible colors under atom dark
# this is a hack until I get general way to do colorstyle setting
# color_scheme[Token.Comment]  = ('darkgray', 'white')
# color_scheme[Token.Keyword]  = ('darkblue', 'green')
# color_scheme[Token.Number]  = ('darkblue', 'blue')
# color_scheme[Keyword]  = ('darkblue', 'turquoise')
# color_scheme[Number]  = ('darkblue', 'green')

pyficache.dark_terminal_formatter.colorscheme = color_scheme
pyficache.light_terminal_formatter.colorscheme = color_scheme


def format_token(token_type, token_value: str, style: Optional[str]) -> str:
    """
    Decorate ``token_value`` with coloring matching `token_type` and return
    the resulting string.
    """
    if style == "none" or style is None:
        return token_value
    terminal_256_formatter = Terminal256Formatter(style=style)
    return format([[token_type, token_value]], terminal_256_formatter)


Arrow = Name.Variable
Compare = Name.Exception
Const = String
Details = Generic.Emph
Filename = Comment.Preproc
Function = Name.Function
Hex = Number.Hex
Label = Operator.Word
LineNumber = Number
Integer = Number
Offset = Operator
Opcode = Name.Function
Return = Operator.Word
Symbol = String.Symbol
Var = Keyword
Verbatim = String

# Should come last since "Name" is used above
Name = Comment.Preproc


class RstFilter(Filter):
    def __init__(self, **options):
        Filter.__init__(self, **options)
        pass

    def filter(self, lexer, stream):
        last_was_heading_title = ""
        for ttype, value in stream:
            if ttype is Token.Name.Variable:
                value = value[1:-1]
                last_was_heading_title = ""
                pass
            if ttype is Token.Generic.Emph:
                value = value[1:-1]
                last_was_heading_title = ""
                pass
            elif ttype is Token.Generic.Strong:
                value = value[2:-2]
                last_was_heading_title = ""
                pass
            elif ttype is Token.Text and last_was_heading_title and value == "\n":
                value = ""
            elif ttype is Token.Generic.Heading:
                # Remove the underline line following a section header
                # That is remove:
                # Header
                # ------ <- remove this line
                if last_was_heading_title and re.match(r"^(?:=|-)+$", value):
                    value = ""
                    last_was_heading_title = ""
                else:
                    # We store the entire string in case someday we want to
                    # match whether the underline size matches the title size
                    last_was_heading_title = value
                    pass
            yield ttype, value
            pass
        return

    pass


class RSTTerminalFormatter(Formatter):
    r"""
    Format tokens with ANSI color sequences, for output in a text console.
    Color sequences are terminated at newlines, so that paging the output
    works correctly.

    The `get_style_defs()` method doesn't do anything special since there is
    no support for common styles.

    Options accepted:

    `bg`
        Set to ``"light"`` or ``"dark"`` depending on the terminal's background
        (default: ``"light"``).

    `colorscheme`
        A dictionary mapping token types to (lightbg, darkbg) color names or
        ``None`` (default: ``None`` = use builtin colorscheme).
    """

    name = "Terminal"
    aliases = ["terminal", "console"]
    filenames = []

    def __init__(self, **options):
        Formatter.__init__(self, **options)
        self.is_dark_bg = 1 if DEBUGGER_SETTINGS["highlight"] == "dark" else 0
        self.colorscheme = options.get("colorscheme", None) or color_scheme
        self.width = options.get("width", 80)
        self.verbatim = False
        self.in_list = False
        self.column = 1
        self.last_was_nl = False
        return

    def reset(self, width=None):
        self.column = 0
        if width:
            self.width = width
        return

    def format(self, tokensource, outfile):
        # hack: if the output is a terminal and has an encoding set,
        # use that to avoid unicode encode problems
        if (
            not self.encoding
            and hasattr(outfile, "encoding")
            and hasattr(outfile, "isatty")
            and outfile.isatty()
            and sys.version_info < (3,)
        ):
            self.encoding = outfile.encoding
            pass
        self.outfile = outfile
        return Formatter.format(self, tokensource, outfile)

    def write_verbatim(self, text):
        # If we are doing color, then change to the verbatim
        # color
        if self.__class__ != MonoRSTTerminalFormatter:
            cs = self.colorscheme.get(Verbatim)
            color = cs[self.is_dark_bg]
        else:
            color = None
            pass
        return self.write(text, color)

    def write(self, text, color):
        color_text = text
        if color:
            color_text = ansiformat(color, color_text)
        self.outfile.write(color_text)
        self.column += len(text)
        return self.column

    def write_nl(self):
        self.outfile.write("\n")
        self.column = 0
        return self.column

    def reflow_text(self, text, ttype, color):
        # print '%r' % text
        # from trepan.api import debug
        # if u' or ' == text: debug()
        if text == "::" and ttype == Token.Literal.String:
            self.verbatim = "colon-verbatim"
            return
        elif (
            self.verbatim == "colon-verbatim" and ttype == Token.Text and text == "\n\n"
        ):
            self.write_nl()
            self.last_was_nl = True
            return
        elif self.verbatim == "colon-verbatim" and ttype == Token.Text and text == "\n":
            self.write_nl()
            self.last_was_nl = False
            return
        last_last_nl = self.last_was_nl
        if text == "":
            pass
        elif text[-1] == "\n":
            if self.last_was_nl:
                self.write_nl()
                self.write_nl()
                text = text[:-1]
            elif self.verbatim:
                self.write_verbatim(text)
                self.column = 0
                self.verbatim = len(text) >= 2 and text[-2] == "\n"
                self.last_was_nl = True
                return
            else:
                self.write(" ", color)
                text = text[:-1]
                pass
            self.last_was_nl = True
            if "" == text:
                return
            while text[-1] == "\n":
                self.write_nl()
                text = text[:-1]
                if "" == text:
                    return
                pass
            pass
        else:
            self.last_was_nl = False
            pass
        self.in_list = False
        if last_last_nl:
            if " * " == text[0:3]:
                self.in_list = True
            elif "  " == text[0:2]:
                self.verbatim = self.verbatim or True
            pass

        # FIXME: there may be nested lists, tables and so on.
        if self.verbatim:
            self.write_verbatim(text)
        elif self.in_list:
            # FIXME:
            self.write(
                text,
                color,
            )
        else:
            words = re.compile("[ \t]+").split(text)
            for word in words[:-1]:
                # print "column: %d, word %s" % (self.column, word)
                if (self.column + len(word) + 1) >= self.width:
                    self.write_nl()
                    pass
                if not (self.column == 0 and word == ""):
                    self.write(word + " ", color)
                    pass
                pass
            if words[-1]:
                # print "column2: %d, word %r" % (self.column, words[-1])
                if (self.column + len(words[-1])) >= self.width:
                    self.write_nl()
                    pass
                self.write(words[-1], color)
                pass
            pass
        return

    def format_unencoded(self, tokensource, outfile):
        for ttype, text in tokensource:
            # print("ttype: ", ttype, "text: ", text)
            color = self.colorscheme.get(ttype)
            resolved_type = ttype
            while color is None:
                resolved_type = resolved_type[:-1]
                color = self.colorscheme.get(resolved_type)
                pass
            if color:
                color = color[self.is_dark_bg]
            self.reflow_text(text, ttype, color)
            pass
        return

    pass


class MonoRSTTerminalFormatter(RSTTerminalFormatter):
    def format_unencoded(self, tokensource, outfile):
        for ttype, text in tokensource:
            if ttype is Token.Name.Variable:
                text = f'"{text}"'
                pass
            elif ttype is Token.Generic.Emph:
                text = f"*{text}*"
                pass
            elif ttype is Token.Generic.Strong:
                text = text.upper()
                pass
            self.reflow_text(text, ttype, None)
            pass
        return

    pass


class MonoTerminalFormatter(TerminalFormatter):
    def format_unencoded(self, tokensource, outfile):
        for ttype, text in tokensource:
            if ttype is Token.Name.Variable:
                text = f'"{text}"'
                pass
            elif ttype is Token.Generic.Emph:
                text = f"*{text}*"
                pass
            elif ttype is Token.Generic.Strong:
                text = text.upper()
                pass
            pass

            outfile.write(text)
            pass
        return

    pass


rst_lex = RstLexer()
rst_filt = RstFilter()
rst_lex.add_filter(rst_filt)
color_tf = RSTTerminalFormatter(colorscheme=color_scheme)
mono_tf = MonoRSTTerminalFormatter()
python_lexer = PythonLexer()


def format_python(python_str: str, style) -> str:
    """Add terminial formatting for a Python string
    ``python_str``, using pygments style ``style``.
    """
    if style is None:
        return python_str
    terminal_formatter = Terminal256Formatter(style=style)
    return highlight(python_str, python_lexer, terminal_formatter)


def rst_text(text, mono, width=80):
    if mono:
        tf = mono_tf
    else:
        tf = color_tf
        pass
    tf.reset(width)
    return highlight(text, rst_lex, tf)


if __name__ == "__main__":

    def show_it(string, tf, width=80):
        tf.reset(width)
        print("=" * 30)
        for t in lex(string, rst_lex):
            print(t)
            pass
        print("-" * 30)
        print(highlight(string, rst_lex, tf))
        return

    # string = '`A` very *emphasis* **strong** `code`'
    # show_it(string, color_tf)
    # show_it(string, mono_tf)

    test_string = """
Heading
-------

This is an example to show off *reformatting.*
We have several lines
here which should be reflowed.

But paragraphs should be respected.

   And verbatim
   text should not be
   touched

End of test.
"""
    #
    #    rst_tf = RSTTerminalFormatter(colorscheme=color_scheme)
    #    show_it(test_string, rst_tf)

    rst_tf = MonoRSTTerminalFormatter()
    show_it(test_string, rst_tf, 30)

    text = """**break** [*location*] [if *condition*]]

With a line number argument, set a break there in the current file.
With a function name, set a break at first executable line of that
function.  Without argument, set a breakpoint at current location.  If
a second argument is `if`, subsequent arguments given an expression
which must evaluate to true before the breakpoint is honored.

The location line number may be prefixed with a filename or module
name and a colon. Files is searched for using *sys.path*, and the `.py`
suffix may be omitted in the file name.

Examples:
---------

   break              # Break where we are current stopped at
   break if i < j     # Break at current line if i < j
   break 10           # Break on line 10 of the file we are
                      #  currently stopped at
   break os.path.join # Break in function os.path.join
   break os.path:45   # Break on line 45 of os.path
   break myfile:5 if i < j # Same as above but only if i < j
   break myfile.py:45 # Break on line 45 of myfile.py
   break myfile:45    # Same as above.
"""

    show_it(text, rst_tf)
    rst_tf = RSTTerminalFormatter(colorscheme=color_scheme)
    show_it(text, rst_tf)

    text = """Examples:
---------
::

    disasm
    disasm *10, *20
    """
    show_it(text, rst_tf)
