local svbf = ffi.new("float[?]", 2 ^ 17)

local function frameProc(item, path)
  if type(item) == "cdata" then
    if vec3.is_vec3(item) or quat.is_quat(item) then
      return item
    elseif type(item[0]) == "number" then
      return nil
    else
      local s = 1 -- use all those bitz
      for l = 1, item[0][0], 1 do
        local i = l
        svbf[i * 8], svbf[i * 8 + 1], svbf[i * 8 + 2], svbf[i * 8 + 3], svbf[i * 8 + 4], svbf[i * 8 + 5], svbf[i * 8 + 6], svbf[i * 8 + 7] =
          item[l][1] * s,
          item[l][2] * s,
          item[l][3] * s,
          item[l][4] * s,
          item[l][5] * s,
          item[l][6] * s,
          (item[l][7] or 1) * 4000,
          (item[l][8] or 1) * 4000
      end
      local str = ffi_string(svbf, (item[0][0] + 1) * 32)
      -- print(ffi_string(item,(item[0][0]+1)*32))
      return str
    end
  elseif not (type(item) == "userdata" or (type(item) == "table" and (item.nocopy or item.debug))) then
    return item
  else
    return nil
  end
end

local function checkCopy(t)
	if type(t) == "table" and t.nocopy then
		return nil
	end
	return t
end

