import os
import pathlib as p
import sys
from collections.abc import Callable
from sys import exit

import glfw as gf
import imgui
import OpenGL.GL as gl
from imgui.integrations.glfw import GlfwRenderer

from ..src.tcr_console import console as c


def ctrlc_grace_exit() -> bool:
  print('^C', end='')
  return True  # Exit


class ImGuiHandler:
  frame: Callable[[], None]
  """The frame callback."""
  title: str
  """The title of the window."""
  font_path: p.Path | None
  """The path to the font file to use, or None to disable."""
  ctrlc_handler: Callable[[], bool] | None
  """The Ctrl-C handler to use, or None to disable. Return True to exit the gui, False to continue. Do not exit()."""

  def __init__(
    self,
    frame: Callable[['ImGuiHandler'], None],
    *,
    title: str = 'Untitled window',
    font_path: p.Path | str | None = None,
    ctrlc_handler: Callable[[], bool] = ctrlc_grace_exit,
    start_maximised: bool = False,
  ):
    if not callable(frame):
      raise TypeError('frame must be Callable[[], None]')
    self.frame = frame
    if ctrlc_handler is not None and not callable(ctrlc_handler):
      raise TypeError('ctrlc_handler must be Callable[[], bool]')
    self.ctrlc_handler = ctrlc_handler

    if not isinstance(title, str):
      raise TypeError('title must be str')
    self.title = title

    if isinstance(font_path, str):
      font_path = p.Path(font_path)
    if font_path is not None and not isinstance(font_path, p.Path):
      raise TypeError('font_path must be str or pathlib.Path (or None to disable)')
    if font_path is not None and not font_path.is_file():
      raise FileNotFoundError(f"font_path {font_path} doesn't exist or is not a file")
    self.font_path = font_path

    self.start_maximised = start_maximised

    self._setup()

  def _setup(self):
    imgui.create_context()
    if not gf.init():
      raise RuntimeError('Could not initialize OpenGL context')

    gf.window_hint(gf.CONTEXT_VERSION_MAJOR, 3)
    gf.window_hint(gf.CONTEXT_VERSION_MINOR, 3)
    gf.window_hint(gf.OPENGL_PROFILE, gf.OPENGL_CORE_PROFILE)
    gf.window_hint(gf.OPENGL_FORWARD_COMPAT, gl.GL_TRUE)

    window = gf.create_window(*gf.get_monitor_physical_size(gf.get_primary_monitor()), self.title, None, None)
    gf.make_context_current(window)

    if not window:
      gf.terminate()
      raise RuntimeError('Could not initialize GLFW window.')

    if self.start_maximised:
      gf.maximize_window(window)

    impl = GlfwRenderer(window)

    io = imgui.get_io()
    jb = io.fonts.add_font_from_file_ttf(self.font_path, 30) if self.font_path is not None else None
    impl.refresh_font_texture()

    while not gf.window_should_close(window):
      try:
        self._render(impl, window, jb)
      except KeyboardInterrupt:
        if self.ctrlc_handler():
          break

    impl.shutdown()
    gf.terminate()

  def _render(self, impl, window, font):
    gf.poll_events()
    impl.process_inputs()
    imgui.new_frame()

    gl.glClearColor(0.1, 0.1, 0.1, 1)
    gl.glClear(gl.GL_COLOR_BUFFER_BIT)

    if font is not None:
      imgui.push_font(font)

    self.frame()

    if font is not None:
      imgui.pop_font()

    imgui.render()
    impl.render(imgui.get_draw_data())
    gf.swap_buffers(window)
