--[[
This is a deriation of ciribobs Autolase at https://github.com/ciribob/DCS-JTACAutoLaze/blob/master/JTACAutoLase.lua
Parts of MIST used. Source is  https://github.com/mrSkortch/MissionScriptingTools/blob/master/mist.lua
]]

jtac.AutoLase = {}

-- CONFIG
jtac.AutoLase.JTAC_maxDistance = 8000 -- How far a JTAC can "see" in meters (with LOS)

jtac.AutoLase.JTAC_smokeOn = true -- enables marking of target with smoke, can be overriden by the JTACAutoLase in editor

jtac.AutoLase.JTAC_smokeColour = 1 -- Green = 0 , Red = 1, White = 2, Orange = 3, Blue = 4

jtac.AutoLase.JTAC_jtacStatusF10 = true -- enables F10 JTAC Status menu

jtac.AutoLase.JTAC_location = true -- shows location in JTAC message, can be overriden by the JTACAutoLase in editor

jtac.AutoLase.JTAC_lock =  "all" -- "vehicle" OR "troop" OR "all" forces JTAC to only lock vehicles or troops or all ground units 

-- END CONFIG

-- BE CAREFUL MODIFYING BELOW HERE

jtac.AutoLase.GLOBAL_JTAC_LASE = {}
jtac.AutoLase.GLOBAL_JTAC_IR = {}
jtac.AutoLase.GLOBAL_JTAC_SMOKE = {}
jtac.AutoLase.GLOBAL_JTAC_UNITS = {} -- list of JTAC units for f10 command
jtac.AutoLase.GLOBAL_JTAC_CURRENT_TARGETS = {}
jtac.AutoLase.GLOBAL_JTAC_RADIO_ADDED = {} --keeps track of who's had the radio command added
jtac.AutoLase.GLOBAL_JTAC_LASER_CODES = {} -- keeps track of laser codes for jtac


