-------------------------------------------------------------
--------              utility functions              --------
-------------------------------------------------------------
function trim(s)
  return (s:gsub("^%s*(.-)%s*$", "%1"))
end

function isAllIdent(check)
	check = check:lower()
	if check == "**all**" then
		return true
	else
		return false
	end
end

function indexOf(t,val)
    for k,v in ipairs(t) do 
        if v == val then 
			return k 
		end
    end
end
-------------------------------------------------------------
---- defining functions used for parsing the config file ----
-------------------------------------------------------------

-- gets the next line out of the file
local function getNextLine(file)
	if file:isValid() then
		local line = file:read("*l")
		return line
	else
		ba.warning("Invalid file handle pased to readLine function")
		return nil
	end
end

-- looks up the set function which is connected to the specified keyword
local function getSetFunc(keyword)
	keyword = keyword:lower()
	for i,v in pairs(setTable) do
		index = i:lower()
		if index == keyword then
			return v
		end
	end
	return nil
end

local function addAllowedShipClass(value)
	if isAllIdent(value) then
		zoomAllowedShips = {}
		zoomAllowedShips[1] = value
		return false
	end
	if zoomAllowedShips[1] == nil or not isAllIdent(zoomAllowedShips[1]) then
		
		if indexOf(zoomAllowedShips,value) ~= nil then
			ba.warning("Ship " .. value .. " was specified more than once. Skipping...")
			return false
		end
		
		local ship = tb.ShipClasses[value]
		if ship ~= nil and ship:isValid() then
			table.insert(zoomAllowedShips, value)
			
			useWeaponEnd = false
			
			return true
		else
			ba.warning("Specified ship class '" .. value .. "' does not exist.")
			return false
		end
	end
end

local function addRestrictedShipClass(value)
	if isAllIdent(value) then
		zoomRestrictedShips = {}
		zoomRestrictedShips[1] = value
		return true
	end
	if zoomRestrictedShips[1] == nil or not isAllIdent(zoomRestrictedShips[1]) then
		
		if indexOf(zoomRestrictedShips,value) ~= nil then
			ba.warning("Ship " .. value .. " was specified more than once. Skipping...")
			return false
		end
		
		local ship = tb.ShipClasses[value]
		if ship ~= nil and ship:isValid() then
			table.insert(zoomRestrictedShips, value)
			
			useWeaponEnd = false
			
			return true
		else
			ba.warning("Specified ship class '" .. value .. "' does not exist.")
			return false
		end
	end
end

local function addAllowedWeaponClass(value)
	if isAllIdent(value) then
		zoomAllowedWeapons = {}
		zoomAllowedWeapons[1] = value
		return false
	end
	
	if zoomAllowedWeapons[1] == nil or not isAllIdent(zoomAllowedWeapons[1]) then
	
		if indexOf(zoomAllowedWeapons,value) ~= nil then
			ba.warning("Weapon " .. value .. " was specified more than once. Skipping...")
			return false
		end
	
		local weapon = tb.WeaponClasses[value]
		if weapon ~= nil and weapon:isValid() then
			table.insert(zoomAllowedWeapons, value)
			
			useWeaponEnd = false
			
			return true
		else
			ba.warning("Specified weapon class '" .. value .. "' does not exist.")
			return false
		end
	end
end

local function addRestrictedWeaponClass(value)
	if isAllIdent(value) then
		zoomRestrictedWeapons = {}
		zoomRestrictedWeapons[1] = value
		return false
	end
	if zoomRestrictedWeapons[1] == nil or not isAllIdent(zoomRestrictedWeapons[1]) then
		
		if indexOf(zoomRestrictedWeapons,value) ~= nil then
			ba.warning("Weapon " .. value .. " was specified more than once. Skipping...")
			return false
		end
		
		local weapon = tb.WeaponClasses[value]
		if weapon ~= nil and weapon:isValid() then
			table.insert(zoomRestrictedWeapons, value)
			
			useWeaponEnd = false
			
			return true
		else
			ba.warning("Specified weapon class '" .. value .. "' does not exist.")
			return false
		end
	end