local function saveSymbol(symbol, splitFrames)
	local texsize = 0
	local widest = 0
	local tallest = 0

	for a, anim in pairs(symbol.animations) do
		for l, layer in ipairs(anim.layers) do
			for f, frame in ipairs(layer.frames) do
				if not symbol.splitFrames then
					frame.w = frame.canvas and frame.canvas:getWidth() or frame.w
					frame.h = frame.canvas and frame.canvas:getHeight() or frame.h
					frame.w = frame.w > widest and frame.w or widest
					frame.h = frame.h > tallest and frame.h or tallest
					texsize = texsize + frame.w * frame.h
				end
				frame.index = f
			end
		end
	end

	local newSheet
	local axis = math.sqrt(texsize)
	texsize = math.pow(2, math.ceil(math.log(axis) / math.log(2)) + 2)
	texw = texsize
	texh = texsize

	gfx.push("all")

	if symbol.splitFrames then
	else
		newSheet = newCanvas(math.max(widest, texw), math.max(tallest, texh), { format = "srgba8" })
		gfx.setCanvas(newSheet)
		gfx.setBlendMode("replace", "premultiplied")
	end

	print("model1")
	if symbol.models then
		for m, model in pairs(symbol.models) do
			local filename = symbol.dir .. symbol.name .. "/.models/" .. model.id
			local data = inspect(model, { process = checkCopy })
			engine_writeProjectFile(filename, "return " .. data)
		end
	end

	print("model2")
	gfx.setColor(1, 1, 1, 1)

	local stack = { x = 1, y = 1, t = 0 }

	if symbol.splitFrames then
		lfs.mkdir((ctx.zip and LINK_DIR or GAME_DIR) .. "symbols/" .. symbol.name)
	end

	local map = {}
	local anims = {}
	for a, anim in pairs(symbol.animations) do
		local animBounds = { t = nil, b = nil, l = nil, r = nil }
		local animDims = { w = 0, h = 0 }

		symbol.dateCreated = symbol.dateCreated or os.time()

		anims[anim.key] = {}
		anims[anim.key].key = anim.key
		anims[anim.key].fps = anim.fps
		anims[anim.key].name = anim.name
		anims[anim.key].audio = anim.audio
		anims[anim.key].color = anim.color
		anims[anim.key].layers = {}

		splitFrames[anim.key] = {}

		for l, layer in ipairs(anim.layers) do
			anims[anim.key].layers[l] = {
				name = layer.name,
				frames = {},
				id = layer.id,
				type = layer.type,
				obj = layer.obj,
				iqm = layer.iqm,
				smooth = layer.smooth,
				format = layer.format,
				alpha = layer.alpha,
				center = layer.center,
				data = layer.data and layer.data.id,
				clips = layer.clips,
				visible = layer.visible,
				resolution = layer.resolution,
				skeleton = layer.skeleton,
				catmull = layer.catmull,
				lineWidth = layer.lineWidth,
				lineWidthOverride = layer.lineWidthOverride,
				onePixel = layer.onePixel,
				wobble = layer.wobble,
				loop = layer.loop,
				length = layer.length,
				line = layer.line,
				depths = layer.depths,
				fill = layer.fill,
				palette = layer.palette,
			}

			for f, frame in ipairs(layer.frames) do
				print("FRAME ID: ", frame.id)
				frame.id = frame.id or newFrameID()
				anims[anim.key].layers[l].frames[frame.index] = frame.id

				animBounds.l = (animBounds.l == nil or frame.ox - frame.w / 2 < animBounds.l) and frame.ox - frame.w / 2
					or animBounds.l
				animBounds.r = (animBounds.r == nil or frame.ox + frame.w / 2 > animBounds.r) and frame.ox + frame.w / 2
					or animBounds.r
				animBounds.t = (animBounds.t == nil or frame.oy - frame.h / 2 < animBounds.t) and frame.oy - frame.h / 2
					or animBounds.t
				animBounds.b = (animBounds.b == nil or frame.oy + frame.h / 2 > animBounds.b) and frame.oy + frame.h / 2
					or animBounds.b
				animBounds.ox = (animBounds.l + animBounds.r) / 2
				animBounds.oy = (animBounds.t + animBounds.b) / 2
				animDims.w = animBounds.r - animBounds.l
				animDims.h = animBounds.b - animBounds.t
			end
		end

		anims[anim.key].bounds = animBounds
		anims[anim.key].size = animDims
		anims[anim.key].length = anim.length
	end

	local symbolPath = (ctx.zip and LINK_DIR or GAME_DIR) .. "symbols/" .. symbol.name .. "/." .. symbol.key
	local symbolData = "local t = " .. inspect(anims, { newline = "", indent = "" }) .. " return t"

	print("\nwrite symbol data")

	if ctx.zip then
		lfs.write(symbolData,symbolPath)
	else
		engine_writeProjectFile(symbolData, symbolPath)
	end

	local padding = 1 -- temp
	for a, anim in pairs(symbol.animations) do
		for l, layer in ipairs(anim.layers) do
			splitFrames[anim.key][l] = {}

			for f, frame in ipairs(layer.frames) do
				if stack.x + frame.w > texw then
					stack.x = 1
					stack.y = stack.y + stack.t + padding
					stack.t = 0
				end
				stack.t = frame.h > stack.t and frame.h or stack.t

				local frameID = stringFrameID(frame.id)

				local frameDir = string.format(
					"%ssymbols/%s/frames/%s",
					(ctx.zip and LINK_DIR or GAME_DIR),
					tostring(symbol.name),
					frameID
				)

				local skipFrame = (frame.lastDrawn and frame.lastDrawn == frame.lastSave)

				if not lfs.exists(frameDir) then
					skipFrame = false
				end
				if layer.format == "raster" and not lfs.exists(frameDir .. ".png") then
					skipFrame = false
				end

				frame.preSaveTime = frame.lastSave
				frame.lastDrawn = time
				frame.lastSave = time

				if symbol.splitFrames and not skipFrame then
					local frameData = {
						blank = frame.blank,
						x = stack.x,
						y = stack.y,
						w = frame.w,
						h = frame.h,
						anim = frame.anim,
						abs = frame.abs,
						tween = frame.tween,
						isKey = frame.isKey,
						pose = frame.pose,
						ox = frame.ox,
						oy = frame.oy,
						l = frame.l,
						strokes = ctx.isAnim and frame.strokes or copyStrokes(frame.strokes, true),
						id = frame.id,
						symbols = {},
						lastDrawn = os.time(),
						lastSave = os.time(),
						lastPosition = f,
					}

					for s, symbol in ipairs(frame.symbols) do
						table.insert(frameData.symbols, {
							key = symbol.key,
							uid = symbol.uid,
							position = symbol.position,
							scale = symbol.scale,
							orientation = symbol.orientation,
						})
					end

					if layer.format == "raster" then
						if frame.canvas and (frame.dirty or not frame.packedCanvas) then
							print("canvas not already packed, packing now...")
							frame.packedCanvas = editor_packCanvas(frame.canvas)
						end
						if frame.packedCanvas then
							local packed = frame.packedCanvas
							local data = packed.packer:newImageData()
							local pixels = ffi.cast("const unsigned char*", data.data.data)

							print("pack raster 2...")
							frameData.packedCanvas = {
								packer = ffi_string(pixels, 4 * packed.tileStride ^ 2),
								width = packed.width,
								height = packed.height,
								tileSize = packed.tileSize,
								tileStride = packed.tileStride,
								tileCount = packed.tileCount,
							}
							print("pack raster 3...")
							if animatePanel.debugCompaction then
								print("unpacker", packed.width, packed.height)
								print("packer", packed.tileStride, packed.tileStride)
								local packer = gfx.newImageData(packed.tileStride, packed.tileStride)
								ffi_copy(packer.data.data, frameData.packedCanvas.packer)
								local unpacker = gfx.newImageData(packed.width + 1, packed.height + 1)
								for t = 0, packed.tileCount - 1, 1 do
									local x, y = t % packed.tileStride, math.floor(t / packed.tileStride)
									local r, g, b, a = packer:getPixel(x, y)
									unpacker:setPixel(r * 255, g * 255, x / 255, y / 255, 0, 1)
								end
								print("\nPIXEL COLOR", unpacker:getPixel(60, 60))
								data:encode("png", "sameTest1.png")
								packer:encode("png", "sameTest2.png")
								unpacker:encode("png", "matchTest2.png")
							end
						end
					end

					print(frameID, "save")
					table.insert(splitFrames, {
						data = frameData,
						size = frame.canvas and frame.canvas:getWidth() * frame.canvas:getHeight() or 0,
						canvas = layer.format == "raster" and frame.packedCanvas or nil,
						rawcanvas = layer.format == "raster" and frame.canvas or nil,
						name = frameID,
						dir = frameDir,
					})
				else
					print(frameID, "skip")
				end

				map[frameID] = true
				map[frameID .. ".png"] = true
				stack.x = stack.x + frame.w + animatePanel.padding
			end
		end
	end

	local dir = string.format("%ssymbols/%s/frames/", (ctx.zip and LINK_DIR or GAME_DIR), tostring(symbol.name))
	for f, frame in ipairs(lfs.getDirectoryItems(dir)) do
		if not map[frame.name] then
			print("try 2 remove", dir .. frame.name)
			lfs.remove(dir .. frame.name)
		end
	end

	gfx.pop()

	if not symbol.animations.default then
		symbol.animations.default = nil
		if animatePanel.setDefault then
			symbol.default = animatePanel.setDefault
		end
	end
