
-- Explicitly require love modules first
require("love.graphics")
require("love.window")

require "engine/object"
require "bit"
require "engine/string_packer"
require "engine/controller"
require "back"
require "tag"
require "engine/event"
require "engine/node"
require "engine/moveable"
require "engine/sprite"
require "engine/animatedsprite"
require "functions/misc_functions"
require "game"
require "globals"
require "engine/ui"
require "functions/UI_definitions"
require "functions/state_events"
require "functions/common_events"
require "functions/button_callbacks"
require "functions/misc_functions"
require "functions/test_functions"
require "card"
require "cardarea"
require "blind"
require "card_character"
require "engine/particles"
require "engine/text"
require "challenges"

math.randomseed( G.SEED )

function love.run()
	print("[MAIN.LUA] love.run() function starting")
	if love.load then 
		print("[MAIN.LUA] About to call love.load()")
		love.load(love.arg.parseGameArguments(arg), arg) 
		print("[MAIN.LUA] love.load() completed")
	end

	-- We don't want the first frame's dt to include time taken by love.load.
	if love.timer then love.timer.step() end

	local dt = 0
	local dt_smooth = 1/100
	local run_time = 0
	local frame_count = 0
	print("[MAIN.LUA] Starting main loop with dt_smooth=" .. dt_smooth)

	-- Main loop time.
	return function()
		frame_count = frame_count + 1
		if frame_count <= 10 or frame_count % 60 == 0 then
			print("[MAIN.LUA] Frame " .. frame_count .. " starting")
		end
		
		run_time = love.timer.getTime()
		-- Process events.
		if love.event then
			print("[MAIN.LUA] G.CONTROLLER exists: " .. tostring(G and G.CONTROLLER ~= nil))
			if G and G.CONTROLLER then
				print("[MAIN.LUA] Pumping events...")
			else
				print("[MAIN.LUA] Skipping event pumping - no G.CONTROLLER")
			end
		end
		if love.event and G and G.CONTROLLER then
			love.event.pump()
			local _n,_a,_b,_c,_d,_e,_f,touched
			for name, a,b,c,d,e,f in love.event.poll() do
				if frame_count <= 10 then
					print("[MAIN.LUA] Processing event: " .. tostring(name))
				end
				if name == "quit" then
					if not love.quit or not love.quit() then
						return a or 0
					end
				end
				if name == 'touchpressed' then
					touched = true
				elseif name == 'mousepressed' then 
					_n,_a,_b,_c,_d,_e,_f = name,a,b,c,d,e,f
				else
					love.handlers[name](a,b,c,d,e,f)
				end
			end
			if _n then 
				love.handlers['mousepressed'](_a,_b,_c,touched)
			end
		end

		-- Update dt, as we'll be passing it to update
		if love.timer then dt = love.timer.step() end
		dt_smooth = math.min(0.8*dt_smooth + 0.2*dt, 0.1)
		
		if frame_count <= 10 then
			print("[MAIN.LUA] Frame " .. frame_count .. ": dt=" .. string.format("%.4f", dt) .. " dt_smooth=" .. string.format("%.4f", dt_smooth))
		end
		
		-- Call update and draw
		if love.update then 
			if frame_count <= 10 then
				print("[MAIN.LUA] Calling love.update() with dt_smooth=" .. string.format("%.4f", dt_smooth))
			end
			love.update(dt_smooth) -- will pass 0 if love.timer is disabled
			if frame_count <= 10 then
				print("[MAIN.LUA] love.update() completed")
			end
		end

		-- Always present frames when graphics exists; isActive() may be unreliable on Wii U
		if love.graphics then
			if frame_count <= 10 then
				print("[MAIN.LUA] Graphics available, calling love.draw()")
			end
			if love.draw then love.draw() end
			if frame_count <= 10 then
				print("[MAIN.LUA] About to call love.graphics.present()")
			end
			love.graphics.present()
			if frame_count <= 10 then
				print("[MAIN.LUA] love.graphics.present() completed")
			end
		end

		run_time = math.min(love.timer.getTime() - run_time, 0.1)
		G.FPS_CAP = G.FPS_CAP or 500
		if run_time < 1./G.FPS_CAP then love.timer.sleep(1./G.FPS_CAP - run_time) end
	end