end

local function parseLine(line)
	if line == nil or line == "" then -- Check if this line needs to be parsed
		return
	end
	
	line = trim(line)
		
	local comm_s, comm_e = line:find("--",1,true) 
	if comm_s ~= nil then
		line = line:sub(1,(comm_s - 1))
	end
	if line == "" then -- was the line fully commented away?
		return -- Nothing to be done...
	end
	local key_s, key_e = line:find(":",1,true)
	local val_s, val_e = line:find(".",key_e + 1)
	
	if key_s == nil then -- malformatted line
		return "Malformatted line: " .. line .. "\nSkipping..."
	end
	
	local keyword = line:sub(1,(key_e - 1))
	
	if val_s == nil then -- Maybe we_ve got a digit value
		-- We have no value
		return "Keyword '" .. keyword .. "' hasn't specified a value. Skipping..."
	end
	
	keyword = keyword:lower() -- make the keyword lowercase
	
	local val = line:sub(val_s, line:len())
	
	if val ~= nil then
		val = trim(val)
		keyword = trim(keyword)
		local setFunc = getSetFunc(keyword)
		if setFunc == nil then
			return "Unknown keyword: '" .. keyword .. "'"
		end
		setFunc(val)
	else
		return nil
	end
end

local function initSetTable()
	setTable = {}
	setTable["Zoom Factor"]=
	function(value) 
		local val = tonumber(value)
		if val == nil then
			return
			ba.warning("Non numeric value given to 'Zoom Factor'. Skipping...")
		end
		zoomValue = val
	end
	
	setTable["Transition Time"]=
	function(value)
		local val = tonumber(value)
		if val == nil then
			ba.warning("Non numeric value given to 'Transition Time'. Skipping...")
			return
		end
		transitionTime = val
	end
	
	setTable["Sensitivity"]=
	function(value)
		local val = tonumber(value)
		if val == nil then
			ba.warning("Non numeric value given to 'Sensitivity'. Skipping...")
			return
		end
		sensitivity = val
	end
	
	setTable["Allowed Ship Class"]=addAllowedShipClass
	
	setTable["Restricted Ship Class"]=addRestrictedShipClass
	
	setTable["Allowed Weapon Class"]=addAllowedWeaponClass
	
	setTable["Restricted Weapon Class"]=addRestrictedWeaponClass
	
	setTable["Linked Zoom"]=
	function(value)
		local yesKey = "yes"
		value = value:lower()
		if value == yesKey then
			linkedZoom = true
		else
			linkedZoom = false
		end
	end
	
	setTable["Key"]=
	function(key)
		if key == nil then
			ba.warning("Invalid value given for 'Key'. Skipping...")
			return
		end
		zoomKey = key
	end
	
	setTable["Zoom Lock Key"]=
	function(key)
		if key == nil then
			ba.warning("Invalid value given for 'Zoom Lock Key'. Skipping...")
			return
		end
		zoomLockKey = key
	end
	
	setTable["Progress Offset X"]=
	function(value)
		local val = tonumber(value)
		if val == nil then
			ba.warning("Non numeric value given to 'Progress Offset X'. Skipping...")
			return
		end
		offset_x = val
	end
	
	setTable["Progress Offset Y"]=
	function(value)
		local val = tonumber(value)
		if val == nil then
			ba.warning("Non numeric value given to 'Progress Offset >'. Skipping...")
			return
		end
		offset_Y = val
	end
	
	setTable["Progress Height"]=
	function(value)
		local val = tonumber(value)
		if val == nil then
			ba.warning("Non numeric value given to 'Progress Height'. Skipping...")
			return
		end
		heigth = val
	end
	
	setTable["Progress Width"]=
	function(value)
		local val = tonumber(value)
		if val == nil then
			ba.warning("Non numeric value given to 'Progress Width'. Skipping...")
			return
		end
		width = val
	end
	
	setTable["Weapon End"]=
	function(val)
		if val == nil then
			ba.warning("Invalid value given to 'Weapon End'!")
			return
		end	
		weaponEnd = val
		useWeaponEnd = true
	end
	