function jtac.AutoLase.JTACAutoLase(jtacGroupName, laserCode, smoke, lock, colour)

    if smoke == nil then
    
        smoke = jtac.AutoLase.JTAC_smokeOn  
    end

    if lock == nil then
    
        lock = jtac.AutoLase.JTAC_lock
    end

    if colour == nil then
    	colour = jtac.AutoLase.JTAC_smokeColour
    end

    jtac.AutoLase.GLOBAL_JTAC_LASER_CODES[jtacGroupName] = laserCode
    
    local jtacGroup = jtac.AutoLase.getGroup(jtacGroupName)
    local jtacUnit

    if jtacGroup == nil or #jtacGroup == 0 then

      jtac.AutoLase.notify("JTAC Group " .. jtacGroupName .. " KIA!", 10)

        --remove from list
        jtac.AutoLase.GLOBAL_JTAC_UNITS[jtacGroupName] = nil

        jtac.AutoLase.cleanupJTAC(jtacGroupName)

        return
    else

        jtacUnit = jtacGroup[1]
        --add to list
        jtac.AutoLase.GLOBAL_JTAC_UNITS[jtacGroupName] = jtacUnit:getName()


    end


    -- search for current unit

    if jtacUnit:isActive() == false then

        jtac.AutoLase.cleanupJTAC(jtacGroupName)

        env.info(jtacGroupName .. ' Not Active - Waiting 30 seconds')
        timer.scheduleFunction(jtac.AutoLase.timerJTACAutoLase, { jtacGroupName, laserCode,smoke,lock,colour}, timer.getTime() + 30)

        return
    end

    local enemyUnit = jtac.AutoLase.getCurrentUnit(jtacUnit, jtacGroupName)

    if enemyUnit == nil and jtac.AutoLase.GLOBAL_JTAC_CURRENT_TARGETS[jtacGroupName] ~= nil then

        local tempUnitInfo = jtac.AutoLase.GLOBAL_JTAC_CURRENT_TARGETS[jtacGroupName]

  --      env.info("TEMP UNIT INFO: " .. tempUnitInfo.name .. " " .. tempUnitInfo.unitType)

        local tempUnit = Unit.getByName(tempUnitInfo.name)

        if tempUnit ~= nil and tempUnit:getLife() > 0 and tempUnit:isActive() == true then
            jtac.AutoLase.notify(jtacGroupName .. " target " .. tempUnitInfo.unitType .. " lost. Scanning for Targets. ", 10)
        else
            jtac.AutoLase.notify(jtacGroupName .. " target " .. tempUnitInfo.unitType .. " KIA. Good Job! Scanning for Targets. ", 10)
        end

        --remove from smoke list
        jtac.AutoLase.GLOBAL_JTAC_SMOKE[tempUnitInfo.name] = nil

        -- remove from target list
        jtac.AutoLase.GLOBAL_JTAC_CURRENT_TARGETS[jtacGroupName] = nil

        --stop lasing
        jtac.AutoLase.cancelLase(jtacGroupName)

    end


    if enemyUnit == nil then
        enemyUnit = jtac.AutoLase.findNearestVisibleEnemy(jtacUnit,lock)

        if enemyUnit ~= nil then

            -- store current target for easy lookup
            jtac.AutoLase.GLOBAL_JTAC_CURRENT_TARGETS[jtacGroupName] = { name = enemyUnit:getName(), unitType = enemyUnit:getTypeName(), unitId = enemyUnit:getID() }

            jtac.AutoLase.notify(jtacGroupName .. " lasing new target " .. enemyUnit:getTypeName() .. '. CODE: ' .. laserCode .. "\n" .. jtac.AutoLase.getPositionString(enemyUnit), 10)

            -- create smoke
            if smoke == true then

                --create first smoke
                jtac.AutoLase.createSmokeMarker(enemyUnit,colour)
            end
        end
    end

    if enemyUnit ~= nil then

        jtac.AutoLase.laseUnit(enemyUnit, jtacUnit, jtacGroupName, laserCode)

        --   env.info('Timer timerSparkleLase '..jtacGroupName.." "..laserCode.." "..enemyUnit:getName())
        timer.scheduleFunction(jtac.AutoLase.timerJTACAutoLase, { jtacGroupName, laserCode, smoke,lock,colour }, timer.getTime() + 1)


        if smoke == true then
            local nextSmokeTime = jtac.AutoLase.GLOBAL_JTAC_SMOKE[enemyUnit:getName()]

            --recreate smoke marker after 5 mins
            if nextSmokeTime ~= nil and nextSmokeTime < timer.getTime() then

              jtac.AutoLase.createSmokeMarker(enemyUnit, colour)
            end
        end

    else
        -- env.info('LASE: No Enemies Nearby')

        -- stop lazing the old spot
        jtac.AutoLase.cancelLase(jtacGroupName)
        --  env.info('Timer Slow timerSparkleLase '..jtacGroupName.." "..laserCode.." "..enemyUnit:getName())

        timer.scheduleFunction(jtac.AutoLase.timerJTACAutoLase, { jtacGroupName, laserCode, smoke,lock,colour }, timer.getTime() + 5)
    end
end


-- used by the timer function
function jtac.AutoLase.timerJTACAutoLase(args)

  jtac.AutoLase.JTACAutoLase(args[1], args[2], args[3],args[4])
end

function jtac.AutoLase.cleanupJTAC(jtacGroupName)
    -- clear laser - just in case
    jtac.AutoLase.cancelLase(jtacGroupName)

    -- Cleanup
    jtac.AutoLase.GLOBAL_JTAC_UNITS[jtacGroupName] = nil

    jtac.AutoLase.GLOBAL_JTAC_CURRENT_TARGETS[jtacGroupName] = nil
end


function jtac.AutoLase.notify(message, displayFor)

    trigger.action.outTextForCoalition(coalition.side.BLUE, message, displayFor)
    -- trigger.action.outSoundForCoalition(coalition.side.BLUE, "radiobeep.ogg")
end

function jtac.AutoLase.createSmokeMarker(enemyUnit,colour)

    --recreate in 5 mins
    jtac.AutoLase.GLOBAL_JTAC_SMOKE[enemyUnit:getName()] = timer.getTime() + 300.0

    -- move smoke 2 meters above target for ease
    local enemyPoint = enemyUnit:getPoint()
    trigger.action.smoke({ x = enemyPoint.x, y = enemyPoint.y + 2.0, z = enemyPoint.z }, colour)
end