end

function love.load() 
	print("[MAIN.LUA] About to call G:start_up(), G type: " .. tostring(type(G)) .. ", G: " .. tostring(G))
	G:start_up()
	print("[MAIN.LUA] G:start_up() completed")
	--Steam integration
	local os = love.system.getOS()
	if os == 'OS X' or os == 'Windows' then 
		-- Steam integration mock
function steam_init()
    print("Steam initialized (mock implementation)")
end

function steam_shutdown()
    print("Steam shutdown (mock implementation)")
end

function steam_get_user()
    return "MockUser"
end

steam_init()

	else
	end

	--Set the mouse to invisible immediately, this visibility is handled in the G.CONTROLLER
	if love.mouse then
		love.mouse.setVisible(false)
	end
end

function love.quit()
	--Steam integration
	if G.SOUND_MANAGER then G.SOUND_MANAGER.channel:push({type = 'stop'}) end
	steam_shutdown()
end

function love.update( dt )
	print("[DEBUG] love.update called, dt: " .. tostring(dt) .. ", G.TIMERS.REAL: " .. tostring(G.TIMERS and G.TIMERS.REAL or "nil"))
    if G.E_MANAGER and G.E_MANAGER.QUEUED then
        print("[DEBUG] Event manager has " .. tostring(#G.E_MANAGER.QUEUED) .. " queued events")
    end
	--Perf monitoring checkpoint
    timer_checkpoint(nil, 'update', true)
    G:update(dt)
end

function love.draw()
	print("[MAIN.LUA] love.draw() called, about to call G:draw()")
	--Perf monitoring checkpoint
    timer_checkpoint(nil, 'draw', true)
	if G and G.draw then
		print("[MAIN.LUA] G and G.draw exist, calling G:draw()")
		G:draw()
		print("[MAIN.LUA] G:draw() completed")
	else
		print("[MAIN.LUA] ERROR: G or G.draw is nil - G: " .. tostring(G) .. ", G.draw: " .. tostring(G and G.draw or "nil"))
	end
end

function love.keypressed(key)
	if not _RELEASE_MODE and G.keybind_mapping[key] then love.gamepadpressed(G.CONTROLLER.keyboard_controller, G.keybind_mapping[key])
	else
		G.CONTROLLER:set_HID_flags('mouse')
		G.CONTROLLER:key_press(key)
	end
end

function love.keyreleased(key)
	if not _RELEASE_MODE and G.keybind_mapping[key] then love.gamepadreleased(G.CONTROLLER.keyboard_controller, G.keybind_mapping[key])
	else
		G.CONTROLLER:set_HID_flags('mouse')
		G.CONTROLLER:key_release(key)
	end
end

function love.gamepadpressed(joystick, button)
	button = G.button_mapping[button] or button
	G.CONTROLLER:set_gamepad(joystick)
    G.CONTROLLER:set_HID_flags('button', button)
    G.CONTROLLER:button_press(button)
end

function love.gamepadreleased(joystick, button)
	button = G.button_mapping[button] or button
    G.CONTROLLER:set_gamepad(joystick)
    G.CONTROLLER:set_HID_flags('button', button)
    G.CONTROLLER:button_release(button)
end

function love.mousepressed(x, y, button, touch)
    G.CONTROLLER:set_HID_flags(touch and 'touch' or 'mouse')
    if button == 1 then 
		G.CONTROLLER:queue_L_cursor_press(x, y)
	end
	if button == 2 then
		G.CONTROLLER:queue_R_cursor_press(x, y)
	end
end


function love.mousereleased(x, y, button)
    if button == 1 then G.CONTROLLER:L_cursor_release(x, y) end
end

function love.mousemoved(x, y, dx, dy, istouch)
	G.CONTROLLER.last_touch_time = G.CONTROLLER.last_touch_time or -1
	if next(love.touch.getTouches()) ~= nil then
		G.CONTROLLER.last_touch_time = G.TIMERS.UPTIME
	end
    G.CONTROLLER:set_HID_flags(G.CONTROLLER.last_touch_time > G.TIMERS.UPTIME - 0.2 and 'touch' or 'mouse')
end

function love.joystickaxis( joystick, axis, value )
    if math.abs(value) > 0.2 and joystick:isGamepad() then
		G.CONTROLLER:set_gamepad(joystick)
        G.CONTROLLER:set_HID_flags('axis')
    end
end

function love.errhand(msg)
	print("[ERROR HANDLER] love.errhand() called with message: " .. tostring(msg))
	print("[ERROR HANDLER] Stack trace: " .. debug.traceback())
	
	-- ADDITIONAL FILE LOGGING for Wii U
	local logFile = io.open("fs:/vol/external01/simple_debug.log", "a")
	if logFile then
		logFile:write("[ERROR HANDLER] LOVE ERROR: " .. tostring(msg) .. "\n")
		logFile:write("[ERROR HANDLER] STACK TRACE: " .. debug.traceback() .. "\n")
		logFile:flush()
		logFile:close()
	end
	
	if G.F_NO_ERROR_HAND then 
		print("[ERROR HANDLER] G.F_NO_ERROR_HAND is true, returning")
		if logFile then
			local logFile2 = io.open("fs:/vol/external01/simple_debug.log", "a")
			if logFile2 then
				logFile2:write("[ERROR HANDLER] G.F_NO_ERROR_HAND is true, returning early\n")
				logFile2:flush()
				logFile2:close()
			end
		end
		return 
	end
	msg = tostring(msg)

	if G.SETTINGS.crashreports and _RELEASE_MODE and G.F_CRASH_REPORTS then 
		local http_thread = love.thread.newThread([[
			local https = require('https')
			CHANNEL = love.thread.getChannel("http_channel")

			while true do
				--Monitor the channel for any new requests
				local request = CHANNEL:demand()
				if request then
					https.request(request)
				end
			end
		]])
		local http_channel = love.thread.getChannel('http_channel')
		http_thread:start()
		local httpencode = function(str)
			local char_to_hex = function(c)
				return string.format("%%%02X", string.byte(c))
			end
			str = str:gsub("\n", "\r\n"):gsub("([^%w _%%%-%.~])", char_to_hex):gsub(" ", "+")
			return str
		end
		

		local error = msg
		local file = string.sub(msg, 0,  string.find(msg, ':'))
		local function_line = string.sub(msg, string.len(file)+1)
		function_line = string.sub(function_line, 0, string.find(function_line, ':')-1)
		file = string.sub(file, 0, string.len(file)-1)
		local trace = debug.traceback()
		local boot_found, func_found = false, false
		for l in string.gmatch(trace, "(.-)\n") do
			if string.match(l, "boot.lua") then
				boot_found = true
			elseif boot_found and not func_found then
				func_found = true
				trace = ''
				function_line = string.sub(l, string.find(l, 'in function')+12)..' line:'..function_line
			end

			if boot_found and func_found then 
				trace = trace..l..'\n'
			end
		end

		http_channel:push('https://958ha8ong3.execute-api.us-east-2.amazonaws.com/?error='..httpencode(error)..'&file='..httpencode(file)..'&function_line='..httpencode(function_line)..'&trace='..httpencode(trace)..'&version='..(G.VERSION))
	end

	if not love.window or not love.graphics or not love.event then
		return
	end

	if not love.graphics.isCreated() or not love.window.isOpen() then
		local success, status = pcall(love.window.setMode, 800, 600)
		if not success or not status then
			return
		end
	end

	-- Reset state.
	if love.mouse then
		love.mouse.setVisible(true)
		love.mouse.setGrabbed(false)
		love.mouse.setRelativeMode(false)
	end
	if love.joystick then
		-- Stop all joystick vibrations.
		for i,v in ipairs(love.joystick.getJoysticks()) do
			v:setVibration()
		end
	end
	if love.audio then love.audio.stop() end
	love.graphics.reset()
	local font = love.graphics.setNewFont("resources/fonts/m6x11plus.ttf", 20)

	love.graphics.clear(G.C.BLACK)
	love.graphics.origin()


	local p = 'Oops! Something went wrong:\n'..msg..'\n\n'..(not _RELEASE_MODE and debug.traceback() or G.SETTINGS.crashreports and
		'Since you are opted in to sending crash reports, LocalThunk HQ was sent some useful info about what happened.\nDon\'t worry! There is no identifying or personal information. If you would like\nto opt out, change the \'Crash Report\' setting to Off' or
		'Crash Reports are set to Off. If you would like to send crash reports, please opt in in the Game settings.\nThese crash reports help us avoid issues like this in the future')

	local function draw()
		local pos = love.window.toPixels(70)
		love.graphics.push()
		love.graphics.clear(G.C.BLACK)
		love.graphics.setColor(1., 1., 1., 1.)
		love.graphics.setFont(font)
		-- Use print instead of printf to avoid StreamBuffer issues
		love.graphics.print(p, pos, pos)
		love.graphics.pop()
		-- Make sure no render target is active before present
		love.graphics.setCanvas()
		love.graphics.present()

	end

	while true do
		love.event.pump()

		for e, a, b, c in love.event.poll() do
			if e == "quit" then
				return
			elseif e == "keypressed" and a == "escape" then
				return
			elseif e == "touchpressed" then
				local name = love.window.getTitle()
				if #name == 0 or name == "Untitled" then name = "Game" end
				local buttons = {"OK", "Cancel"}
				local pressed = love.window.showMessageBox("Quit "..name.."?", "", buttons)
				if pressed == 1 then
					return
				end
			end
		end

		draw()

		if love.timer then
			love.timer.sleep(0.1)
		end
	end

end

function love.resize(w, h)
	if w/h < 1 then --Dont allow the screen to be too square, since pop in occurs above and below screen
		h = w/1
	end

	--When the window is resized, this code resizes the Canvas, then places the 'room' or gamearea into the middle without streching it
	if w/h < G.window_prev.orig_ratio then
		G.TILESCALE = G.window_prev.orig_scale*w/G.window_prev.w
	else
		G.TILESCALE = G.window_prev.orig_scale*h/G.window_prev.h
	end

	if G.ROOM then
		G.ROOM.T.w = G.TILE_W
		G.ROOM.T.h = G.TILE_H
		G.ROOM_ATTACH.T.w = G.TILE_W
		G.ROOM_ATTACH.T.h = G.TILE_H		

		if w/h < G.window_prev.orig_ratio then
			G.ROOM.T.x = G.ROOM_PADDING_W
			G.ROOM.T.y = (h/(G.TILESIZE*G.TILESCALE) - (G.ROOM.T.h+G.ROOM_PADDING_H))/2 + G.ROOM_PADDING_H/2
		else
			G.ROOM.T.y = G.ROOM_PADDING_H
			G.ROOM.T.x = (w/(G.TILESIZE*G.TILESCALE) - (G.ROOM.T.w+G.ROOM_PADDING_W))/2 + G.ROOM_PADDING_W/2
		end

		G.ROOM_ORIG = {
            x = G.ROOM.T.x,
            y = G.ROOM.T.y,
            r = G.ROOM.T.r
        }

		if G.buttons then G.buttons:recalculate() end
		if G.HUD then G.HUD:recalculate() end
	end

	G.WINDOWTRANS = {
		x = 0, y = 0,
		w = G.TILE_W+2*G.ROOM_PADDING_W, 
		h = G.TILE_H+2*G.ROOM_PADDING_H,
		real_window_w = w,
		real_window_h = h
	}

	G.CANV_SCALE = 1

	if love.system.getOS() == 'Windows' and false then --implement later if needed
		local render_w, render_h = love.window.getDesktopDimensions(G.SETTINGS.WINDOW.selcted_display)
		local unscaled_dims = love.window.getFullscreenModes(G.SETTINGS.WINDOW.selcted_display)[1]

		local DPI_scale = math.floor((0.5*unscaled_dims.width/render_w + 0.5*unscaled_dims.height/render_h)*500 + 0.5)/500

		if DPI_scale > 1.1 then
			G.CANV_SCALE = 1.5

			G.AA_CANVAS = love.graphics.newCanvas(G.WINDOWTRANS.real_window_w*G.CANV_SCALE, G.WINDOWTRANS.real_window_h*G.CANV_SCALE, {type = '2d', readable = true})
			G.AA_CANVAS:setFilter('linear', 'linear')
		else
			G.AA_CANVAS = nil
		end
	end

	G.CANVAS = love.graphics.newCanvas(w*G.CANV_SCALE, h*G.CANV_SCALE, {type = '2d', readable = true})
	G.CANVAS:setFilter('linear', 'linear')
end 