end

local function readConfig()
	if cf.fileExists(configFile, "data/config", true) then
		local config = cf.openFile(configFile)
		if config:isValid() then
			initSetTable()
			local lineNumber = 0
			local parse = true
			while parse do
				lineNumber = lineNumber + 1
				local line = getNextLine(config)
				if line == nil then
					parse = false
					break
				end
				local error = parseLine(line) -- parse the found line
				if error ~= nil then
					ba.warning(configFile .. " [" .. tostring(lineNumber) .. "] : " .. error)
				end
			end
			-- Prevent error on more recent FSO builds
			config:close()
		else
			ba.warning("Handle to config file is invalid. Is the file readable?")
		end
	else
		ba.warning("No config file found. Returning to default")
	end
end

-------------------------------------------------------------
------these functions will be use for the FRED interface-----
-------------------------------------------------------------

function z_zoomIn()
	if defaultZoomSet and cam:isValid() then
		cam:setFOV(normalFOV) -- Resetting the FOV in case we have not zoomed out completely
	end
	fredOverride = true
	zoomIn()
end

function z_zoomOut()
	fredOverride = false
	zoomOut()
end

function z_alS(ship)
	if addAllowedShipClass(ship) then
		table.insert(zoomTempAllowedShip, ship)
	end
end

function z_alW(weapon)
	if addAllowedWeaponClass(weapon) then
		table.insert(zoomTempAllowedWeapon, weapon)
	end
end

function z_reS(ship)
	if addRestrictedShipClass(ship) then
		table.insert(zoomTempRestrictedShip, ship)
	end
end

function z_reW(weapon)
	if addRestrictedWeaponClass(weapon) then
		table.insert(zoomTempRestrictedWeapon, weapon)
	end
end


-------------------------------------------------------------
------      defining functions used in the script      ------
-------------------------------------------------------------
function removeTemps()
	for i,v in ipairs(zoomTempAllowedShip) do
		index = indexOf(zoomAllowedShips,v)
		if index ~= nil then
			zoomAllowedShips[index]=nil
		end
	end
	
	for i,v in ipairs(zoomTempRestrictedShip) do
		index = indexOf(zoomRestrictedShips,v)
		if index ~= nil then
			zoomRestrictedShips[index]=nil
		end
	end
	
	for i,v in ipairs(zoomTempAllowedWeapon) do
		index = indexOf(zoomAllowedWeapons,v)
		if index ~= nil then
			zoomAllowedWeapons[index]=nil
		end
	end
	
	for i,v in ipairs(zoomTempRestrictedWeapon) do
		index = indexOf(zoomRestrictedWeapons,v)
		if index ~= nil then
			zoomRestrictedWeapons[index]=nil
		end
	end
end

local function checkTables(allTbl, restrTbl, checkHandle)
	if checkHandle == nil then
		return false
	end
	if allTbl ~= nil and restrTbl ~= nil then	
		if isAllIdent(allTbl[1]) then
			if #restrTbl > 0 then
				if isAllIdent(restrTbl[1]) then
					return true
				end
			end
			for i,v in ipairs(restrTbl) do
			if checkHandle.Name == v then
					return false
				end
			end
			return true
		elseif isAllIdent(restrTbl[1]) then
			if #allTbl > 0 then
				if isAllIdent(allTbl[1]) then
					return true
				end
			end
			for i,v in ipairs(allTbl) do
				if checkHandle.Name == v then
					return true
				end
			end
			return false
		else
			local allowed = false
			for i,v in ipairs(allTbl) do
				if checkHandle.Name == v then
					allowed = true
					break
				end
			end
			for i,v in ipairs(restrTbl) do
				if checkHandle.Name == v then
					allowed = false
					break
				end
			end
			return allowed
		end
	end
	return true
