#! /usr/bin/env python3

import sys
import re
import os
import subprocess

from dataclasses import dataclass

@dataclass
class Args:
    files: list[str]
    env: dict

def recursive_file_filter(f):
    if f.endswith(".png"): return False
    if f.endswith(".pdf"): return False
    if ".git" in f: return False
    return True

def eval_args():
    recursive = False
    root_files = []
    env = {}
    for arg in sys.argv[1:]:
        if arg.startswith("--"):
            arg = arg.removeprefix("--")
            if "=" in arg:
                arg, val = arg.split("=")
                env[arg] = val
            else:
                env[arg] = ""
        elif arg.startswith("-"):
            arg = arg.removeprefix("-")
            if arg == "R":
                recursive = True
            else:
                print(f"Unknown argument: -{arg}")
                sys.exit(1)
        else:
            root_files.append(arg)

    files = []
    for root in root_files:
        if os.path.isdir(root):
            for root, dirs, leaves in os.walk(root):
                for file in leaves:
                    full = root + "/" + file
                    if recursive_file_filter(full):
                        files.append(full)
        else:
            files.append(root)
    return Args(files, env)

def ansi_color(code):
    return lambda text: f"\x1b[{code}m{text}\x1b[0m"

red = ansi_color(31)
green = ansi_color(32)
yellow = ansi_color(33)
blue = ansi_color(34)
purple = ansi_color(35)
teal = ansi_color(36)
gray = ansi_color(37)

@dataclass
class File:
    lines: list[str]

    @staticmethod
    def from_lines(lines):
        return File(lines)

    def select(self, lineno):
        return Selection(self, lineno, 1)

    def len(self):
        return len(self.lines)

    def __getitem__(self, i):
        return self.lines[i]

@dataclass
class Selection:
    file: File
    start: int
    len: int

    def jump(self, pat):
        print(f"    Start cursor forward until {teal(pat)}", end="")
        while self.start < self.file.len():
            if "@scrybe" in self.file[self.start]:
                self.start += 1
            elif pat in self.file[self.start]:
                break
            else:
                self.start += 1
        self.len = 1
        print(f"\t\t selected: {blue(self.start + 1)}-{blue(self.start + self.len)}")

    def until(self, pat):
        print(f"    End cursor forward until {teal(pat)}", end="")
        self.len = 1
        while self.start + self.len < self.file.len():
            if pat in self.file[self.start + self.len]:
                break
            self.len += 1
        print(f"\t\t selected: {blue(self.start + 1)}-{blue(self.start + self.len)}")

    def extract(self):
        return self.file.lines[self.start:self.start + self.len]

def main():
    args = eval_args()

    print("Flags:")
    for (flag, val) in args.env.items():
        print(f"  {blue(flag)}\t{teal(val)}")
    print()

    if args.files == []:
        print("No input files")
        sys.exit(1)

    for file in args.files:
        print(f"Reading {teal(file)}")
        with open(file) as f:
            lines = f.readlines()
        _file = File.from_lines(lines)

        for (i, line) in enumerate(lines):
            match = re.search(r"@scrybe\((.*)\)", line)
            if match == None: continue
            instrs = match.groups()[0].split(";")
            _sel = _file.select(i + 1)
            print(f"  Checking command group at line {i + 1}")
            for instr in instrs:
                selection = _sel.extract()
                instr = instr.strip().split(" ")
                cmd = instr[0]
                rest = " ".join(instr[1:])

                if cmd == "if":
                    found = rest in args.env
                    sat = green("sat") if found else red("unsat")
                    print(f"    Guarded by {teal(rest)} ({sat})", end="")
                    if found:
                        print(f"\t\t proceed")
                    else:
                        print(f"\t\t {yellow("skip")}")
                        break

                elif cmd == "not":
                    found = rest in args.env
                    sat = red("sat") if found else green("unsat")
                    print(f"    Guarded by {teal("not " + rest)} ({sat})", end="")
                    if found:
                        print(f"\t\t {yellow("skip")}")
                        break
                    else:
                        print(f"\t\t proceed")

                elif cmd == "jump":
                    _sel.jump(rest)

                elif cmd == "until":
                    _sel.until(rest)

                elif cmd == "grep":
                    for var,val in args.env.items():
                        rest = rest.replace("{{" + var + "}}", val)
                    print(f"    Searching for pattern: {teal(rest)}", end="")
                    proc = subprocess.Popen(f"grep {rest}", shell=True, text=True, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
                    (out, err) = proc.communicate(input="".join(selection), timeout=1)
                    if err != "":
                        print(f"\n      {red(f"ERROR: {err}")}")
                        continue
                    if out == "":
                        print(f"\n      {red("ERROR: No match!")}")
                        continue
                    print(f"\t\t {green("ok")}")

                elif cmd == "diff":
                    print(f"    Diff to {teal(rest)}")
                    proc = subprocess.Popen(f"diff {rest} -", shell=True, text=True, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
                    (out, err) = proc.communicate(input="".join(selection), timeout=1)
                    if err != "":
                        print(f"      {red(f"ERROR: {err}")}")
                        continue
                    if out != "":
                        for lout in out.split("\n"):
                            if len(lout) == 0: continue
                            if lout[0] == ">":
                                print("      " + purple(lout))
                            elif lout[0] == "<":
                                print("      " + yellow(lout))

                elif cmd == "panic":
                    print("    Panic")
                    for line in rest.split("\n"):
                        print("      " + red(line))
                    sys.exit(1)

                elif cmd == "print":
                    print("    Info")
                    for line in selection:
                        print("      " + gray(line.strip('\n')))

                else:
                    print(f"Unknown instruction: {instr}")
                    sys.exit(1)
            print()
        print()

