local args = {...}
local component = require("component")
local computer = require("computer")
local event = require("event")
local gpu = component.gpu
local unicode = require("unicode")
local keyboard = require("keyboard")
local text = require("text")
local os = require("os")
local pal = {}

function resetGPU(w, h)
	gpu.setResolution(w, h)
	gpu.setBackground(0, false)
	gpu.setForeground(16777215, false)
	gpu.fill(1, 1, w, h, " ")
end

local q = {}
for i=0,255 do
  local dat = (i & 0x01) << 7
  dat = dat | (i & 0x02) >> 1 << 6
  dat = dat | (i & 0x04) >> 2 << 5
  dat = dat | (i & 0x08) >> 3 << 2
  dat = dat | (i & 0x10) >> 4 << 4
  dat = dat | (i & 0x20) >> 5 << 1
  dat = dat | (i & 0x40) >> 6 << 3
  dat = dat | (i & 0x80) >> 7
  q[i + 1] = unicode.char(0x2800 | dat)
end

function error(str)
  print("ERROR: " .. str)
  os.exit()
end

function resetPalette(data)
 for i=0,255 do
  if (i < 16) then
    if data == nil then
      pal[i] = (i * 15) << 16 | (i * 15) << 8 | (i * 15)
    else
      pal[i] = data[3][i]
      gpu.setPaletteColor(i, data[3][i])
    end
  else
    local j = i - 16
    local b = math.floor((j % 5) * 255 / 4.0)
    local g = math.floor((math.floor(j / 5.0) % 8) * 255 / 7.0)
    local r = math.floor((math.floor(j / 40.0) % 6) * 255 / 5.0)
    pal[i] = r << 16 | g << 8 | b
  end
 end
end

resetPalette(nil)

function r8(file)
  local byte = file:read(1)
  if byte == nil then
    return 0
  else
    return string.byte(byte) & 255
  end
end

function r16(file)
  local x = r8(file)
  return x | (r8(file) << 8)
end

function loadImage(filename)
  local data = {}
  local file = io.open(filename, 'rb')
  local hdr = {67,84,73,70}

  for i=1,4 do
    if r8(file) ~= hdr[i] then
      error("Invalid header!")
    end
  end

  local hdrVersion = r8(file)
  local platformVariant = r8(file)
  local platformId = r16(file)

  if hdrVersion > 1 then
    error("Unknown header version: " .. hdrVersion)
  end

  if platformId ~= 1 or platformVariant ~= 0 then
    error("Unsupported platform ID: " .. platformId .. ":" .. platformVariant)
  end

  data[1] = {}
  data[2] = {}
  data[3] = {}
  data[2][1] = r8(file)
  data[2][1] = (data[2][1] | (r8(file) << 8))
  data[2][2] = r8(file)
  data[2][2] = (data[2][2] | (r8(file) << 8))

  local pw = r8(file)
  local ph = r8(file)
  if not (pw == 2 and ph == 4) then
    error("Unsupported character width: " .. pw .. "x" .. ph)
  end

  data[2][3] = r8(file)
  if (data[2][3] ~= 4 and data[2][3] ~= 8) or data[2][3] > gpu.getDepth() then
    error("Unsupported bit depth: " .. data[2][3])
  end

  local ccEntrySize = r8(file)
  local customColors = r16(file)
  if customColors > 0 and ccEntrySize ~= 3 then
    error("Unsupported palette entry size: " .. ccEntrySize)
  end
  if customColors > 16 then
    error("Unsupported palette entry amount: " .. customColors)
  end

  for p=0,customColors-1 do
    local w = r16(file)
    data[3][p] = w | (r8(file) << 16)
  end

  local WIDTH = data[2][1]
  local HEIGHT = data[2][2]

  for y=0,HEIGHT-1 do
    for x=0,WIDTH-1 do
      local j = (y * WIDTH) + x + 1
      local w = r16(file)
      if data[2][3] > 4 then
        data[1][j] = w | (r8(file) << 16)
      else
        data[1][j] = w
      end
    end
  end

  io.close(file)
  return data
end

function gpuBG()
  local a, al = gpu.getBackground()
  if al then
    return gpu.getPaletteColor(a)
  else
    return a
  end
end
function gpuFG()
  local a, al = gpu.getForeground()
  if al then
    return gpu.getPaletteColor(a)
  else
    return a
  end
end

function drawImage(data, offx, offy)
  if offx == nil then offx = 0 end
  if offy == nil then offy = 0 end

  local WIDTH = data[2][1]
  local HEIGHT = data[2][2]

  gpu.setResolution(WIDTH, HEIGHT)
  resetPalette(data)
  gpu.setViewport(1, 1)

  local bg = 0
  local fg = 0
  local cw = 0

  for y=0,HEIGHT-1 do
    local str = ""
    for x=0,WIDTH-1 do
      local ind = (y * WIDTH) + x + 1
      local gBG = gpuBG()
      local gFG = gpuFG()
      if data[2][3] > 4 then
        bg = pal[data[1][ind] & 0xFF]
        fg = pal[(data[1][ind] >> 8) & 0xFF]
        cw = ((data[1][ind] >> 16) & 0xFF) + 1
      else
        fg = pal[data[1][ind] & 0x0F]
        bg = pal[(data[1][ind] >> 4) & 0x0F]
        cw = ((data[1][ind] >> 8) & 0xFF) + 1
      end
      if (gBG == fg) and (gFG == bg) then
        str = str .. q[cw ^ 255]
      elseif (gBG ~= bg) or (gFG ~= fg) then
        if #str > 0 then
          gpu.set(x + 1 + offx - unicode.wlen(str), y + 1 + offy, str)
        end
        if gBG ~= bg then
          gpu.setBackground(bg)
        end
        if gFG ~= fg then
          gpu.setForeground(fg)
        end
        str = q[cw]
      else
        str = str .. q[cw]
      end
    end
    if #str > 0 then
      gpu.set(WIDTH + 1 - unicode.wlen(str) + offx, y + 1 + offy, str)
    end
  end
