import cv2
import numpy as np
import os
import time
import sys
import multiprocessing as mp
from colorama import Fore, Style, init

# Obsługa klawiatury: użyj msvcrt na Windows, fallback dla Unix
try:
    import msvcrt
    _HAS_MSVCRT = True
except Exception:
    import select
    import tty
    import termios
    _HAS_MSVCRT = False

# Inicjalizacja colorama
init(autoreset=True)

# Informacja developerska: tryb kolorowy jest przedpremierowy (previrew)
# i może powodować problemy z wydajnością w niektórych konfiguracjach.
# Ten dopisek ma charakter ostrzeżenia - testuj i raportuj ewentualne regresje.
#
# Uwaga: zachowano słowo "previrew" zgodnie z oznaczeniem wersji.

# Zestawy znaków ASCII – różne style do wyboru
ASCII_SETS = {
    "standard": "@%#*+=-:. ",
    "detailed": "$@B%8&WM#*oahkbdpqwmZO0QLCJUYXzcvunxrjft/\\|()1{}[]?-_+~<>i!lI;:,\"^`'. ",
    "simple": "#=-. ",
    "block": "▓▒░ ",
    "minimal": "██▉▊▋▌▅▴▃▂▁ "
}

# Funkcja konwertująca fragment obrazu na ASCII
def frame_to_ascii(frame, width, use_color=False, outline_only=False, invert_colors=False,
                   brightness=1.0, contrast=1.0, sharpen=False, char_set="standard"):
    try:
        # Odwrócenie poziome (usuwa efekt lustra)
        frame = cv2.flip(frame, 1)

        # Dostosowanie jasności i kontrastu
        frame = cv2.convertScaleAbs(frame, alpha=contrast, beta=(brightness - 1.0) * 100)

        # Wyostrzanie (opcjonalne)
        if sharpen:
            kernel = np.array([[0, -1, 0],
                               [-1, 5, -1],
                               [0, -1, 0]])
            frame = cv2.filter2D(frame, -1, kernel)

        # Obliczenie nowej wysokości z zachowaniem proporcji (współczynnik ~0.55 dla terminala)
        h, w = frame.shape[:2]
        ratio = h / w / 0.55
        new_height = max(1, int(width * ratio))
        resized = cv2.resize(frame, (width, new_height), interpolation=cv2.INTER_AREA)

        # Konwersja do szarości
        gray = cv2.cvtColor(resized, cv2.COLOR_BGR2GRAY)

        # Odwrócenie kolorów (opcjonalne)
        if invert_colors:
            gray = 255 - gray

        chars = ASCII_SETS.get(char_set, ASCII_SETS["standard"])
        ascii_image = []

        for i in range(gray.shape[0]):
            line = []
            for j in range(gray.shape[1]):
                pixel = int(gray[i, j])
                char = chars[pixel * len(chars) // 256]

                if outline_only:
                    char = "#" if pixel < 128 else " "

                if use_color:
                    b, g, r = resized[i, j]
                    line.append(f"\033[38;2;{int(r)};{int(g)};{int(b)}m{char}")
                else:
                    line.append(char)
            ascii_image.append("".join(line))

        return "\n".join(ascii_image)
    except Exception as e:
        return f"Error rendering frame: {e}"

# Funkcja do renderowania fragmentu ekranu w wielu rdzeniach
def render_chunk(args):
    chunk, width, use_color, outline_only, invert_colors, brightness, contrast, sharpen, char_set = args
    return frame_to_ascii(
        chunk, width, use_color, outline_only, invert_colors,
        brightness, contrast, sharpen, char_set
    )

# Główna klasa programu
class ASCIICam:
    def __init__(self):
        self.width = 120
        self.use_color = False
        self.outline_only = False
        self.invert_colors = False
        self.brightness = 1.0
        self.contrast = 1.0
        self.sharpen = False
        self.split = 4
        self.target_fps = 30
        self.show_fps = True
        self.camera_index = 0
        self.char_set = "standard"  # domyślny zestaw znaków

    def main_menu(self):
        while True:
            os.system('cls' if os.name == 'nt' else 'clear')
            print("═════ ASCII CAMERA ═════")
            print("1. Start kamery")
            print("2. Ustawienia")
            print("3. Wyjście")
            choice = input("Wybierz opcję: ")

            if choice == "1":
                self.start_camera()
            elif choice == "2":
                self.settings_menu()
            elif choice == "3":
                print("Do zobaczenia!")
                break
            else:
                print("Niepoprawny wybór!")
                input("Naciśnij Enter...")

    def settings_menu(self):
        while True:
            os.system('cls' if os.name == 'nt' else 'clear')
            print("═════ USTAWIENIA ═════")
            print(f"1. Szerokość ASCII      : {self.width}")
            print(f"2. Zestaw znaków        : {self.char_set}")
            print(f"3. Kolory               : {'WŁ' if self.use_color else 'WYŁ'}")
            print(f"4. Tylko kontur         : {'WŁ' if self.outline_only else 'WYŁ'}")
            print(f"5. Odwróć kolory        : {'WŁ' if self.invert_colors else 'WYŁ'}")
            print(f"6. Jasność              : {self.brightness:.2f}")
            print(f"7. Kontrast             : {self.contrast:.2f}")
            print(f"8. Wyostrzanie          : {'WŁ' if self.sharpen else 'WYŁ'}")
            print(f"9. Liczba rdzeni        : {self.split}")
            print(f"10. Cel FPS             : {self.target_fps}")
            print(f"11. Pokazuj FPS         : {'WŁ' if self.show_fps else 'WYŁ'}")
            print(f"12. Kamera (indeks)     : {self.camera_index}")
            print("13. Powrót")

            choice = input("Wybierz opcję: ")

            if choice == "1":
                try:
                    w = int(input("Podaj szerokość ASCII (10–300): "))
                    self.width = max(10, min(300, w))
                except ValueError:
                    print("Niepoprawna wartość!")
                    input("Naciśnij Enter...")

            elif choice == "2":
                print("Dostępne zestawy:")
                for i, name in enumerate(ASCII_SETS.keys(), 1):
                    print(f"{i}. {name}")
                try:
                    idx = int(input("Wybierz numer zestawu: ")) - 1
                    keys = list(ASCII_SETS.keys())
                    if 0 <= idx < len(keys):
                        self.char_set = keys[idx]
                except (ValueError, IndexError):
                    print("Niepoprawny wybór!")
                    input("Naciśnij Enter...")

            elif choice == "3":
                self.use_color = not self.use_color
            elif choice == "4":
                self.outline_only = not self.outline_only
            elif choice == "5":
                self.invert_colors = not self.invert_colors
            elif choice == "6":
                try:
                    b = float(input("Jasność (np. 0.5–2.0): "))
                    self.brightness = max(0.1, min(3.0, b))
                except ValueError:
                    print("Niepoprawna wartość!")
                    input("Naciśnij Enter...")
            elif choice == "7":
                try:
                    c = float(input("Kontrast (np. 0.5–2.0): "))
                    self.contrast = max(0.1, min(3.0, c))
                except ValueError:
                    print("Niepoprawna wartość!")
                    input("Naciśnij Enter...")
            elif choice == "8":
                self.sharpen = not self.sharpen
            elif choice == "9":
                try:
                    s = int(input("Liczba rdzeni (1–16): "))
                    self.split = max(1, min(16, s))
                except ValueError:
                    print("Niepoprawna wartość!")
                    input("Naciśnij Enter...")
            elif choice == "10":
                try:
                    fps = int(input("Cel FPS (1–60): "))
                    self.target_fps = max(1, min(60, fps))
                except ValueError:
                    print("Niepoprawna wartość!")
                    input("Naciśnij Enter...")
            elif choice == "11":
                self.show_fps = not self.show_fps
            elif choice == "12":
                try:
                    idx = int(input("Indeks kamery (0, 1, ...): "))
                    self.camera_index = max(0, idx)
                except ValueError:
                    print("Niepoprawna wartość!")
                    input("Naciśnij Enter...")
            elif choice == "13":
                break
            else:
                print("Niepoprawny wybór!")
                input("Naciśnij Enter...")

    def start_camera(self):
        cap = None
        pool = None
        try:
            cap = cv2.VideoCapture(self.camera_index)
            if not cap.isOpened():
                print(Fore.RED + "Nie udało się uruchomić kamery!")
                input("Naciśnij Enter...")
                return

            # Ustawienie rozdzielczości kamery (opcjonalne, nie zawsze wspierane)
            cap.set(cv2.CAP_PROP_FRAME_WIDTH, 640)
            cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 480)

            pool = mp.Pool(processes=self.split)

            # Przygotowanie obsługi klawiatury (nieblokująco)
            old_term_settings = None
            if not _HAS_MSVCRT:
                try:
                    fd = sys.stdin.fileno()
                    old_term_settings = termios.tcgetattr(fd)
                    tty.setcbreak(fd)
                except Exception:
                    old_term_settings = None

            frame_count = 0
            start_time = time.time()
            last_time = start_time

            while True:
                ret, frame = cap.read()
                if not ret:
                    print(Fore.RED + "Nie udało się odczytać klatki z kamery!")
                    break

                # Dzielenie obrazu na fragmenty
                chunks = np.array_split(frame, self.split, axis=0)
                args = [
                    (
                        chunk, self.width, self.use_color, self.outline_only,
                        self.invert_colors, self.brightness, self.contrast,
                        self.sharpen, self.char_set
                    )
                    for chunk in chunks
                ]

                try:
                    results = pool.map(render_chunk, args)
                except Exception as e:
                    print(Fore.RED + f"Błąd podczas renderowania: {e}")
                    break

                ascii_frame = "\n".join(results)

                # Obliczanie FPS
                frame_count += 1
                current_time = time.time()
                elapsed = current_time - last_time
                fps = 1.0 / elapsed if elapsed > 0 else 0

                # Ograniczenie FPS
                target_frame_time = 1.0 / self.target_fps
                sleep_time = target_frame_time - elapsed
                if sleep_time > 0:
                    time.sleep(sleep_time)
                last_time = time.time()

                # Czyszczenie terminala
                os.system('cls' if os.name == 'nt' else 'clear')

                # Dodanie FPS (opcjonalnie)
                if self.show_fps:
                    ascii_frame += f"\n{Fore.YELLOW}FPS: {fps:.1f}{Style.RESET_ALL}"

                print(ascii_frame)

                # Sprawdź naciśnięcie klawisza 'q' w terminalu (nieblokująco)
                try:
                    if _HAS_MSVCRT:
                        if msvcrt.kbhit():
                            ch = msvcrt.getwch()
                            if ch.lower() == 'q':
                                break
                    else:
                        dr, _, _ = select.select([sys.stdin], [], [], 0)
                        if dr:
                            ch = sys.stdin.read(1)
                            if ch.lower() == 'q':
                                break
                except Exception:
                    # Jeśli sprawdzanie klawiatury zawiedzie, pozostaw sprawdzenie przez OpenCV
                    pass

                # Dodatkowe sprawdzenie z użyciem OpenCV (jeśli dostępne okno)
                if cv2.waitKey(1) & 0xFF == ord('q'):
                    break

        except Exception as e:
            print(Fore.RED + f"Wystąpił błąd: {e}")
            input("Naciśnij Enter, aby wrócić do menu...")
        finally:
            if cap:
                cap.release()
            if pool:
                pool.close()
                pool.join()

            # Przywróć ustawienia terminala dla Unix (jeśli zmieniono)
            if not _HAS_MSVCRT and old_term_settings is not None:
                try:
                    termios.tcsetattr(fd, termios.TCSADRAIN, old_term_settings)
                except Exception:
                    pass

            cv2.destroyAllWindows()

if __name__ == "__main__":
    # Wyraźne ostrzeżenie przy starcie programu
    try:
        print(Fore.YELLOW + "Uwaga: Tryb kolorowy jest przedpremierowy (previrew) i może powodować problemy z wydajnością." + Style.RESET_ALL)
    except Exception:
        # Jeśli colorama/kolory nie działają, wypisz prosty tekst
        print("Uwaga: Tryb kolorowy jest przedpremierowy (previrew) i może powodować problemy z wydajnością.")

    app = ASCIICam()
    app.main_menu()