import os
import datetime
import inspect
import threading

THROBBER = ["⠦", "⠇", "⠋", "⠙", "⠸", "⠴"]

class BojanConsole:
    def __init__(self, printing=True, progress_bar=True) -> None:
        self.log = ""
        self.progress_bar = ProgressBar()
        self.printing = printing
        self.throbber_index = 0
        self.log_throbber()
        
    def update_throbber(self):
        self.throbber_index += 1
        if self.throbber_index >= len(THROBBER):
            self.throbber_index = 0
        return THROBBER[self.throbber_index]

    def log_throbber(self):
        print(self.update_throbber(), end="\r")
        # Call this function every 0.1 seconds
        threading.Timer(0.1, self.log_throbber).start()

    def log_plain(self, message):
        date = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
        prefix = "INFO"
        if "⚠️" in message:
            prefix = "WARN"
        if "❌" in message:
            prefix = "ERROR"
        if "💬" in message:
            prefix = "DEBUG"
        if "✅" in message:
            prefix = "DONE"

        # Get caller information
        caller_frame = inspect.stack()[1]
        caller_file = caller_frame.filename
        caller_line = caller_frame.lineno
        caller_method = caller_frame.function
        caller_class = caller_frame.frame.f_locals.get('self', None).__class__.__name__ if 'self' in caller_frame.frame.f_locals else None
        
        
        caller_tree = ""
        if caller_file:
            caller_tree += f"{caller_file}"
        if caller_line:
            caller_tree += f":{caller_line}"
        if caller_class:
            caller_tree += f" > {caller_class}"
        if caller_method:
            caller_tree += f" > {caller_method}"
    
        self.log += f"{message} [{date}] ({caller_tree})\n"
        if self.printing:
            print(message)
        
    def print(self, message, identifier="🕸️", depth=0):
        padding = "\t" * depth
        message = f"{padding}{identifier} {message}"
        self.log_plain(message)

    def debug(self, message, depth=0):
        if(type(message) != str):
            message = str(message)
        self.print(bcolors.GREY + message + bcolors.END, "💬", depth=depth)
    
    def error(self, message, depth=0):
        if(type(message) != str):
            message = str(message)
        self.print(bcolors.RED + message + bcolors.END, "❌", depth=depth)
        
    def success(self, message, depth=0):
        if(type(message) != str):
            message = str(message)
        self.print(bcolors.GREEN + message + bcolors.END, "✅", depth=depth)

    def warning(self, message, depth=0):
        if(type(message) != str):
            message = str(message)
        self.print(bcolors.YELLOW + message + bcolors.END, "⚠️", depth=depth)

    def dictionary(self, d, depth=0):
        depth_emoji = ["🏰", "🛖", "🌲", "🐦", "🐛", "🧬"]
        for key, value in d.items():
            if isinstance(value, dict):
                self.print(f"{bcolors.ITALIC}{bcolors.WHITE if depth == 0 else ''}{key}{bcolors.END}:", depth_emoji[depth], depth)
                self.dictionary(value, depth + 1)
            else:
                self.print(f"{key}: {value}", depth_emoji[depth], depth)
    
    def print_parameter(self, section, parameters, icon="🔧"):
        self.log_plain(f"{icon} {bcolors.BOLD}{bcolors.YELLOW}{section}{bcolors.END}:")
        for key, value in parameters.items():
            self.log_plain(f"\t{bcolors.BLUE}{key}{bcolors.END} : {bcolors.BLUE}{value}{bcolors.END}")

    def print_parameters(self, mappings, settings):
        self.log_plain(f"STARTING 🌱 {bcolors.BOLD}{bcolors.GREEN}VELES{bcolors.END}🌱 WITH FOLLOWING PARAMETERS:")
        self.print_parameter("Settings", settings, "⚙️")
        self.print_parameter("Mappings", mappings, "🗺️")
    
    def strip_colors(self, string):
        return string.replace(bcolors.END, "").replace(bcolors.BOLD, "").replace(bcolors.ITALIC, "").replace(bcolors.URL, "").replace(bcolors.BLINK, "").replace(bcolors.BLINK2, "").replace(bcolors.SELECTED, "").replace(bcolors.BLACK, "").replace(bcolors.RED, "").replace(bcolors.GREEN, "").replace(bcolors.YELLOW, "").replace(bcolors.BLUE, "").replace(bcolors.VIOLET, "").replace(bcolors.BEIGE, "").replace(bcolors.WHITE, "").replace(bcolors.BLACKBG, "").replace(bcolors.REDBG, "").replace(bcolors.GREENBG, "").replace(bcolors.YELLOWBG, "").replace(bcolors.BLUEBG, "").replace(bcolors.VIOLETBG, "").replace(bcolors.BEIGEBG, "").replace(bcolors.WHITEBG, "").replace(bcolors.GREY, "").replace(bcolors.RED2, "").replace(bcolors.GREEN2, "").replace(bcolors.YELLOW2, "").replace(bcolors.BLUE2, "").replace(bcolors.VIOLET2, "").replace(bcolors.BEIGE2, "").replace(bcolors.WHITE2, "").replace(bcolors.GREYBG, "").replace(bcolors.REDBG2, "").replace(bcolors.GREENBG2, "").replace(bcolors.YELLOWBG2, "").replace(bcolors.BLUEBG2, "").replace(bcolors.VIOLETBG2, "").replace(bcolors.BEIGEBG2, "").replace(bcolors.WHITEBG2, "")
    
    def save(self, filename):
        if "/" in filename or "\\" in filename:
            os.makedirs(os.path.dirname(filename), exist_ok=True)
        with open(filename, "w+", encoding="utf-8") as file:
            file.write(self.strip_colors(self.log))

class ProgressBar:
    def __init__(self, total, length=50) -> None:
        self.total = total
        self.length = length
        self.progress = 0
        self.update(0)
    
    def update(self, progress):
        self.progress = progress
    
    def print(self):
        progress = int(self.progress/self.total*self.length)
        print(f"[{'#'*progress}{'-'*(self.length-progress)}] {progress/self.length*100:.2f}%", end="\r")

class bcolors:
    END      = '\33[0m'
    BOLD     = '\33[1m'
    ITALIC   = '\33[3m'
    URL      = '\33[4m'
    BLINK    = '\33[5m'
    BLINK2   = '\33[6m'
    SELECTED = '\33[7m'

    BLACK  = '\33[30m'
    RED    = '\33[31m'
    GREEN  = '\33[32m'
    YELLOW = '\33[33m'
    BLUE   = '\33[34m'
    VIOLET = '\33[35m'
    BEIGE  = '\33[36m'
    WHITE  = '\33[37m'

    BLACKBG  = '\33[40m'
    REDBG    = '\33[41m'
    GREENBG  = '\33[42m'
    YELLOWBG = '\33[43m'
    BLUEBG   = '\33[44m'
    VIOLETBG = '\33[45m'
    BEIGEBG  = '\33[46m'
    WHITEBG  = '\33[47m'

    GREY    = '\33[90m'
    RED2    = '\33[91m'
    GREEN2  = '\33[92m'
    YELLOW2 = '\33[93m'
    BLUE2   = '\33[94m'
    VIOLET2 = '\33[95m'
    BEIGE2  = '\33[96m'
    WHITE2  = '\33[97m'

    GREYBG    = '\33[100m'
    REDBG2    = '\33[101m'
    GREENBG2  = '\33[102m'
    YELLOWBG2 = '\33[103m'
    BLUEBG2   = '\33[104m'
    VIOLETBG2 = '\33[105m'
    BEIGEBG2  = '\33[106m'
    WHITEBG2  = '\33[107m'