local Util = require("util")

local DEFAULT_C = " "
local DEFAULT_FG = colors.white
local DEFAULT_BG = colors.black

local function ensureC(cVal)
  return (cVal ~= nil and #cVal == 1 and cVal) or DEFAULT_C
end

local WindowBuffer = {}

-- Create a shallow wrapper for window object
function WindowBuffer.wrapWindow(w, defaultC, defaultFg, defaultBg, clearColor)
  defaultC = ensureC(defaultC)
  defaultFg = defaultFg or DEFAULT_FG
  defaultBg = defaultBg or DEFAULT_BG
  clearColor = clearColor or DEFAULT_BG

  local obj = {}

  function obj.blit(text, fg, bg)
    w.blit(text, fg, bg)
  end

  function obj.flush() end
  function obj.flushPartial() end
  function obj.getSize()
    return w.getSize()
  end

  function obj._setPixel(x, y, c, fg, bg)
    w.setCursorPos(x, y)
    w.setFgColor(fg or defaultFg)
    w.setBgColor(bg or defaultBg)
    w.write(c or defaultC)
  end

  function obj.setPixel(x, y, c, fg, bg)
    local width, height = obj.getSize()
    Util.boundCheck(x, y, width, height)
    obj._setPixel(x, y, c, fg, bg)
  end

  -- Partial writes are not possible for native window access
  obj._writePixel = obj._setPixel
  obj.writePixel = obj.setPixel

  function obj.setCursorPos(x, y)
    w.setCursorPos(x, y)
  end

  function obj.getCursorPos()
    return w.getCursorPos()
  end

  function obj.clear()
    w.setBgColor(clearColor)
    --w.setFgColor(clearColor) -- Shouldn't matter for native clear
    w.clear()
  end

  return obj
end

function WindowBuffer.new(width, height, clearC, clearFg, clearBg)
  clearC = ensureC(clearC)
  clearFg = clearFg or DEFAULT_FG
  clearBg = clearBg or DEFAULT_BG

  local obj = {}

  local function setState(o, c, fg, bg)
    o.c = c
    o.bg = bg
    o.fg = fg
    return o
  end

  local function writeState(o, c, fg, bg)
    o.c = c or o.c
    o.bg = bg or o.bg
    o.fg = fg or o.fg
    return o
  end

  local buffer = {}
  for i=1,height do
    local line = {}
    for j=1,width do
      line[j] = setState({})
    end
    buffer[i] = line
  end

  local pX, pY = 1, 1

  function obj.blit(text, fg, bg)
    assert(#text == #fg, "Foreground color blit length must match text length")
    assert(#text == #bg, "Background color blit length must match text length")

    local line = buffer[pY]
    local baseX = pX
    for i=1,math.min(#text, width - baseX - 1) do
      local blitChar = line[i]
      blitChar.c = text:sub(i, i)
      blitChar.fg = fg:sub(i, i)
      blitChar.bg = bg:sub(i, i)
    end

    pX = pX + #text
  end

  function obj._setPixel(x, y, c, fg, bg)
    setState(buffer[y][x], c, fg, bg)
  end

  function obj.setPixel(x, y, c, fg, bg)
    Util.boundCheck(x, y, width, height)
    obj._setPixel(x, y, c, fg, bg)
  end

  function obj._writePixel(x, y, c, fg, bg)
    writeState(buffer[y][x], c, fg, bg)
  end

  function obj.writePixel(x, y, c, fg, bg)
    Util.boundCheck(x, y, width, height)
    obj._writePixel(x, y, c, fg, bg)
  end

  function obj.setCursorPos(x, y)
    pX, pY = x, y
  end

  function obj.getCursorPos()
    return pX, pY
  end

  function obj.clear()
    for i=1,height do
      local line = buffer[i]
      for j=1,width do
        setState(line[j], clearC, clearFg, clearBg)
      end
    end
  end

  function obj.flush(win, offsetX, offsetY)
    for i=1,height do
      local line = buffer[i]
      for j=1,width do
        local entry = line[j]
        win._writePixel(j + offsetX, i + offsetY, entry.c, entry.fg, entry.bg)
      end
    end
  end

  function obj.flushPartial(win, winOffsetX, winOffsetY, baseX, baseY, flushWidth, flushHeight)
    for i=baseY,baseY+flushHeight do
      local line = buffer[i]
      for j=baseX,baseX+flushWidth do
        local entry = line[j]
        win._writePixel(j + winOffsetX, i + winOffsetY, entry.c, entry.fg, entry.bg)
      end
    end
  end

  return obj
end

function WindowBuffer.shallowChild(windowBuffer, width, height, offsetX, offsetY, clearC, clearFg, clearBg)
  -- Assume static for now
  local parentWidth, parentHeight = windowBuffer.getSize()
  Util.boundCheck(offsetX, offsetY, parentWidth, parentHeight)
  Util.boundCheck(offsetX + width, offsetY + height, parentWidth, parentHeight)

  clearC = ensureC(clearC)
  clearFg = clearFg or DEFAULT_FG
  clearBg = clearBg or DEFAULT_BG

  local obj = {}

  local pX, pY = 1, 1

  local function setCursorPos()
    windowBuffer.setCursorPos(pX + offsetX, pY + offsetY)
  end

  function obj.blit(text, fg, bg)
    setCursorPos()
    windowBuffer.blit(text, fg, bg)
    pX = pX + #text
  end

  function obj._setPixel(c, fg, bg)
    setCursorPos()
    windowBuffer._setPixel(c, fg, bg)
  end

  function obj.setPixel(c, fg, bg)
    setCursorPos()
    windowBuffer.setPixel(c, fg, bg)
  end

  function obj._writePixel(c, fg, bg)
    setCursorPos()
    windowBuffer._writePixel(c, fg, bg)
  end

  function obj.writePixel(c, fg, bg)
    setCursorPos()
    windowBuffer.writePixel(c, fg, bg)
  end

  function obj.setCursorPos(x, y)
    pX, pY = x, y
  end

  function obj.getCursorPos()
    return pX, pY
  end

  function obj.getSize()
    return width, height
  end

  function obj.clear()
    for i=1,height do
      for j=1,width do
        windowBuffer._setPixel(j + offsetX, i + offsetY, clearC, clearFg, clearBg)
      end
    end
  end

  -- Reconsider implementation?
  obj.flush = windowBuffer.flush
  obj.flushPartial = windowBuffer.flushPartial

  return obj
end

return WindowBuffer