end

function zoom_isAllowedShip(shipClass)
	if useWeaponEnd then
		return true
	else
		return checkTables(zoomAllowedShips,zoomRestrictedShips,shipClass)
	end
end

function zoom_isAllowedWeapon(weaponClass)
	if useWeaponEnd then
		local name = weaponClass.Name
		local start = name:find("#", 0, true)
		if not start then
			return false
		end
		local substr = name:sub(start + 1, name:len())
		
		return substr:lower() == weaponEnd:lower()
	else
		return checkTables(zoomAllowedWeapons,zoomRestrictedWeapons,weaponClass)
	end
end

function getProgressString() 
	local progressString = "Zooming"
	if zoomingIn then
		progressString = progressString .. " in:"
	elseif zoomingOut then
		progressString = progressString .. " out:"
	elseif zoomedIn then
		progressString = "Zoomed in:"
	elseif zoomedOut then
		progressString = "Zoomed out:"
	else
		progressString = "Zoomed:"
	end
	return progressString
end

function handleControls()
	if zoomingIn or zoomedIn then
		if currentProgr > 0 then
			local tempSensValue = sensitivity * 100 / currentProgr
			
			if tempSensValue <= 1 then
				local ci = ba.getControlInfo()
				ci.Pitch = ci.Pitch * tempSensValue
				ci.Heading = ci.Heading * tempSensValue
				ci.Bank = ci.Bank * tempSensValue
			end
		end
	end
end

function isAllowedToZoom()
	local allowedShip = zoom_isAllowedShip(hv.Player.Class)
	
	local allowedWeapon = true
	local primWeaponBank = hv.Player.PrimaryBanks
	if not linkedZoom and primWeaponBank.Linked then
		allowedWeapon = false
	else
		for i=0, #primWeaponBank do
			local v = primWeaponBank[i]
			if v.Armed then
				if zoom_isAllowedWeapon(tb.WeaponClasses[v.WeaponClass.Name]) then
					allowedWeapon = true
					break
				else
					allowedWeapon = false
				end
			end
		end
		if (allowedWeapon and not useWeaponEnd) or (useWeaponEnd and not allowedWeapon) then
			local secWeaponBank = hv.Player.SecondaryBanks
			for i=0, #secWeaponBank do
				local v = secWeaponBank[i]
				if v.Armed then
					if zoom_isAllowedWeapon(tb.WeaponClasses[v.WeaponClass.Name]) then
						allowedWeapon = true
						break
					else
						allowedWeapon = false
					end
				end
			end
		end
	end
	
	return allowedShip and allowedWeapon
end