function jtac.AutoLase.cancelLase(jtacGroupName)

    --local index = "JTAC_"..jtacUnit:getID()

    local tempLase = jtac.AutoLase.GLOBAL_JTAC_LASE[jtacGroupName]

    if tempLase ~= nil then
        Spot.destroy(tempLase)
        jtac.AutoLase.GLOBAL_JTAC_LASE[jtacGroupName] = nil

        --      env.info('Destroy laze  '..index)

        tempLase = nil
    end

    local tempIR = jtac.AutoLase.GLOBAL_JTAC_IR[jtacGroupName]

    if tempIR ~= nil then
        Spot.destroy(tempIR)
        jtac.AutoLase.GLOBAL_JTAC_IR[jtacGroupName] = nil

        --  env.info('Destroy laze  '..index)

        tempIR = nil
    end
end

function jtac.AutoLase.laseUnit(enemyUnit, jtacUnit, jtacGroupName, laserCode)

    --jtac.AutoLase.cancelLase(jtacGroupName)

    local spots = {}

    local enemyVector = enemyUnit:getPoint()
    local enemyVectorUpdated = { x = enemyVector.x, y = enemyVector.y + 2.0, z = enemyVector.z }

    local oldLase = jtac.AutoLase.GLOBAL_JTAC_LASE[jtacGroupName]
    local oldIR = jtac.AutoLase.GLOBAL_JTAC_IR[jtacGroupName]

    if oldLase == nil or oldIR == nil then

        -- create lase
        local status, result = pcall(function()
            spots['irPoint'] = Spot.createInfraRed(jtacUnit, { x = 0, y = 2.0, z = 0 }, enemyVectorUpdated)
            spots['laserPoint'] = Spot.createLaser(jtacUnit, { x = 0, y = 2.0, z = 0 }, enemyVectorUpdated, laserCode)
            return spots
        end)

        if not status then
            env.error('ERROR: ' .. assert(result), false)
        else
            if result.irPoint then

                --    env.info(jtacUnit:getName() .. ' placed IR Pointer on '..enemyUnit:getName())

                jtac.AutoLase.GLOBAL_JTAC_IR[jtacGroupName] = result.irPoint --store so we can remove after

            end
            if result.laserPoint then

                --	env.info(jtacUnit:getName() .. ' is Lasing '..enemyUnit:getName()..'. CODE:'..laserCode)

                jtac.AutoLase.GLOBAL_JTAC_LASE[jtacGroupName] = result.laserPoint
            end
        end
    else

        -- update lase

        if oldLase~=nil then
            oldLase:setPoint(enemyVectorUpdated)
        end

        if oldIR ~= nil then
            oldIR:setPoint(enemyVectorUpdated)
        end

    end

end

-- get currently selected unit and check they're still in range
function jtac.AutoLase.getCurrentUnit(jtacUnit, jtacGroupName)


    local unit = nil

    if jtac.AutoLase.GLOBAL_JTAC_CURRENT_TARGETS[jtacGroupName] ~= nil then
        unit = Unit.getByName(jtac.AutoLase.GLOBAL_JTAC_CURRENT_TARGETS[jtacGroupName].name)
    end

    local tempPoint = nil
    local tempDist = nil
    local tempPosition = nil

    local jtacPosition = jtacUnit:getPosition()
    local jtacPoint = jtacUnit:getPoint()

    if unit ~= nil and unit:getLife() > 0 and unit:isActive() == true then

        -- calc distance
        tempPoint = unit:getPoint()
        --   tempPosition = unit:getPosition()

        tempDist = jtac.AutoLase.getDistance(tempPoint.x, tempPoint.z, jtacPoint.x, jtacPoint.z)
        if tempDist < jtac.AutoLase.JTAC_maxDistance then
            -- calc visible

            -- check slightly above the target as rounding errors can cause issues, plus the unit has some height anyways
            local offsetEnemyPos = { x = tempPoint.x, y = tempPoint.y + 2.0, z = tempPoint.z }
            local offsetJTACPos = { x = jtacPoint.x, y = jtacPoint.y + 2.0, z = jtacPoint.z }

            if land.isVisible(offsetEnemyPos, offsetJTACPos) then
                return unit
            end
        end
    end
    return nil
end