end

local function saveFrame(frame)
  local panel = animatePanel

  if not frame then
    return
  end

  print("\n SAVE FRAME-----------------------")
  local rasterData, frameData

  if frame.canvas then
    print(" -> Raster")

    if frame.canvas.canvas:type() == "Canvas" then
      rasterData = frame.canvas.canvas:newImageData()
      print("  Converting to storage form")
    else
      rasterData = frame.canvas.canvas
      print("  Already in storage form")
    end
  else
    print(" -> Vector")
  end

  frameData = inspect(frame.data, { newline = "", indent = "", process = frameProc })

  local rasterDir, rasterName = frame.dir .. ".png", frame.name .. ".png"
  local rasterPath = ctx.zip and (SAVE_DIR .. rasterDir):gsub("//", "/") or rasterDir
  if rasterData then
    -- print("encode ",rasterName)
    -- rasterData:encode("png",rasterName)
    print("encode ", rasterPath)
    rasterData:encode("png", rasterPath)
  end

  if ctx.zip then
    if rasterData then
      -- print("move",rasterName,q(SAVE_DIR..rasterDir))
      -- os.rename(rasterName,(SAVE_DIR..rasterDir):gsub("//","/"))
    end

    lfs.write( "local t = " .. frameData .. " return t",frame.dir)
  else
    if rasterData then
      -- print("move",rasterName,rasterDir)
      -- os.rename(rasterName,rasterDir)
    end

    local file, e = io.open(frame.dir, "w")
    if not file then
      file, e = io.open('"' .. frame.dir .. '"', "w")
    end
    file:write("local t = " .. frameData .. " return t")
    file:close()
  end
  rasterData = nil -- :release()
  frameData = nil -- :release()
  print("")
