#!/usr/bin/python3.13

from __future__ import print_function

import sys, os, string, re, signal, errno


# redefine readline to pass invalidly encoded characters unchanged, if possible
if hasattr(sys.stdin, 'reconfigure') and hasattr(sys.stdout, 'reconfigure'):
    # at least python3.7
    sys.stdin.reconfigure(errors='surrogateescape')
    sys.stdout.reconfigure(errors='surrogateescape')
    myreadline = sys.stdin.readline
    myprint = print
else:
    if hasattr(sys.stdin, 'buffer'):
        # python
        buffer_reader = sys.stdin.buffer
    else:
        buffer_reader = sys.stdin
    def myreadline():
        for line in buffer_reader:
            try:
                decoded = line.decode('utf-8', errors='surrogateescape')
            except (UnicodeDecodeError, LookupError):
                decoded = line.decode('utf-8', errors='ignore')
            return decoded
        return ''
    def myprint(x):
        try:
            print(x)
        except UnicodeEncodeError:
            print(x.encode('utf-8', errors='replace').decode('utf-8'))

#some default definitions
colours = {
            'none'       :    "",
            'default'    :    "\033[0m",
            'bold'       :    "\033[1m",
            'underline'  :    "\033[4m",
            'blink'      :    "\033[5m",
            'reverse'    :    "\033[7m",
            'concealed'  :    "\033[8m",

            'black'      :    "\033[30m", 
            'red'        :    "\033[31m",
            'green'      :    "\033[32m",
            'yellow'     :    "\033[33m",
            'blue'       :    "\033[34m",
            'magenta'    :    "\033[35m",
            'cyan'       :    "\033[36m",
            'white'      :    "\033[37m",

            'on_black'   :    "\033[40m", 
            'on_red'     :    "\033[41m",
            'on_green'   :    "\033[42m",
            'on_yellow'  :    "\033[43m",
            'on_blue'    :    "\033[44m",
            'on_magenta' :    "\033[45m",
            'on_cyan'    :    "\033[46m",
            'on_white'   :    "\033[47m",

            'beep'       :    "\007",
            'previous'   :    "prev",
            'unchanged'  :    "unchanged",

            # non-standard attributes, supported by some terminals
            'dark'         :    "\033[2m",
            'italic'       :    "\033[3m",
            'rapidblink'   :    "\033[6m",
            'strikethrough':    "\033[9m",

            # aixterm bright color codes
            # prefixed with standard ANSI codes for graceful failure
            'bright_black'      :    "\033[30;90m",
            'bright_red'        :    "\033[31;91m",
            'bright_green'      :    "\033[32;92m",
            'bright_yellow'     :    "\033[33;93m",
            'bright_blue'       :    "\033[34;94m",
            'bright_magenta'    :    "\033[35;95m",
            'bright_cyan'       :    "\033[36;96m",
            'bright_white'      :    "\033[37;97m",

            'on_bright_black'   :    "\033[40;100m", 
            'on_bright_red'     :    "\033[41;101m",
            'on_bright_green'   :    "\033[42;102m",
            'on_bright_yellow'  :    "\033[43;103m",
            'on_bright_blue'    :    "\033[44;104m",
            'on_bright_magenta' :    "\033[45;105m",
            'on_bright_cyan'    :    "\033[46;106m",
            'on_bright_white'   :    "\033[47;107m",
            }


# ignore ctrl C - this is not ideal for standalone grcat, but
# enables propagating SIGINT to the other subprocess in grc
signal.signal(signal.SIGINT, signal.SIG_IGN)

def add2list(clist, m, patterncolour):
    for group in range(0, len(m.groups()) +1):
        if group < len(patterncolour):
            clist.append((m.start(group), m.end(group), patterncolour[group]))
        else:
            clist.append((m.start(group), m.end(group), patterncolour[0]))

def get_colour(x):
    if x in colours:
        return colours[x]
    elif len(x)>=2 and x[0]=='"' and x[-1]=='"':
        return eval(x)
    else:
        raise ValueError('Bad colour specified: '+x)


home = []
conffile = None
xdg_config = os.environ.get('XDG_CONFIG_HOME')
xdg_data = os.environ.get('XDG_DATA_HOME')
home = os.environ.get('HOME')
if home and not xdg_config:
    xdg_config = home + '/.config'
if home and not xdg_data:
    xdg_data = home + '/.local/share'

conffilepath = [""]
if xdg_data:
    conffilepath += [xdg_data + '/grc/']
if xdg_config:
    conffilepath += [xdg_config + '/grc/']
if home:
    conffilepath += [home + '/.grc/']
conffilepath += ['/usr/local/share/grc/', '/usr/share/grc/']
if len(sys.argv) != 2:
    sys.stderr.write("You are not supposed to call grcat directly, but the usage is: grcat conffile\n")
    sys.exit(1)

conffile_arg = sys.argv[1] # tentative conffile
for i in conffilepath:
    # test if conffile exists, it can be also a pipe
    if os.path.exists(i+conffile_arg) and not os.path.isdir(i+conffile_arg):
        conffile = i+conffile_arg
        break

