GitFOSS
~kernel/vga.cr
.cr
Crystal
(text/x-crystal)
struct VGACursor
  col : Int32 = 0
  row : Int32 = 0
  
  def initialize
    @col = 0
    @row = 0
  end
  
  def col : Int32
    @col
  end
  def col=(c : Int32)
    @col = c
  end
  def col_incr(c : Int32)
    @col += c
  end
  
  def row : Int32
    @row
  end
  def row=(r : Int32)
    @row = r
  end
  def row_incr(r : Int32)
    @row += r
  end
end

module VGA
  # VGA text mode buffer address (0xB8000)
  VGA_ADDRESS = 0xB8000_u64
  VGA_BUFFER = Pointer(UInt8).new(VGA_ADDRESS)

  # Buffer size
  VGA_WIDTH = 80
  VGA_HEIGHT = 25
  VGA_SIZE = VGA_WIDTH * VGA_HEIGHT
  
  # Cursor state
  VGA_CURSOR = VGACursor.new

  # Default color: white text on black background
  DEFAULT_COLOR = 0x07_u8
  
  module Colors
    DEFAULT       = 0x07_u8
    BLACK_ON_CYAN = 0xB0_u8
    BLACK_ON_LIME = 0xA0_u8
  end

  # Initialize VGA subsystem (clear screen)
  def self.init : Nil
    clear
  end

  # Clear the entire VGA screen
  def self.clear : Nil
    i = 0
    while i < VGA_SIZE
      (VGA_BUFFER + (i * 2)).value = " ".to_unsafe.value
      (VGA_BUFFER + (i * 2) + 1).value = Colors::DEFAULT
      i += 1
    end
    VGA_CURSOR.col = 0
    VGA_CURSOR.row = 0
  end

  # Write a single character to the current cursor position
  def self.putchar(
    ch : UInt8,
    i : Int32? = 0,
    line : Int32? = 0,
    color : UInt8? = Colors::DEFAULT
  ) : Nil
    VGA_CURSOR.col = i
    VGA_CURSOR.row = line
  
    # Simplified cursor handling (replace with actual cursor logic later)
    if ch == ("\n".to_unsafe).value # 0x0A_u8 # \n
      VGA_CURSOR.col = 0
      VGA_CURSOR.row += 1
      if VGA_CURSOR.row >= VGA_HEIGHT
        VGA_CURSOR.row = VGA_HEIGHT - 1
      end
      return
    end

    offset = (VGA_CURSOR.row * VGA_WIDTH + VGA_CURSOR.col) * 2

    (VGA_BUFFER + offset).value = ch
    (VGA_BUFFER + offset + 1).value = color
    
    VGA_CURSOR.col += 1
    
    if VGA_CURSOR.col >= VGA_WIDTH
      VGA_CURSOR.col = 0
      VGA_CURSOR.row += 1
      
      if VGA_CURSOR.row >= VGA_HEIGHT
        VGA_CURSOR.row = VGA_HEIGHT - 1
      end
    end

    # Wrap around if past screen
    # i %= VGA_SIZE
  end

  # Write a string to VGA
  def self.puts(
    str : String,
    color : UInt8? = Colors::DEFAULT,
    line : Int32? = 0,
  ) : Nil
    i = 0
    while i < str.size - 1
      ch = (str.to_unsafe + i).value
      putchar(ch, i, line, color)
      i += 1
    end
  end
  
  def self.debug_write_at(r : Int32, c : Int32, s : String, color : UInt8 = Colors::DEFAULT)
    off = ((r * VGA_WIDTH) + c) * 2
    i = 0
    ptr = s.to_unsafe
    while i < s.bytesize
      (VGA_BUFFER + off + (i * 2)).value = (ptr + i).value
      (VGA_BUFFER + off + (i * 2) + 1).value = color
      i += 1
    end
  end

  def self.dump_cursor_and_cells
    # Write cursor state at row 2 col 0
    # debug_write_at(2, 0, ["CURSOR R:",VGA_CURSOR.row," C:",VGA_CURSOR.col].join(""), Colors::BLACK_ON_CYAN)
    # Also copy first 32 bytes of VGA buffer to row 3 for inspection
    i = 0
    while i < 32
      ch = (VGA_BUFFER + (i * 2)).value
      (VGA_BUFFER + ((3 * VGA_WIDTH + i) * 2)).value = ch
      (VGA_BUFFER + ((3 * VGA_WIDTH + i) * 2) + 1).value = Colors::BLACK_ON_LIME
      i += 1
    end
  end
  
end