-- Find nearest enemy to JTAC that isn't blocked by terrain
function jtac.AutoLase.findNearestVisibleEnemy(jtacUnit, targetType)

    local x = 1
    local i = 1

    local units = nil
    local groupName = nil

    local nearestUnit = nil
    local nearestDistance = jtac.AutoLase.JTAC_maxDistance

    local redGroups = coalition.getGroups(1, Group.Category.GROUND)

    local jtacPoint = jtacUnit:getPoint()
    local jtacPosition = jtacUnit:getPosition()

    local tempPoint = nil
    local tempPosition = nil

    local tempDist = nil

    -- finish this function
    for i = 1, #redGroups do
        if redGroups[i] ~= nil then
            groupName = redGroups[i]:getName()
            units = jtac.AutoLase.getGroup(groupName)
            if #units > 0 then

                for x = 1, #units do

                    --check to see if a JTAC has already targeted this unit
                    local targeted = jtac.AutoLase.alreadyTarget(jtacUnit,units[x])
                    local allowedTarget = true

                    if targetType == "vehicle" then
                        
                        allowedTarget = jtac.AutoLase.isVehicle(units[x])

                    elseif targetType == "troop" then

                        allowedTarget = jtac.AutoLase.isInfantry(units[x])

                    end


                    if units[x]:isActive() == true and targeted == false and allowedTarget == true then

                        -- calc distance
                        tempPoint = units[x]:getPoint()
                        -- tempPosition = units[x]:getPosition()

                        tempDist = jtac.AutoLase.getDistance(tempPoint.x, tempPoint.z, jtacPoint.x, jtacPoint.z)

                        --     env.info("tempDist" ..tempDist)
                        --    env.info("jtac.AutoLase.JTAC_maxDistance" ..jtac.AutoLase.JTAC_maxDistance)

                        if tempDist < jtac.AutoLase.JTAC_maxDistance and tempDist < nearestDistance then

                            local offsetEnemyPos = { x = tempPoint.x, y = tempPoint.y + 2.0, z = tempPoint.z }
                            local offsetJTACPos = { x = jtacPoint.x, y = jtacPoint.y + 2.0, z = jtacPoint.z }


                            -- calc visible
                            if land.isVisible(offsetEnemyPos, offsetJTACPos) then

                                nearestDistance = tempDist
                                nearestUnit = units[x]
                            end

                        end
                    end
                end
            end
        end
    end


    if nearestUnit == nil then
        return nil
    end

    return nearestUnit
end
-- tests whether the unit is targeted by another JTAC
function jtac.AutoLase.alreadyTarget(jtacUnit, enemyUnit)

    for y , jtacTarget in pairs(jtac.AutoLase.GLOBAL_JTAC_CURRENT_TARGETS) do

        if jtacTarget.unitId == enemyUnit:getID() then
           -- env.info("ALREADY TARGET")
            return true
        end

    end

    return false

end


-- Returns only alive units from group but the group / unit may not be active

function jtac.AutoLase.getGroup(groupName)

    local groupUnits = Group.getByName(groupName)

    local filteredUnits = {} --contains alive units
    local x = 1

    if groupUnits ~= nil then

        groupUnits = groupUnits:getUnits()

        if groupUnits ~= nil and #groupUnits > 0 then
            for x = 1, #groupUnits do
                if groupUnits[x]:getLife() > 0 then
                    table.insert(filteredUnits, groupUnits[x])
                end
            end
        end
    end

    return filteredUnits
end

-- Distance measurement between two positions, assume flat world

function jtac.AutoLase.getDistance(xUnit, yUnit, xZone, yZone)
    local xDiff = xUnit - xZone
    local yDiff = yUnit - yZone

    return math.sqrt(xDiff * xDiff + yDiff * yDiff)
end