function zoomIn()
	if not cam or not cam:isValid() then
		for i=1,#gr.Cameras do
			if gr.Cameras[i].Name == "Main camera" then -- There is no better method than this :(
				cam = gr.Cameras[i]
				break
			end
		end
	end
	
	if not zooming then
		if hv.Player:isValid() then
			if cam ~= nil and cam:isValid() then
				if isAllowedToZoom() then
					if currentProgr > 0 and currentProgr < 100 then
						stepWidth = width / currentProgr -- Setting the stepWidth in case the zoom may not have been completed
					end
					
					zoomEndProgress = 100
				
					zooming = true
					
					zoomingIn = true
					zoomedIn = false
					zoomedOut = false
					zoomingOut = false
					
					if not defaultZoomSet then
						normalFOV = cam.FOV
						defaultZoomSet = true
					end
					zoom = normalFOV * zoomValue
					
					ba.setControlMode(LUA_FULL_CONTROLS)
					
					cam:setFOV(zoom,transitionTime,transitionTime / 2,transitionTime / 4)
				end
			end
		end
	end
end

function zoomOut()
	if zooming and not zoomOutOverride then
		if hv.Player:isValid() then
			if cam ~= nil and cam:isValid() then
			
				stepWidth = width / 100
			
				zoomingIn = false
				zoomingOut = true
				zoomedIn = false
				zoomedOut = false
				
				if currentProgr > 0 then
					zoomEndProgress = currentProgr -- Save the progress we made as we begin to zoom back
				end
				
				cam:setFOV(normalFOV,transitionTime,transitionTime / 2,transitionTime / 4)
				
				ba.setControlMode(NORMAL_CONTROLS)
				
				fredOverride = false
			end
		end
	end
end

function drawProgress()
	progressString = getProgressString()
	
	gr.setColor(0,255,0)
	
	local stringWidth = gr.getStringWidth(progressString);
	gr.drawString(progressString,30,70)
		
	local realOffset_x = offset_x + stringWidth + 10
	gr.drawRectangle(realOffset_x, offset_y,realOffset_x + width, offset_y + heigth, zoomedIn)
	local frameTime = ba.getFrametime()
	
	local progressLineOffset_x = realOffset_x + stepWidth * currentProgr
	gr.drawLine(progressLineOffset_x, offset_y, progressLineOffset_x, offset_y + heigth)
	
	if not isAllowedToZoom() and not zoomOutOverride then
		if zoomingIn or zoomedIn then
			zoomOut()
			zoomOutOverride = true
		end
	end
	
	local thisFrameProg = (frameTime / transitionTime) * zoomEndProgress
	if zoomingIn then
		currentProgr = currentProgr + thisFrameProg
		if currentProgr >= 100 then
			currentProgr = 100
		zoomingIn = false
		zoomedIn = true
		end
	elseif zoomingOut then
		currentProgr = currentProgr - thisFrameProg
		if currentProgr <= 0 then
			currentProgr = 0
			zoomOutOverride = false
			zoomedOut = true
			zoomingOut = false
			zooming = false
		end
	end
end

local function init() 
	if useConfig then
		readConfig() -- read the config file if exists
	end
end

local function initVars() 
	-- Some things to tell the script to do something and some default values
	useConfig = true
	
	zoomValue = 0.1
	normalFOV = 0.75
	transitionTime = 2
	zooming = false
	defaultZoomSet = false
	configFile = "zoom_config.cfg"
	zoomOutOverride = false
	linkedZoom = true
	zoomKey = "d"
	zoomLockKey = nil
	
	fredOverride = false
	
	lockedZoom = false
		
	-- Setting the values to be used in the $On Frame: hook
	currentProgr = 0
	zoomEndProgress = 100

	zoomingIn = false
	zoomedIn = false

	zoomingOut = false
	zoomedOut = true

	-- Constants for drawing the progress
	offset_x = 30
	offset_y = 70
	heigth = 8
	width = 80

	stepWidth = width / 100

	-- Settings for the sensitivity
	sensitivity = 0.25

	-- The camera which is used for zooming (is initialized in the first $Key Pressed: hook)
	cam = nil
	
	-- Tables that hold the allowed/restricted informations
	-- Ships
	zoomAllowedShips = {"**all**"}
	zoomTempAllowedShip = {}
	
	zoomRestrictedShips = {}
	zoomTempRestrictedShip = {}
	
	-- Weapons
	zoomAllowedWeapons = {"**all**"}
	zoomTempAllowedWeapon = {}	
	
	zoomRestrictedWeapons = {}
	zoomTempRestrictedWeapon = {}
	
	-- or the equal weapon-postfix method
	weaponEnd = "zoom"
	useWeaponEnd = false
end

function resetZoomScript()
	-- Reset camera in use...
	cam = nil
end

-- Initialize the global variables
initVars()

-- Initialize the rest
init()

ba.print("Zoom Script initialized!\n")