if not conffile:
    sys.stderr.write("config file [%s] not found\n" % sys.argv[1])
    sys.exit(1)

regexplist = []

f = open(conffile, "r")
is_last = 0
split = str.split
lower = str.lower
letters = string.ascii_letters
while not is_last:
    ll = {'count':"more"}
    while 1:
        l = f.readline()
        if l == "": 
            is_last = 1
            break
        if l[0] == "#" or l[0] == '\012':
            continue
        if not l[0] in letters:
            break
        fields = split(l.rstrip('\r\n'), "=", 1)
        if len(fields) != 2:
            sys.stderr.write('Error in configuration, I expect keyword=value line\n')
            sys.stderr.write('But I got instead:\n')
            sys.stderr.write(repr(l))
            sys.stderr.write('\n')
            sys.exit(1)
        keyword, value = fields
        keyword = lower(keyword)
        if keyword in  ('colors', 'colour', 'color'):
            keyword = 'colours'
        if not keyword in ["regexp", "colours", "count", "command", "skip", "replace", "concat"]:
            raise ValueError("Invalid keyword")
        ll[keyword] = value

    # Split string into one string per regex group
    # e.g. split "brown bold, red" into "brown bold" and
    # "red"
    #colstrings = []
    #for colgroup in split(ll['colours'], ','):
    #    colourlist = split(colgroup)
    #    c = ""
    #        for i in colourlist :
    #        c = c + colours[i]
    #    colstrings.append(c)
    # do not try to understand the optimized form below :-)
    if 'colours' in ll:
        colstrings = list(
                        [''.join([get_colour(x) for x in split(colgroup)]) for colgroup in split(ll['colours'], ',')]
                        )
        ll['colours'] = colstrings

    cs = ll['count']
    if 'regexp' in ll:
        ll['regexp'] = re.compile(ll['regexp']).search
        regexplist.append(ll)

prevcolour = colours['default']
prevcount = "more"
blockflag = 0

while 1:
    line = myreadline()
    if line == "" :
        break
    if line[-1] in '\r\n':
        line = line[:-1]
    clist = []
    skip = 0
    for pattern in regexplist:
        pos = 0
        currcount = pattern['count']
        was_replace = 0 # watch replacements, replace regexp only one per line, to avoid infinte loops if the replacement matches the regexp again
        while 1:
            m = pattern['regexp'](line, pos)
            if m:
                if 'replace' in pattern:
                    if was_replace:
                        break
                    line = re.sub(m.re, pattern['replace'], line)
                    was_replace = 1
                if 'colours' in pattern:
                    if currcount == "block":
                        blockflag = 1
                        blockcolour = pattern['colours'][0]
                        currcount = "stop"
                        break
                    elif currcount == "unblock":
                        blockflag = 0
                        blockcolour = colours['default']
                        currcount = "stop"
                    add2list(clist, m, pattern['colours'])
                    if currcount == "previous":
                        currcount = prevcount
                    if currcount == "stop":
                        break
                    if currcount == "more":
                        prevcount = "more"
                        newpos = m.end(0)
                        # special case, if the regexp matched but did not consume anything,
                        # advance the position by 1 to escape endless loop
                        if newpos == pos:
                            pos += 1
                        else:
                            pos = newpos
                    else:
                        prevcount = "once"
                        pos = len(line)
                if 'concat' in pattern:
                    with open(pattern['concat'], 'a') as f :
                        f.write(line + '\n')
                    if 'colours' not in pattern:
                        break
                if 'command' in pattern:
                    os.system(pattern['command'])
                    if 'colours' not in pattern:
                        break
                if 'skip' in pattern:
                    skip = pattern['skip'] in ("yes", "1", "true")
                    if 'colours' not in pattern:
                        break
            else: 
                break
        if m and currcount == "stop":
            prevcount = "stop"
            break
    if len(clist) == 0:
        prevcolour = colours['default']
    first_char = 0
    last_char = 0
    length_line = len(line)
    if blockflag == 0:
        cline = (length_line+1)*[colours['default']]
        for i in clist:
            # each position in the string has its own colour
            if i[2] == "prev":
                cline[i[0]:i[1]] = [colours['default']+prevcolour]*(i[1]-i[0])
            elif i[2] != "unchanged":
                cline[i[0]:i[1]] = [colours['default']+i[2]]*(i[1]-i[0])
            if i[0] == 0:
                first_char = 1
                if i[2] != "prev":
                    prevcolour = i[2]
            if i[1] == length_line:
                last_char = 1
        if first_char == 0 or last_char == 0:
            prevcolour = colours['default']
    else:
        cline = (length_line+1)*[blockcolour]
    nline = ""
    clineprev = ""
    if not skip:
        for i in range(len(line)):
            if cline[i] == clineprev: 
                nline = nline + line[i]
            else:
                nline = nline + cline[i] + line[i]
                clineprev = cline[i]
        nline = nline + colours['default']
        try:
            myprint(nline)
        except IOError as e:
            if e.errno == errno.EPIPE:
                break
            else:
                raise