-- gets the JTAC status and displays to coalition units
function jtac.AutoLase.getJTACStatus()

    --returns the status of all JTAC units

    local jtacGroupName = nil
    local jtacUnit = nil
    local jtacUnitName = nil
    local disp_time = 10

    local message = "JTAC STATUS: \n\n"

    for jtacGroupName, jtacUnitName in pairs(jtac.AutoLase.GLOBAL_JTAC_UNITS) do

        --look up units
        jtacUnit = Unit.getByName(jtacUnitName)

        if jtacUnit ~= nil and jtacUnit:getLife() > 0 and jtacUnit:isActive() == true then

            local enemyUnit = jtac.AutoLase.getCurrentUnit(jtacUnit, jtacGroupName)

            local laserCode =  jtac.AutoLase.GLOBAL_JTAC_LASER_CODES[jtacGroupName]

            if laserCode == nil then
            	laserCode = "UNKNOWN"
            end

            if enemyUnit ~= nil and enemyUnit:getLife() > 0 and enemyUnit:isActive() == true then
                message = message .. "" .. jtacGroupName .. " targeting " .. enemyUnit:getTypeName().. " CODE: ".. laserCode .. "\n" .. jtac.AutoLase.getPositionString(enemyUnit) .. "\n"
                
                -- a valid target will increase the display time
                disp_time = 60
            else
                message = message .. "" .. jtacGroupName .. " searching for targets at " .. jtac.AutoLase.shortPosString(jtacUnit) .."\n"
            end
        end
    end

    jtac.AutoLase.notify(message, disp_time)
end


-- Radio command for players (F10 Menu)

function jtac.AutoLase.addRadioCommands()

    --looop over all players and add command
    --    missionCommands.addCommandForCoalition( coalition.side.BLUE, "JTAC Status" ,nil, jtac.AutoLase.getJTACStatus ,nil)

    timer.scheduleFunction(jtac.AutoLase.addRadioCommands, nil, timer.getTime() + 10)

    local blueGroups = coalition.getGroups(coalition.side.BLUE)
    local x = 1

    if blueGroups ~= nil then
        for x, tmpGroup in pairs(blueGroups) do


            local index = "GROUP_" .. Group.getID(tmpGroup)
            --   env.info("adding command for "..index)
            if jtac.AutoLase.GLOBAL_JTAC_RADIO_ADDED[index] == nil then
                -- env.info("about command for "..index)
                missionCommands.addCommandForGroup(Group.getID(tmpGroup), "JTAC Status", nil, jtac.AutoLase.getJTACStatus, nil)
                jtac.AutoLase.GLOBAL_JTAC_RADIO_ADDED[index] = true
               -- env.info("Added command for " .. index)
            end
        end
    end
end

function jtac.AutoLase.isInfantry(unit)

    local typeName = unit:getTypeName()

    --type coerce tostring
    typeName = string.lower(typeName.."")

    local soldierType = { "infantry","paratrooper","stinger","manpad"}

    for key,value in pairs(soldierType) do
        if string.match(typeName, value) then
            return true
        end
    end

    return false

end

-- assume anything that isnt soldier is vehicle
function jtac.AutoLase.isVehicle(unit)

    if jtac.AutoLase.isInfantry(unit) then
        return false
    end

    return true

end
    

function jtac.AutoLase.getPositionString(unit)

    if jtac.AutoLase.JTAC_location == false then
        return ""
    end

    local pos_string = jtac.AutoLase.fullPosString(unit)
	return pos_string

end


function jtac.AutoLase.shortPosString(unit)
    local p = unit:getPosition().p
	local lat, long = coord.LOtoLL(p)

    local latStr, longStr = util.coords.DMDMString(util.coords.DDtoDMDM(lat, long))
    local mgrsStr = util.coords.MGRSString(coord.LLtoMGRS(lat, long))

    -- render message
    local message = 
       "MGRS: " .. mgrsStr .. "\n"

    return message
end


function jtac.AutoLase.fullPosString(unit)
    local p = unit:getPosition().p
	local lat, long = coord.LOtoLL(p)

    local latStr, longStr = util.coords.DMDMString(util.coords.DDtoDMDM(lat, long))
    local mgrsStr = util.coords.MGRSString(coord.LLtoMGRS(lat, long))

    -- altitude in meter and feet
    local altm = string.format("%.0f", p.y)
    local altft = string.format("%.0f", util.conversion.fromM(p.y, "i_ft"))

    -- render message
    local message = 
       "        Lat: " .. latStr .. "\n"
    .. "        Lng: " .. longStr .. "\n"
    .. "        MGRS: " .. mgrsStr .. "\n"
    .. "        Altitude: " .. altm .. " m  (" .. altft .. " ft)" .. "\n"

    return message
end


function jtac.AutoLase.init()
  -- add the radio commands
  if jtac.AutoLase.JTAC_jtacStatusF10 == true then
    timer.scheduleFunction(jtac.AutoLase.addRadioCommands, nil, timer.getTime() + 1)
  end
end