end

active = 1
function onKeyDown(name, addr, char, key, player)
	active = 0
	return false
end
event.listen("key_down", onKeyDown)

resetGPU(160, 50)
local ticks = 0

local image = loadImage("cube.dat")
drawImage(image)

local lastSleep = computer.uptime()
function sleep(time)
	local target = lastSleep + time
	while computer.uptime() < target do
		os.sleep(0.05)
	end
	lastSleep = target
end

function getPixel(x, y)
	local px = x << 1
	local py = y << 2
	return q[((calcPixel(px, py) << 7)
		| (calcPixel(px, py + 1) << 5)
		| (calcPixel(px, py + 2) << 3)
		| (calcPixel(px, py + 3) << 1)
		| (calcPixel(px + 1, py) << 6)
		| (calcPixel(px + 1, py + 1) << 4)
		| (calcPixel(px + 1, py + 2) << 2)
		| (calcPixel(px + 1, py + 3))
	) + 1]
end

local cubeData = {{148, 32}, {144, 32}, {154, 16}, {136, 32},
{126, 32}, {114, 32}, {100, 32}, {84, 32}, {66, 32}, {46, 32},
{24, 32}, {0, 32}, {128, 16}, {100, 16}, {70, 16}, {128, 0}, {36, 16},
{0, 16}, {90, 0}, {50, 0}}

function getColor(ticks, mul)
	local ctr = ((math.floor(ticks) & 31) / 32) * 6
	local x = math.floor(mul * (ctr % 1))
	local y = mul
	local z = math.floor(mul * (1 - (ctr % 1)))
	if ctr >= 5 then
		return (y << 16) | z
    end
	if ctr >= 4 then
		return (x << 16) | y
    end
	if ctr >= 3 then
		return (z << 8) | y
    end
	if ctr >= 2 then
		return (y << 8) | x
    end
	if ctr >= 1 then
		return (z << 16) | (y << 8)
    end
	return (y << 16) | (x << 8)
end
local ticks = 0

function drawLine(y, w, py)
	local t = ((y - 1) & 0xE) * 4
	local ch = ""
	for i=1,50 do
		ch = ch .. getPixel(i, y)
	end
	gpu.set(1, y, ch)
	local sx = cubeData[w][1] + 1
	local sy = cubeData[w][2] + py
	local dx = 26 - w
	local dy = y
	gpu.copy(sx, sy, w * 2, 1, dx - sx, dy - sy)
end

gpu.setViewport(50, 16)
gpu.setBackground(0, false)

local tx = 0
local ty = 0

local xt = {}
for i=0,320 do
  xt[i] = math.sin(i / 10)
end

function calcPixel(x, y)
	local cx = x - 50 + (tx + xt[x] * 10)
	local cy = y - 32 + (math.sin(ticks / 10 + (y * 2) / 30) * 16)
	local t = math.sin((cx * cx + cy * cy) / (100 + ty))
	if t > 0 then return 1 else return 0 end
end

while active == 1 do
	tx = math.cos(ticks / 20) * 20

	local cw = 12 + (math.sin(ticks / 15) * 10) * (5 + math.cos(ticks / 5) * 2) / 5
	if cw < 1 then cw = 1 end
	if cw > 20 then cw = 20 end
	local ch = cw * (5 - math.cos(ticks / 6) * 2) / 5
	local chc = 8 + (math.sin(ticks / 13) * 4)
 local chs = chc + 1 - (ch / 2)
	local che = chs + ch - 1
	local si = (1 + (ticks % 15)) * ch / 16
	local sip = ch - si
	local sim = math.min(si, sip)
	local dw = cw - ((2 - (math.sin(si / ch * math.pi) * 2.75)) * ch / 16)
	dw = cw
	local swu = dw
	local swd = dw
	if si ~= ch then
		swu = math.floor(math.cos(si / ch * math.pi / 2) * swu)
		swd = math.floor(math.sin(si / ch * math.pi / 2) * swd)
	end
	local pwu = dw - swu
	local pwd = dw - swd
	gpu.setForeground(0x030200 * math.floor((cw / 1.65) + 15), false)
	for i=1,16 do
		ty = math.sin((ticks + i) / 45) * 50
		if i >= chs and i <= che then
			local iy = math.floor(i - chs + 1.5)
			local p = 1
			local w = dw
			if iy < si - 0.5 then
				p = (iy / si)
				w = swd + (pwd * p)
			elseif iy > si + 0.5 then
				p = ((iy - si) / sip)
				w = dw - (pwu * p)
			end
			local h = p * 16
			if w > 20 then w = 20 end
			if math.floor(w) > 0 and math.floor(w) <= 20 then
				drawLine(i, math.floor(w), math.floor(h))
			end
		else
			local ch = ""
			for ix=1,50,2 do
				ch = ch .. getPixel(ix, i) .. getPixel(ix+1, i)
			end
			gpu.set(1, i, ch)
		end
	end
	sleep(0.05)
	ticks = ticks + 1
end

resetGPU(80, 25)