end

local function saveProc(item, path)
  local skipKeys = {
    -- ,"line"
    smoothLine = true,
    pcurve = true,
    rcurve = true,
    dists = true,
    prevpoly = true,
    draggingPoint = true,
    viewMesh = true,
    viewVerts = true,
    parentFinder = true,
    shapes = true,
    entities = true,
    selectedEntities = true,
    draws = true,
    saturated = true,
    valued = true,
    hued = true,
    planes = true,
    pointsR = true,
    pointsL = true,
    mesh = true,
    transform = true,
    terrain = true,
    models = true,
    model = true,
    tip = true,
    links = true,
    iqmAnims = true,
    skelly = true,
    -- ,outer = true
    -- ,inner = true
    -- ,color=true
  }
  if
    skipKeys[path[#path]]
    or type(item) == "function"
    or (type(item) == "table" and (item.nocopy or item.isMesh or item.isCanvas or item.isShader))
  then
    return nil
  else
    return item
  end
end

local function saveProcLevel(item, path)
  local item = saveProc(item, path)
  if item then
    if type(item) == "table" then
      if item.isRoom then
        return nil
      elseif path[#path] == "camera" then
        return nil
      elseif path[#path - 1] == "languages" then
        return item:gsub("[\n\r]", "")
      end
    end
  end
  return item
end

local function saveProcRoom(item, path)
  local item = saveProc(item, path)
  if item then
    if type(item) == "table" then
      -- if item.isEntity then
      --   item = scene_returnObject(item)
      -- end
    end
  end
  return item
end

function save_animation(panel)
  if not panel.canSave then
    return
  end
  if ctx.level.temp then
    ctx.level.temp = ctx.level.temp - 1
    if ctx.level.temp == 0 then
      ctx.level.temp = false
    end
  end
  panel.framesToSave = 0

  print("\n///////////////////////////////////////////////")
  print("/////////////    SAVE GRAPHICS    /////////////\n")

  panel.canSave = false
  lwindow.setVsync(false)

  time = os.time()
  panel.attemptedSaveTime = time

  local splitFrames = {}
  for s, symbol in pairs(ctx.symbols) do
    if symbol.folder == "Game" then
      saveSymbol(symbol, splitFrames)
    end
  end
  animatePanel.framesToSave = #splitFrames

  if ctx.zip then
    -- mkdir((LINK_DIR.."/export"):gsub("//","/"))
    local str = "" .. (LINK_DIR .. "/export/sequence/"):gsub("//", "/")
    recursivelyDelete(str)
    print("clear", str)
    -- mkdir(LINK_DIR.."/export/sequence")

    ctx.level.exportScale = panel.exportScale
  end

  panel.saving = true
  panel.saveFrame = 1

  for f, frame in ipairs(splitFrames) do
    panel.saveFrame = f
    saveFrame(frame)
  end

  panel.saving = false

  save_project()

  if ctx.zip then
    local name = config.projects[engine.project].name
    local id = config.projects[engine.project].id
    local dir = config.projects[engine.project].dir
    local tempName = "test.zip"
    local zipName = name .. ".umou"

    panel.zipName = zipName

    --TODO zip umou file
    -- if ffi.os=="Windows" then
    --  local dest = (dir.."/"..zipName):gsub("//","/")
    --  print ("from" ,SAVE_DIR.."projects/"..id)
    --  print("to",dest)
    --  lfs.saveZip(SAVE_DIR.."projects/"..id,dest,true)

    --  local file = io.open(dest,"a")
    --  print("file",file)
    --  file:write(engine.project)
    --  file:close()

    -- else
    --  lfs.saveZip(SAVE_DIR.."projects/"..id,SAVE_DIR..tempName,true)
    --  print(SAVE_DIR..tempName)
    --  local file = io.open(SAVE_DIR..tempName,"a")
    --  file:write(engine.project)
    --  file:close()

    --  local dest = (dir.."/"..zipName):gsub("//","/")
    --  local dest2= (dir.."/"..name):gsub("//","/")
    --  -- local remmed = os.remove(dest)
    --  local from  = SAVE_DIR..tempName
    --  print("move file")
    --  print("from",from)
    --  print("to",dest)
    --  print(fs.move(SAVE_DIR..tempName,dest))
    -- end

    panel.canSave = true

    print("doneZipping")

    -- TODO add icon to file
    if panel.viewport then
      save_thumbnail(panel)

    -- os.rename("icon.png",SAVE_DIR.."thumbnails/"..engine.project..".png")

    -- local fileicondir = "fileicon"
    -- local set =
    --  "\""..ENGINE_DIR..fileicondir.."\" set \""
    --  ..(config.projects[engine.project].dir.."/"..panel.zipName):gsub("//","/")
    --  .."\" \""..SAVE_DIR:gsub("//","/").."icon.png\""
    -- print(set)
    -- os.execute(set)
    else
      print("FAILED TO SAVE SCREENSHOT, VIEWPORT UNINITIALIZED")
    end

    editor_tempTip("Save Complete")
  end

  editor_tempTip("Save Complete")
  input_unlockKeys()
  input_unlockMouse()
  lwindow.setVsync(true)
  game.waiting = {}

  panel.newImage = false

  -- print("\n///////////////////////////////////////////////")
  print("\n/////////////    SAVE COMPLETE    /////////////")
  print("/////////////////////////////////////////////////////////////\n")
end

function save_thumbnail(view, panel)
  -- TODO thumbnail
  if not engine.canvas then
    return
  end
  local size = 128
  local vw, vh = engine.canvas:getWidth(), engine.canvas:getHeight()
  local va = vh / vw
  local w, h = panel and panel.root.stageWidth or screenW, panel and panel.root.stageHeight or screenH
  local aspect = h / w
  print("ass pects", va, aspect)
  local scale = (va >= aspect and size / vw or (size * aspect) / vh)
  local icon = newCanvas(size, size * aspect)
  if not icon then
    return
  end
  local x, y = 0, 0
  gfx.push("all")
  gfx.setCanvas(icon)
  if not panel then
    gfx.clear(colorTable(ctx.page.sky.color))
  end
  gfx.setColor(1, 1, 1, 1)

  if va > aspect then
    y = -(vh * scale - size * aspect) / 2
    print("y", y)
  else
    x = -(vw * scale - size) / 2
    print("x", x)
  end

  local u, d = engine.canvas:getFilter()
  engine.canvas:setFilter("linear", "linear")
  gfx.draw(engine.canvas, x, y, 0, scale, scale)
  engine.canvas:setFilter(u, d)
  gfx.pop()

  local data = icon:newImageData()
  -- data:encode("png", SAVE_DIR .. "thumbnails/" .. engine.project .. ".png")

  for i, item in ipairs(engine.splash.items) do
    if item.id == ctx.project then
      item.thumb = icon
      return
    end
  end
end

function save_previews()
  local img = "Image"
  -- for s,set in pairs(level.pages) do
  --  if set.preview and tostring(set.preview)~=img then
  --    set.preview:newImageData():encode("png", "cache/"..set.key..".png")
  --  end
  -- end
  -- for r,room in pairs(level.rooms) do
  --  if room.preview and tostring(room.preview)~=img then
  --    room.preview:newImageData():encode("png", "cache/"..room.key..".png")
  --  end
  --  for l,link in pairs(room.links) do
  --    if link.cached and tostring(link.cached)~=img then
  --      link.cached:newImageData():encode("png", "cache/"..link.linkID..".png")
  --    end
  --  end
  -- end
end

function save_project()
  -- print("\nPAGES")
  -- for p, page in pairs(ctx.level.pages) do
  --   print(p, page)
  --   for m, mem in pairs(page) do
  --     -- print(m, mem)
  --   end
  --   for v, view in pairs(page.views) do
  --     if type(view.camera) == "table" then
  --       for t, thing in pairs(view.camera) do
  --         print(t, thing)
  --       end
  --     end
  --     print("")
  --   end
  --   print("")
  -- end

  for r, room in pairs(ctx.rooms) do
    engine_writeProjectFile(
      lfs.wrapLua(inspect(room, {
        newline = shiftDown and "\n" or "",
        indent = shiftDown and "   " or "",
        process = saveProcRoom,
      })),
      "rooms",
      room.key
    )
  end

  local proj = config.projects[engine.project]
  local succ, file = pcall(inspect, {
    id = proj.id,
    symbols = load_stripSymbols(),
    dialogue = ctx.dialogue,
    tags = ctx.tags,
    scripts = ctx.scripts,
    levels = ctx.levels,
  }, {
    newline = shiftDown and "\n" or "",
    indent = shiftDown and " " or "",
    process = saveProcLevel,
  })

  if succ then
    engine_writeProjectFile(lfs.wrapLua(file), "." .. proj.id)
    save_previews()
    save_thumbnail()
    load_saveConfig()
    editor_tempTip("Saved")
  end

  editor.saveStep = 0
end

function save_update()
  editor.saveTime = mf(editor.autoSaveTime) * 60
  if editor.saveStep > editor.saveTime then
    if ctx.isAnim then
      editor.saveStep = 30
      save_animation(animatePanel)
    else
      save_project()
    end
  end
end

function save_draw(panel)
  gfx.setColor(1, 1, 1, 0.5)
  --editor_drawContextBack(editor.titlebar)
  --gfx.rectangle("fill",0,0,screenWidth,20)
  if not editor.autoSaveEnabled then
    gfx.setColor(1, 1, 1, 1)
    gfx.line(8 + panel.x, 7, 8 + panel.x, 12)
    gfx.line(8 + panel.x + 4, 7, 8 + panel.x + 4, 12)
  end
  if editor.saveStep < 1 then
    gfx.setColor(1, 1, 1, panel.open and 1 or 0.5)
    -- gfx.draw(load_imageTex("reload"),panel.x+panel.width/2,9, (editor.saveStep/20)*pi*2 , 0.2, 0.2, 64, 64)
  else
    gfx.setLineWidth(1)
    local progress = (editor.saveStep / editor.saveTime) * 2 * pi - pi / 2
    local col = panel.open and 1 or 0.4
    gfx.setColor(1, 1, 1, col)
    if not panel.line then
      gfx.arc("fill", panel.x + panel.width / 2, 10, 7, -pi / 2, (editor.saveStep / editor.saveTime) * 2 * pi - pi / 2, 20)
      gfx.arc("fill", panel.x + panel.width / 2, 10, 7, 0, pi, 20)
    end
    gfx.arc(
      "line",
      (progress > pi + pi / 3 or progress < -pi / 2.2 or panel.line) and "open" or "pie",
      panel.x + panel.width / 2,
      10,
      7,
      -pi / 2,
      progress,
      32
    )
    gfx.setColor(0, 0, 0, col)
    gfx.arc(
      "line",
      (progress > pi + pi / 3 or progress < -pi / 2.2 or panel.line) and "open" or "pie",
      panel.x + panel.width / 2,
      10,
      8,
      -pi / 2,
      progress,
      32
    )
  end
end
