-- putting the orbit stuff that editmesh3d and editvoxelmap both use into here
-- TODO tempted to make this a proper UI component i.e. it includes the viewport and clipping stuff too
local ffi = require 'ffi'
local class = require 'ext.class'
local vec2i = require 'vec-ffi.vec2i'
local vec3d = require 'vec-ffi.vec3d'
local quatd = require 'vec-ffi.quatd'
local gl = require 'gl'

local numo9_rom = require 'numo9.rom'
local matArrType = numo9_rom.matArrType
local clipMax = numo9_rom.clipMax

local Orbit = class()

function Orbit:init(app)
	self.app = app

	self.scale = 1
	self.ortho = false
	self.angle = quatd():fromAngleAxis(1, 0, 0, 30)
	self.orbit = vec3d(0,0,0)
	self.pos = self.angle:zAxis() * 1.5 + self.orbit

	-- have to store as a memeber becuase it is used from beginDraw to endDraw
	self.modelMatPush = matArrType()
	self.viewMatPush = matArrType()
	self.projMatPush = matArrType()
end

function Orbit:beginDraw()
	local app = self.app

	local dx = app.ram.mousePos.x - app.ram.lastMousePos.x
	local dy = app.ram.mousePos.y - app.ram.lastMousePos.y

	local app = self.app
	app:drawSolidRect(0, 8, 256, 256, 0x28, nil, nil, app.paletteMenuTex)
--self:guiSetClipRect(0, 8, 256, 256)

	-- flush before enable depth test so the flush doesn't use depth test...
	app:triBuf_flush()
	gl.glEnable(gl.GL_DEPTH_TEST)
	gl.glClear(gl.GL_DEPTH_BUFFER_BIT)

	ffi.copy(self.modelMatPush, app.ram.modelMat, ffi.sizeof(self.modelMatPush))
	ffi.copy(self.viewMatPush, app.ram.viewMat, ffi.sizeof(self.viewMatPush))
	ffi.copy(self.projMatPush, app.ram.projMat, ffi.sizeof(self.projMatPush))

	local alt = app:key'lalt' or app:key'ralt'
	local uikey
	if ffi.os == 'OSX' then
		uikey = app:key'lgui' or app:key'rgui'
	else
		uikey = app:key'lctrl' or app:key'rctrl'
	end

	-- block all if ctrl or alt are used
	-- (leaving shift for the editor to use for the dropper)
	-- TODO maybe use right mouse here (in combination with some modifier keys that the editor doesn't use ...)
	local mouseHandled = alt or uikey
	local height = 256
	if app:key'mouse_left'
	and (dx ~= 0 or dy ~= 0)
	then
		-- pan
		if uikey and alt then
			local dist = (self.pos - self.orbit):length()
			self.orbit = self.orbit + self.angle:rotate(vec3d(-dx,dy,0) * (dist / height))
			self.pos = self.angle:zAxis() * dist + self.orbit
		-- rotate
		elseif uikey then
			self.angle = self.angle * quatd():fromAngleAxis(-dy,-dx, 0, 3*math.sqrt(dx^2+dy^2))
			self.pos = self.angle:zAxis() * (self.pos - self.orbit):length() + self.orbit
		-- zoom
		elseif alt then
			if self.ortho then
				self.scale = self.scale * math.exp(.1 * dy)
			else
				self.pos = (self.pos - self.orbit) * math.exp(dy * -.03) + self.orbit
			end
		end
	end
	local x,y,z,th = self.angle:toAngleAxis():unpack()

	local ar = app.width / app.height

	-- draw orientation widget
	app:matident()
	app:matident(1)
	app:matident(2)
	app:matortho(-(22 * ar - 2), 2, -20, 2)
	app:matrot(-math.rad(th), x, y, z, 1)	-- -th or +th?
	local thickness = math.max(1, app.width / 256)
	app:drawSolidLine3D(0, 0, 0, 1, 0, 0, 0x19, thickness, app.paletteMenuTex)
	app:drawSolidLine3D(0, 0, 0, 0, 1, 0, 0x1a, thickness, app.paletteMenuTex)
	app:drawSolidLine3D(0, 0, 0, 0, 0, 1, 0x1c, thickness, app.paletteMenuTex)

	app:matident()
	app:matident(1)
	app:matident(2)
	if self.ortho then
		local r = 1.2
		local zn, zf = -1000, 1000
		app:matortho(
			-ar * r,
			ar * r,
			-r,
			r,
			zn, zf)
		-- notice this is flipping y ... hmm TODO do that in matortho? idk...
	else
		local zn, zf = .1, 1000
		app:matfrustum(
			-ar * zn,
			ar * zn,
			-zn,
			zn,
			zn, zf)
	end

	app:matrot(-math.rad(th),x,y,z, 1)
	app:mattrans(-self.pos.x, -self.pos.y, -self.pos.z, 1)
	app:matscale(self.scale, self.scale, self.scale, 1)

	return mouseHandled
end

function Orbit:endDraw()
	local app = self.app
	ffi.copy(app.ram.modelMat, self.modelMatPush, ffi.sizeof(self.modelMatPush))
	app:onModelMatChange()
	ffi.copy(app.ram.viewMat, self.viewMatPush, ffi.sizeof(self.viewMatPush))
	app:onViewMatChange()
	ffi.copy(app.ram.projMat, self.projMatPush, ffi.sizeof(self.projMatPush))
	app:onProjMatChange()
--self:guiSetClipRect(0, 0, clipMax, clipMax)
	-- flush before disable depth test so the flush will use depth test...
	app:triBuf_flush()
	gl.glDisable(gl.GL_DEPTH_TEST)
end

return Orbit
