--- **Functional** -- Modular, Automatic and Network capable Targeting and Interception System for Air Defenses
--
-- ===
--
-- **MANTIS** - Moose derived  Modular, Automatic and Network capable Targeting and Interception System
-- Controls a network of SAM sites. Uses detection to switch on the AA site closest to the enemy.   
-- Automatic mode (default since 0.8) can set-up your SAM site network automatically for you.   
-- Leverage evasiveness from SEAD, leverage attack range setting.   
--
-- ===
--
-- ## Missions:
--
-- ### [MANTIS - Modular, Automatic and Network capable Targeting and Interception System](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/MTS%20-%20Mantis/MTS-010%20-%20Basic%20Mantis%20Demo)
--
-- ===
--
-- ### Author : **applevangelist **
--
-- @module Functional.Mantis
-- @image Functional.Mantis.jpg
--
-- Date: Dec 2021

-------------------------------------------------------------------------
--- **MANTIS** class, extends Core.Base#BASE
-- @type MANTIS
-- @field #string ClassName
-- @field #string name Name of this Mantis
-- @field #string SAM_Templates_Prefix Prefix to build the #SET_GROUP for SAM sites
-- @field Core.Set#SET_GROUP SAM_Group The SAM #SET_GROUP
-- @field #string EWR_Templates_Prefix Prefix to build the #SET_GROUP for EWR group
-- @field Core.Set#SET_GROUP EWR_Group The EWR #SET_GROUP
-- @field Core.Set#SET_GROUP Adv_EWR_Group The EWR #SET_GROUP used for advanced mode
-- @field #string HQ_Template_CC The ME name of the HQ object
-- @field Wrapper.Group#GROUP HQ_CC The #GROUP object of the HQ
-- @field #table SAM_Table Table of SAM sites
-- @field #string lid Prefix for logging
-- @field Functional.Detection#DETECTION_AREAS Detection The #DETECTION_AREAS object for EWR
-- @field Functional.Detection#DETECTION_AREAS AWACS_Detection The #DETECTION_AREAS object for AWACS
-- @field #boolean debug Switch on extra messages
-- @field #boolean verbose Switch on extra logging
-- @field #number checkradius Radius of the SAM sites
-- @field #number grouping Radius to group detected objects
-- @field #number acceptrange Radius of the EWR detection
-- @field #number detectinterval Interval in seconds for the target detection
-- @field #number engagerange Firing engage range of the SAMs, see [https://wiki.hoggitworld.com/view/DCS_option_engagementRange]
-- @field #boolean autorelocate Relocate HQ and EWR groups in random intervals. Note: You need to select units for this which are *actually mobile*
-- @field #boolean advanced Use advanced mode, will decrease reactivity of MANTIS, if HQ and/or EWR network dies. Set SAMs to RED state if both are dead. Requires usage of an HQ object
-- @field #number adv_ratio Percentage to use for advanced mode, defaults to 100%
-- @field #number adv_state Advanced mode state tracker
-- @field #boolean advAwacs Boolean switch to use Awacs as a separate detection stream
-- @field #number awacsrange Detection range of an optional Awacs unit
-- @field #boolean UseEmOnOff Decide if we are using Emissions on/off (true) or AlarmState red/green (default)
-- @field Functional.Shorad#SHORAD Shorad SHORAD Object, if available
-- @field #boolean ShoradLink If true, #MANTIS has #SHORAD enabled
-- @field #number ShoradTime Timer in seconds, how long #SHORAD will be active after a detection inside of the defense range
-- @field #number ShoradActDistance Distance of an attacker in meters from a Mantis SAM site, on which Shorad will be switched on. Useful to not give away Shorad sites too early. Default 15km. Should be smaller than checkradius.
-- @extends Core.Base#BASE


--- *The worst thing that can happen to a good cause is, not to be skillfully attacked, but to be ineptly defended.* - Frédéric Bastiat
--
-- Simple Class for a more intelligent Air Defense System
--
-- #MANTIS
-- Moose derived  Modular, Automatic and Network capable Targeting and Interception System.
-- * Controls a network of SAM sites. Uses detection to switch on the SAM site closest to the enemy.
-- * **Automatic mode** (default since 0.8) can set-up your SAM site network automatically for you
-- * **Classic mode** behaves like before
-- * Leverage evasiveness from SEAD, leverage attack range setting
-- * Automatic setup of SHORAD based on groups of the class "short-range"
--
-- # 0. Base considerations and naming conventions
-- 
-- **Before** you start to set up your SAM sites in the mission editor, please think of naming conventions. This is especially critical to make
-- eveything work as intended, also if you have both a blue and a red installation!
-- 
-- You need three **non-overlapping** "name spaces" for everything to work properly:
-- 
-- * SAM sites, e.g. each **group name** begins with "Red SAM"
-- * EWR network and AWACS, e.g. each **group name** begins with "Red EWR" and *not* e.g. "Red SAM EWR" (overlap with  "Red SAM"), "Red EWR Awacs" will be found by "Red EWR"
-- * SHORAD, e.g. each **group name** begins with "Red SHORAD" and *not" e.g. just "SHORAD" because you might also have "Blue SHORAD"
-- 
-- It's important to get this right because of the nature of the filter-system in @{Core.Set#SET_GROUP}. Filters are "greedy", that is they
-- will match *any* string that contains the search string - hence we need to avoid that SAMs, EWR and SHORAD step on each other\'s toes.
-- 
-- Second, for auto-mode to work, the SAMs need the **SAM Type Name** in their group name, as MANTIS will determine their capabilities from this.
-- This is case-sensitive, so "sa-11" is not equal to "SA-11" is not equal to "Sa-11"!
-- 
-- Known SAM types at the time of writing are:
-- 
-- * Avenger
-- * Chaparrel
-- * Hawk
-- * Linebacker
-- * NASAMS
-- * Patriot
-- * Rapier
-- * Roland
-- * Silkworm (though strictly speaking this is a surface to ship missile)
-- * SA-2, SA-3, SA-5, SA-6, SA-7, SA-8, SA-9, SA-10, SA-11, SA-13, SA-15, SA-19
-- * and from HDS (see note below): SA-2, SA-3, SA-10B, SA-10C, SA-12, SA-17, SA-20A, SA-20B, SA-23, HQ-2
-- 
-- Following the example started above, an SA-6 site group name should start with "Red SAM SA-6" then, or a blue Patriot installation with e.g. "Blue SAM Patriot".   
-- **NOTE** If you are using the High-Digit-Sam Mod, please note that the **group name** for the following SAM types also needs to contain the keyword "HDS":
-- 
-- * SA-2 (with V759 missile, e.g. "Red SAM SA-2 HDS")
-- * SA-2 (with HQ-2 launcher, use HQ-2 in the group name, e.g. "Red SAM HQ-2" )
-- * SA-3 (with V601P missile, e.g. "Red SAM SA-3 HDS")
-- * SA-10B (overlap with other SA-10 types, e.g. "Red SAM SA-10B HDS")
-- * SA-10C (overlap with other SA-10 types, e.g. "Red SAM SA-10C HDS")
-- * SA-12 (launcher dependent range, e.g. "Red SAM SA-12 HDS")
-- * SA-23 (launcher dependent range, e.g. "Red SAM SA-23 HDS") 
-- 
-- The other HDS types work like the rest of the known SAM systems.
-- 
-- # 0.1 Set-up in the mission editor
-- 
-- Set up your SAM sites in the mission editor. Name the groups using a systematic approach like above.
-- Set up your EWR system in the mission editor. Name the groups using a systematic approach like above. Can be e.g. AWACS or a combination of AWACS and Search Radars like e.g. EWR 1L13 etc. 
-- Search Radars usually have "SR" or "STR" in their names. Use the encyclopedia in the mission editor to inform yourself.
-- Set up your SHORAD systems. They need to be **close** to (i.e. around) the SAM sites to be effective. Use **one** group per SAM location. SA-15 TOR systems offer a good missile defense.
-- 
-- [optional] Set up your HQ. Can be any group, e.g. a command vehicle.
-- 
-- # 1. Basic tactical considerations when setting up your SAM sites
--
-- ## 1.1 Radar systems and AWACS
--
--  Typically, your setup should consist of EWR (early warning) radars to detect and track targets, accompanied by AWACS if your scenario forsees that. Ensure that your EWR radars have a good coverage of the area you want to track.
--  **Location** is of highest importance here. Whilst AWACS in DCS has almost the "all seeing eye", EWR don't have that. Choose your location wisely, against a mountain backdrop or inside a valley even the best EWR system
--  doesn't work well. Prefer higher-up locations with a good view; use F7 in-game to check where you actually placed your EWR and have a look around. Apart from the obvious choice, do also consider other radar units
--  for this role, most have "SR" (search radar) or "STR" (search and track radar) in their names, use the encyclopedia to see what they actually do.
--
-- ## 1.2 SAM sites
--
-- Typically your SAM should cover all attack ranges. The closer the enemy gets, the more systems you will need to deploy to defend your location. Use a combination of long-range systems like the SA-5/10/11, midrange like SA-6 and short-range like
-- SA-2 for defense (Patriot, Hawk, Gepard, Blindfire for the blue side). For close-up defense and defense against HARMs or low-flying aircraft, helicopters it is also advisable to deploy SA-15 TOR systems, Shilka, Strela and Tunguska units, as well as manpads (Think Gepard, Avenger, Chaparral,
-- Linebacker, Roland systems for the blue side). If possible, overlap ranges for mutual coverage.
--
-- ## 1.3 Typical problems
--
-- Often times, people complain because the detection cannot "see" oncoming targets and/or Mantis switches on too late. Three typial problems here are
--
--   * bad placement of radar units,
--   * overestimation how far units can "see" and
--   * not taking into account that a SAM site will take (e.g for a SA-6) 30-40 seconds between switching on, acquiring the target and firing.
--
-- An attacker doing 350knots will cover ca 180meters/second or thus more than 6km until the SA-6 fires. Use triggers zones and the ruler in the missione editor to understand distances and zones. Take into account that the ranges given by the circles
-- in the mission editor are absolute maximum ranges; in-game this is rather 50-75% of that depending on the system. Fiddle with placement and options to see what works best for your scenario, and remember **everything in here is in meters**.
--
-- # 2. Start up your MANTIS with a basic setting
--
--        myredmantis = MANTIS:New("myredmantis","Red SAM","Red EWR",nil,"red",false)
--        myredmantis:Start()
--
-- Use
--
--  *     MANTIS:SetEWRGrouping(radius) [classic mode]
--  *     MANTIS:SetSAMRadius(radius) [classic mode]
--  *     MANTIS:SetDetectInterval(interval) [classic & auto modes]
--  *     MANTIS:SetAutoRelocate(hq, ewr) [classic & auto modes]
--
-- before starting #MANTIS to fine-tune your setup.
--
-- If you want to use a separate AWACS unit to support your EWR system, use e.g. the following setup:
--
--        mybluemantis = MANTIS:New("bluemantis","Blue SAM","Blue EWR",nil,"blue",false,"Blue Awacs")
--        mybluemantis:Start()
-- 
-- ## 2.1 Auto mode features
-- 
-- ### 2.1.1 You can now add Accept-, Reject- and Conflict-Zones to your setup, e.g. to consider borders or de-militarized zones:   
-- 
--        -- Parameters are tables of Core.Zone#ZONE objects!   
--        -- This is effectively a 3-stage filter allowing for zone overlap. A coordinate is accepted first when   
--        -- it is inside any AcceptZone. Then RejectZones are checked, which enforces both borders, but also overlaps of   
--        -- Accept- and RejectZones. Last, if it is inside a conflict zone, it is accepted.   
--        `mybluemantis:AddZones(AcceptZones,RejectZones,ConflictZones)`   
--        
--        
-- ### 2.1.2 Change the number of long-, mid- and short-range systems going live on a detected target:   
-- 
--        -- parameters are numbers. Defaults are 1,2,2,6 respectively
--        `mybluemantis:SetMaxActiveSAMs(Short,Mid,Long,Classic)`
-- 
-- ### 2.1.3 SHORAD will automatically be added from SAM sites of type "short-range"   
--        
-- ### 2.1.4 Advanced features   
-- 
--        -- switch off auto mode **before** you start MANTIS.   
--        `mybluemantis.automode = false`
--        
--        -- switch off auto shorad **before** you start MANTIS.   
--        `mybluemantis.autoshorad = false`
--        
--        -- scale of the activation range, i.e. don't activate at the fringes of max range, defaults below.   
--        -- also see engagerange below.   
--        `    self.radiusscale[MANTIS.SamType.LONG] = 1.1`   
--        `    self.radiusscale[MANTIS.SamType.MEDIUM] = 1.2`   
--        `    self.radiusscale[MANTIS.SamType.SHORT] = 1.3`   
-- 
-- # 3. Default settings [both modes unless stated otherwise]
--
-- By default, the following settings are active:
--
--  * SAM_Templates_Prefix = "Red SAM" - SAM site group names in the mission editor begin with "Red SAM"
--  * EWR_Templates_Prefix = "Red EWR" - EWR group names in the mission editor begin with "Red EWR" - can also be combined with an AWACS unit
--  * [classic mode] checkradius = 25000 (meters) - SAMs will engage enemy flights, if they are within a 25km around each SAM site - `MANTIS:SetSAMRadius(radius)`
--  * grouping = 5000 (meters) - Detection (EWR) will group enemy flights to areas of 5km for tracking - `MANTIS:SetEWRGrouping(radius)`
--  * detectinterval = 30 (seconds) - MANTIS will decide every 30 seconds which SAM to activate - `MANTIS:SetDetectInterval(interval)`
--  * engagerange = 95 (percent) - SAMs will only fire if flights are inside of a 95% radius of their max firerange - `MANTIS:SetSAMRange(range)`
--  * dynamic = false - Group filtering is set to once, i.e. newly added groups will not be part of the setup by default - `MANTIS:New(name,samprefix,ewrprefix,hq,coaltion,dynamic)`
--  * autorelocate = false - HQ and (mobile) EWR system will not relocate in random intervals between 30mins and 1 hour - `MANTIS:SetAutoRelocate(hq, ewr)`
--  * debug = false - Debugging reports on screen are set to off - `MANTIS:Debug(onoff)`
--
-- # 4. Advanced Mode
--
--  Advanced mode will *decrease* reactivity of MANTIS, if HQ and/or EWR  network dies.  Awacs is counted as one EWR unit. It will set SAMs to RED state if both are dead.  Requires usage of an **HQ** object and the **dynamic** option.
--
--  E.g.        mymantis:SetAdvancedMode( true, 90 )
--
--  Use this option if you want to make use of or allow advanced SEAD tactics.
--
-- # 5. Integrate SHORAD [classic mode]
--
--  You can also choose to integrate Mantis with @{Functional.Shorad#SHORAD} for protection against HARMs and AGMs. When SHORAD detects a missile fired at one of MANTIS' SAM sites, it will activate SHORAD systems in
--  the given defense checkradius around that SAM site. Create a SHORAD object first, then integrate with MANTIS like so:
--
--          local SamSet = SET_GROUP:New():FilterPrefixes("Blue SAM"):FilterCoalitions("blue"):FilterStart()
--          myshorad = SHORAD:New("BlueShorad", "Blue SHORAD", SamSet, 22000, 600, "blue")
--          -- now set up MANTIS
--          mymantis = MANTIS:New("BlueMantis","Blue SAM","Blue EWR",nil,"blue",false,"Blue Awacs")
--          mymantis:AddShorad(myshorad,720)
--          mymantis:Start()
--  
--  If you systematically name your SHORAD groups starting with "Blue SHORAD" you'll need exactly **one** SHORAD instance to manage all SHORAD groups.
--  
--  (Optionally) you can remove the link later on with
--
--          mymantis:RemoveShorad()
--
-- # 6. Integrated SEAD
--  
--  MANTIS is using @{Functional.Sead#SEAD} internally to both detect and evade HARM attacks. No extra efforts needed to set this up! 
--  Once a HARM attack is detected, MANTIS (via SEAD) will shut down the radars of the attacked SAM site and take evasive action by moving the SAM
--  vehicles around (*if they are __drivable__*, that is). There's a component of randomness in detection and evasion, which is based on the
--  skill set of the SAM set (the higher the skill, the more likely). When a missile is fired from far away, the SAM will stay active for a 
--  period of time to stay defensive, before it takes evasive actions.
--  
--  You can link into the SEAD driven events of MANTIS like so:
--  
--        function mymantis:OnAfterSeadSuppressionPlanned(From, Event, To, Group, Name, SuppressionStartTime, SuppressionEndTime)
--          -- your code here - SAM site shutdown and evasion planned, but not yet executed
--          -- Time entries relate to timer.getTime() - see https://wiki.hoggitworld.com/view/DCS_func_getTime
--        end
--        
--        function mymantis:OnAfterSeadSuppressionStart(From, Event, To, Group, Name)
--          -- your code here - SAM site is emissions off and possibly moving
--        end
--        
--        function mymantis:OnAfterSeadSuppressionEnd(From, Event, To, Group, Name)
--          -- your code here - SAM site is back online
--        end
--  
-- @field #MANTIS
MANTIS = {
  ClassName             = "MANTIS",
  name                  = "mymantis",
  SAM_Templates_Prefix  = "",
  SAM_Group             = nil,
  EWR_Templates_Prefix  = "",
  EWR_Group             = nil,
  Adv_EWR_Group         = nil,
  HQ_Template_CC        = "",
  HQ_CC                 = nil,
  SAM_Table             = {},
  SAM_Table_Long        = {},
  SAM_Table_Medium      = {},
  SAM_Table_Short       = {},
  lid                   = "",
  Detection             = nil,
  AWACS_Detection       = nil,
  debug                 = false,
  checkradius           = 25000,
  grouping              = 5000,
  acceptrange           = 80000,
  detectinterval        = 30,
  engagerange           = 95,
  autorelocate          = false,
  advanced              = false,
  adv_ratio             = 100,
  adv_state             = 0,
  AWACS_Prefix          = "",
  advAwacs              = false,
  verbose               = false,
  awacsrange            = 250000,
  Shorad                = nil,
  ShoradLink            = false,
  ShoradTime            = 600,
  ShoradActDistance     = 25000,
  UseEmOnOff            = false,
  TimeStamp             = 0,
  state2flag            = false,
  SamStateTracker       = {},
  DLink                 = false,
  DLTimeStamp           = 0,
  Padding               = 10,
  SuppressedGroups      = {},
  automode              = true,
  autoshorad            = true,
  ShoradGroupSet        = nil,
}

--- Advanced state enumerator
-- @type MANTIS.AdvancedState
MANTIS.AdvancedState = {
  GREEN = 0,
  AMBER = 1,
  RED = 2,
}

--- SAM Type
-- @type MANTIS.SamType
MANTIS.SamType = {
  SHORT = "Short",
  MEDIUM = "Medium",
  LONG = "Long",
}

--- SAM data
-- @type MANTIS.SamData
-- @field #number Range Max firing range in km
-- @field #number Blindspot no-firing range (green circle)
-- @field #number Height Max firing height in km
-- @field #string Type #MANTIS.SamType of SAM, i.e. SHORT, MEDIUM or LONG (range)
-- @field #string Radar Radar typename on unit level (used as key)
MANTIS.SamData = {
  ["Hawk"] = { Range=44, Blindspot=0, Height=9, Type="Medium", Radar="Hawk" }, -- measures in km
  ["NASAMS"] = { Range=14, Blindspot=0, Height=3, Type="Short", Radar="NSAMS" },
  ["Patriot"] = { Range=99, Blindspot=0, Height=9, Type="Long", Radar="Patriot" },
  ["Rapier"] = { Range=6, Blindspot=0, Height=3, Type="Short", Radar="rapier" },
  ["SA-2"] = { Range=40, Blindspot=7, Height=25, Type="Medium", Radar="S_75M_Volhov" },
  ["SA-3"] = { Range=18, Blindspot=6, Height=18, Type="Short", Radar="5p73 s-125 ln" },
  ["SA-5"] = { Range=250, Blindspot=7, Height=40, Type="Long", Radar="5N62V" },
  ["SA-6"] = { Range=25, Blindspot=0, Height=8, Type="Medium", Radar="1S91" },
  ["SA-10"] = { Range=119, Blindspot=0, Height=18, Type="Long" , Radar="S-300PS 4"},
  ["SA-11"] = { Range=35, Blindspot=0, Height=20, Type="Medium", Radar="SA-11" },
  ["Roland"] = { Range=8, Blindspot=0, Height=3, Type="Short", Radar="Roland" },
  ["HQ-7"] = { Range=12, Blindspot=0, Height=3, Type="Short", Radar="HQ-7" },
  ["SA-9"] = { Range=4, Blindspot=0, Height=3, Type="Short", Radar="Strela" },
  ["SA-8"] = { Range=10, Blindspot=0, Height=5, Type="Short", Radar="Osa 9A33" },
  ["SA-19"] = { Range=8, Blindspot=0, Height=3, Type="Short", Radar="Tunguska" },
  ["SA-15"] = { Range=11, Blindspot=0, Height=6, Type="Short", Radar="Tor 9A331" },
  ["SA-13"] = { Range=5, Blindspot=0, Height=3, Type="Short", Radar="Strela" },
  ["Avenger"] = { Range=4, Blindspot=0, Height=3, Type="Short", Radar="Avenger" },
  ["Chaparrel"] = { Range=8, Blindspot=0, Height=3, Type="Short", Radar="Chaparral" },
  ["Linebacker"] = { Range=4, Blindspot=0, Height=3, Type="Short", Radar="Linebacker" },
  ["Silkworm"] = { Range=90, Blindspot=1, Height=0.2, Type="Long", Radar="Silkworm" },
  -- units from HDS Mod, multi launcher options is tricky
  ["SA-10B"] = { Range=75, Blindspot=0, Height=18, Type="Medium" , Radar="SA-10B"},
  ["SA-17"] = { Range=50, Blindspot=3, Height=30, Type="Medium", Radar="SA-17" },
  ["SA-20A"] = { Range=150, Blindspot=5, Height=27, Type="Long" , Radar="S-300PMU1"},
  ["SA-20B"] = { Range=200, Blindspot=4, Height=27, Type="Long" , Radar="S-300PMU2"},
  ["HQ-2"] = { Range=50, Blindspot=6, Height=35, Type="Medium", Radar="HQ_2_Guideline_LN" },
}

--- SAM data HDS
-- @type MANTIS.SamDataHDS
-- @field #number Range Max firing range in km
-- @field #number Blindspot no-firing range (green circle)
-- @field #number Height Max firing height in km
-- @field #string Type #MANTIS.SamType of SAM, i.e. SHORT, MEDIUM or LONG (range)
-- @field #string Radar Radar typename on unit level (used as key)
MANTIS.SamDataHDS = {
  -- units from HDS Mod, multi launcher options is tricky
  -- group name MUST contain HDS to ID launcher type correctly!
  ["SA-2 HDS"] = { Range=56, Blindspot=7, Height=30, Type="Medium", Radar="V759" }, 
  ["SA-3 HDS"] = { Range=20, Blindspot=6, Height=30, Type="Short", Radar="V-601P" },  
  ["SA-10C HDS 2"] = { Range=90, Blindspot=5, Height=25, Type="Long" , Radar="5P85DE ln"}, -- V55RUD
  ["SA-10C HDS 1"] = { Range=90, Blindspot=5, Height=25, Type="Long" , Radar="5P85CE ln"}, -- V55RUD
  ["SA-12 HDS 2"] = { Range=100, Blindspot=10, Height=25, Type="Long" , Radar="S-300V 9A82 l"},
  ["SA-12 HDS 1"] = { Range=75, Blindspot=1, Height=25, Type="Long" , Radar="S-300V 9A83 l"},
  ["SA-23 HDS 2"] = { Range=200, Blindspot=5, Height=37, Type="Long", Radar="S-300VM 9A82ME" },
  ["SA-23 HDS 1"] = { Range=100, Blindspot=1, Height=50, Type="Long", Radar="S-300VM 9A83ME" },
  ["HQ-2 HDS"] = { Range=50, Blindspot=6, Height=35, Type="Medium", Radar="HQ_2_Guideline_LN" },
}

-----------------------------------------------------------------------
-- MANTIS System
-----------------------------------------------------------------------

do
  --- Function to instantiate a new object of class MANTIS
  --@param #MANTIS self
  --@param #string name Name of this MANTIS for reporting
  --@param #string samprefix Prefixes for the SAM groups from the ME, e.g. all groups starting with "Red Sam..."
  --@param #string ewrprefix Prefixes for the EWR groups from the ME, e.g. all groups starting with "Red EWR..."
  --@param #string hq Group name of your HQ (optional)
  --@param #string coaltion Coalition side of your setup, e.g. "blue", "red" or "neutral"
  --@param #boolean dynamic Use constant (true) filtering or just filter once (false, default) (optional)
  --@param #string awacs Group name of your Awacs (optional)
  --@param #boolean EmOnOff Make MANTIS switch Emissions on and off instead of changing the alarm state between RED and GREEN (optional)
  --@param #number Padding For #SEAD - Extra number of seconds to add to radar switch-back-on time (optional)
  --@return #MANTIS self
  --@usage Start up your MANTIS with a basic setting
  --
  --        myredmantis = MANTIS:New("myredmantis","Red SAM","Red EWR",nil,"red",false)
  --        myredmantis:Start()
  --
  -- [optional] Use
  --
  --        myredmantis:SetDetectInterval(interval)
  --        myredmantis:SetAutoRelocate(hq, ewr)
  --
  -- before starting #MANTIS to fine-tune your setup.
  --
  -- If you want to use a separate AWACS unit (default detection range: 250km) to support your EWR system, use e.g. the following setup:
  --
  --        mybluemantis = MANTIS:New("bluemantis","Blue SAM","Blue EWR",nil,"blue",false,"Blue Awacs")
  --        mybluemantis:Start()
  --
  function MANTIS:New(name,samprefix,ewrprefix,hq,coaltion,dynamic,awacs, EmOnOff, Padding)

    -- DONE: Create some user functions for these
    -- DONE: Make HQ useful
    -- DONE: Set SAMs to auto if EWR dies
    -- DONE: Refresh SAM table in dynamic mode
    -- DONE: Treat Awacs separately, since they might be >80km off site
    -- DONE: Allow tables of prefixes for the setup
    -- DONE: Auto-Mode with range setups for various known SAM types.

    self.SAM_Templates_Prefix = samprefix or "Red SAM"
    self.EWR_Templates_Prefix = ewrprefix or "Red EWR"
    self.HQ_Template_CC = hq or nil
    self.Coalition = coaltion or "red"
    self.SAM_Table = {}
    self.SAM_Table_Long = {}
    self.SAM_Table_Medium = {}
    self.SAM_Table_Short = {}
    self.dynamic = dynamic or false
    self.checkradius = 25000
    self.grouping = 5000
    self.acceptrange = 80000
    self.detectinterval = 30
    self.engagerange = 95
    self.autorelocate = false
    self.autorelocateunits = { HQ = false, EWR = false}
    self.advanced = false
    self.adv_ratio = 100
    self.adv_state = 0
    self.verbose = false
    self.Adv_EWR_Group = nil
    self.AWACS_Prefix = awacs or nil
    self.awacsrange = 250000      --DONE: 250km, User Function to change
    self.Shorad = nil
    self.ShoradLink = false
    self.ShoradTime = 600
    self.ShoradActDistance = 25000
    self.TimeStamp = timer.getAbsTime()
    self.relointerval = math.random(1800,3600) -- random between 30 and 60 mins
    self.state2flag = false
    self.SamStateTracker = {} -- table to hold alert states, so we don't trigger state changes twice in adv mode
    self.DLink = false
    self.Padding = Padding or 10
    self.SuppressedGroups = {}
    -- 0.8 additions
    self.automode = true
    self.radiusscale = {}
    self.radiusscale[MANTIS.SamType.LONG] = 1.1
    self.radiusscale[MANTIS.SamType.MEDIUM] = 1.2
    self.radiusscale[MANTIS.SamType.SHORT] = 1.3
    --self.SAMCheckRanges = {}
    self.usezones = false
    self.AcceptZones = {}
    self.RejectZones = {}
    self.ConflictZones = {}
    self.maxlongrange = 1
    self.maxmidrange = 2
    self.maxshortrange = 2
    self.maxclassic = 6
    self.autoshorad = true
    self.ShoradGroupSet = SET_GROUP:New() -- Core.Set#SET_GROUP
    
    self.UseEmOnOff = true
    if EmOnOff == false then
      self.UseEmOnOff = false
    end

    if type(awacs) == "string" then
      self.advAwacs = true
    else
      self.advAwacs = false
    end

    -- Inherit everything from BASE class.
    local self = BASE:Inherit(self, FSM:New()) -- #MANTIS

    -- Set the string id for output to DCS.log file.
    self.lid=string.format("MANTIS %s | ", self.name)

    -- Debug trace.
    if self.debug then
      BASE:TraceOnOff(true)
      BASE:TraceClass(self.ClassName)
      --BASE:TraceClass("SEAD")
      BASE:TraceLevel(1)
    end
    
    self.ewr_templates = {}
    if type(samprefix) ~= "table" then
      self.SAM_Templates_Prefix = {samprefix}
    end
    
    if type(ewrprefix) ~= "table" then
      self.EWR_Templates_Prefix = {ewrprefix}
    end
    
    for _,_group in pairs (self.SAM_Templates_Prefix) do
      table.insert(self.ewr_templates,_group)
    end
    
    for _,_group in pairs (self.EWR_Templates_Prefix) do
      table.insert(self.ewr_templates,_group)
    end
    
    if self.advAwacs then
      table.insert(self.ewr_templates,awacs)
    end
    
    self:T({self.ewr_templates})
    
    if self.dynamic then
      -- Set SAM SET_GROUP
      self.SAM_Group = SET_GROUP:New():FilterPrefixes(self.SAM_Templates_Prefix):FilterCoalitions(self.Coalition):FilterStart()
      -- Set EWR SET_GROUP
      self.EWR_Group = SET_GROUP:New():FilterPrefixes(self.ewr_templates):FilterCoalitions(self.Coalition):FilterStart()
    else
      -- Set SAM SET_GROUP
      self.SAM_Group = SET_GROUP:New():FilterPrefixes(self.SAM_Templates_Prefix):FilterCoalitions(self.Coalition):FilterOnce()
      -- Set EWR SET_GROUP
      self.EWR_Group = SET_GROUP:New():FilterPrefixes(self.ewr_templates):FilterCoalitions(self.Coalition):FilterOnce()
    end

    -- set up CC
    if self.HQ_Template_CC then
      self.HQ_CC = GROUP:FindByName(self.HQ_Template_CC)
    end
    
    -- TODO Version
    -- @field #string version
    self.version="0.8.8"
    self:I(string.format("***** Starting MANTIS Version %s *****", self.version))

    --- FSM Functions ---

      -- Start State.
  self:SetStartState("Stopped")

  -- Add FSM transitions.
  --                 From State  -->   Event            -->     To State
  self:AddTransition("Stopped",       "Start",                   "Running")     -- Start FSM.
  self:AddTransition("*",             "Status",                  "*")           -- MANTIS status update.
  self:AddTransition("*",             "Relocating",              "*")           -- MANTIS HQ and EWR are relocating.
  self:AddTransition("*",             "GreenState",              "*")           -- MANTIS A SAM switching to GREEN state.
  self:AddTransition("*",             "RedState",                "*")           -- MANTIS A SAM switching to RED state.
  self:AddTransition("*",             "AdvStateChange",          "*")           -- MANTIS advanced mode state change.
  self:AddTransition("*",             "ShoradActivated",         "*")           -- MANTIS woke up a connected SHORAD.
  self:AddTransition("*",             "SeadSuppressionStart",    "*")           -- SEAD has switched off one group.
  self:AddTransition("*",             "SeadSuppressionEnd",      "*")           -- SEAD has switched on one group.
  self:AddTransition("*",             "SeadSuppressionPlanned",  "*")           -- SEAD has planned a suppression.
  self:AddTransition("*",             "Stop",                    "Stopped")     -- Stop FSM.

  ------------------------
  --- Pseudo Functions ---
  ------------------------

  --- Triggers the FSM event "Start". Starts the MANTIS. Initializes parameters and starts event handlers.
  -- @function [parent=#MANTIS] Start
  -- @param #MANTIS self

  --- Triggers the FSM event "Start" after a delay. Starts the MANTIS. Initializes parameters and starts event handlers.
  -- @function [parent=#MANTIS] __Start
  -- @param #MANTIS self
  -- @param #number delay Delay in seconds.

  --- Triggers the FSM event "Stop". Stops the MANTIS and all its event handlers.
  -- @param #MANTIS self

  --- Triggers the FSM event "Stop" after a delay. Stops the MANTIS and all its event handlers.
  -- @function [parent=#MANTIS] __Stop
  -- @param #MANTIS self
  -- @param #number delay Delay in seconds.

  --- Triggers the FSM event "Status".
  -- @function [parent=#MANTIS] Status
  -- @param #MANTIS self

  --- Triggers the FSM event "Status" after a delay.
  -- @function [parent=#MANTIS] __Status
  -- @param #MANTIS self
  -- @param #number delay Delay in seconds.

  --- On After "Relocating" event. HQ and/or EWR moved.
  -- @function [parent=#MANTIS] OnAfterRelocating
  -- @param #MANTIS self
  -- @param #string From The From State
  -- @param #string Event The Event
  -- @param #string To The To State
  -- @return #MANTIS self

  --- On After "GreenState" event. A SAM group was switched to GREEN alert.
  -- @function [parent=#MANTIS] OnAfterGreenState
  -- @param #MANTIS self
  -- @param #string From The From State
  -- @param #string Event The Event
  -- @param #string To The To State
  -- @param Wrapper.Group#GROUP Group The GROUP object whose state was changed
  -- @return #MANTIS self

  --- On After "RedState" event. A SAM group was switched to RED alert.
  -- @function [parent=#MANTIS] OnAfterRedState
  -- @param #MANTIS self
  -- @param #string From The From State
  -- @param #string Event The Event
  -- @param #string To The To State
  -- @param Wrapper.Group#GROUP Group The GROUP object whose state was changed
  -- @return #MANTIS self

  --- On After "AdvStateChange" event. Advanced state changed, influencing detection speed.
  -- @function [parent=#MANTIS] OnAfterAdvStateChange
  -- @param #MANTIS self
  -- @param #string From The From State
  -- @param #string Event The Event
  -- @param #string To The To State
  -- @param #number Oldstate Old state - 0 = green, 1 = amber, 2 = red
  -- @param #number Newstate New state - 0 = green, 1 = amber, 2 = red
  -- @param #number Interval Calculated detection interval based on state and advanced feature setting
  -- @return #MANTIS self

  --- On After "ShoradActivated" event. Mantis has activated a SHORAD.
  -- @function [parent=#MANTIS] OnAfterShoradActivated
  -- @param #MANTIS self
  -- @param #string From The From State
  -- @param #string Event The Event
  -- @param #string To The To State
  -- @param #string Name Name of the GROUP which SHORAD shall protect
  -- @param #number Radius Radius around the named group to find SHORAD groups
  -- @param #number Ontime Seconds the SHORAD will stay active

  --- On After "SeadSuppressionPlanned" event. Mantis has planned to switch off a site to defend SEAD attack.
  -- @function [parent=#MANTIS] OnAfterSeadSuppressionPlanned
  -- @param #MANTIS self
  -- @param #string From The From State
  -- @param #string Event The Event
  -- @param #string To The To State
  -- @param Wrapper.Group#GROUP Group The suppressed GROUP object
  -- @param #string Name Name of the suppressed group
  -- @param #number SuppressionStartTime Model start time of the suppression from `timer.getTime()`
  -- @param #number SuppressionEndTime Model end time of the suppression from `timer.getTime()`
  -- @param Wrapper.Group#GROUP Attacker The attacking GROUP object

  --- On After "SeadSuppressionStart" event. Mantis has switched off a site to defend a SEAD attack.
  -- @function [parent=#MANTIS] OnAfterSeadSuppressionStart
  -- @param #MANTIS self
  -- @param #string From The From State
  -- @param #string Event The Event
  -- @param #string To The To State
  -- @param Wrapper.Group#GROUP Group The suppressed GROUP object
  -- @param #string Name Name of the suppressed group
  -- @param Wrapper.Group#GROUP Attacker The attacking GROUP object

  --- On After "SeadSuppressionEnd" event. Mantis has switched on a site after a SEAD attack.
  -- @function [parent=#MANTIS] OnAfterSeadSuppressionEnd
  -- @param #MANTIS self
  -- @param #string From The From State
  -- @param #string Event The Event
  -- @param #string To The To State
  -- @param Wrapper.Group#GROUP Group The suppressed GROUP object
  -- @param #string Name Name of the suppressed group
  
  return self
 end

-----------------------------------------------------------------------
-- MANTIS helper functions
-----------------------------------------------------------------------

  --- [Internal] Function to get the self.SAM_Table
  -- @param #MANTIS self
  -- @return #table table
  function MANTIS:_GetSAMTable()
    self:T(self.lid .. "GetSAMTable")
    return self.SAM_Table
  end

  --- [Internal] Function to set the self.SAM_Table
  -- @param #MANTIS self
  -- @return #MANTIS self
  function MANTIS:_SetSAMTable(table)
    self:T(self.lid .. "SetSAMTable")
    self.SAM_Table = table
    return self
  end

  --- Function to set the grouping radius of the detection in meters
  -- @param #MANTIS self
  -- @param #number radius Radius upon which detected objects will be grouped
  function MANTIS:SetEWRGrouping(radius)
    self:T(self.lid .. "SetEWRGrouping")
    local radius = radius or 5000
    self.grouping = radius
    return self
  end
  
  --- Function to set accept and reject zones.
  -- @param #MANTIS self
  -- @param #table AcceptZones Table of @{Core.Zone#ZONE} objects
  -- @param #table RejectZones Table of @{Core.Zone#ZONE} objects
  -- @param #table ConflictZones Table of @{Core.Zone#ZONE} objects
  -- @return #MANTIS self
  -- @usage
  -- Parameters are **tables of Core.Zone#ZONE** objects!   
  -- This is effectively a 3-stage filter allowing for zone overlap. A coordinate is accepted first when   
  -- it is inside any AcceptZone. Then RejectZones are checked, which enforces both borders, but also overlaps of   
  -- Accept- and RejectZones. Last, if it is inside a conflict zone, it is accepted.   
  function MANTIS:AddZones(AcceptZones,RejectZones, ConflictZones)
    self:T(self.lid .. "AddZones")
    self.AcceptZones = AcceptZones or {}
    self.RejectZones = RejectZones or {}
    self.ConflictZones = ConflictZones or {}
    if #AcceptZones > 0 or #RejectZones > 0 or #ConflictZones > 0 then
      self.usezones = true
    end
    return self
  end
  
  --- Function to set the detection radius of the EWR in meters. (Deprecated, SAM range is used)
  -- @param #MANTIS self
  -- @param #number radius Radius of the EWR detection zone
  function MANTIS:SetEWRRange(radius)
    self:T(self.lid .. "SetEWRRange")
    --local radius = radius or 80000
    -- self.acceptrange = radius
    return self
  end

  --- Function to set switch-on/off zone for the SAM sites in meters. Overwritten per SAM in automode.
  -- @param #MANTIS self
  -- @param #number radius Radius of the firing zone in classic mode
  function MANTIS:SetSAMRadius(radius)
    self:T(self.lid .. "SetSAMRadius")
    local radius = radius or 25000
    self.checkradius = radius
    return self
  end

  --- Function to set SAM firing engage range, 0-100 percent, e.g. 85
  -- @param #MANTIS self
  -- @param #number range Percent of the max fire range
  function MANTIS:SetSAMRange(range)
    self:T(self.lid .. "SetSAMRange")
    local range = range or 95
    if range < 0 or range > 100 then
      range = 95
    end
    self.engagerange = range
    return self
  end
  
    --- Function to set number of SAMs going active on a valid, detected thread
    -- @param #MANTIS self
    -- @param #number Short Number of short-range systems activated, defaults to 1.
    -- @param #number Mid Number of mid-range systems activated, defaults to 2.
    -- @param #number Long Number of long-range systems activated, defaults to 2.
    -- @param #number Classic (non-automode) Number of overall systems activated, defaults to 6.
    -- @return #MANTIS self
  function MANTIS:SetMaxActiveSAMs(Short,Mid,Long,Classic)
    self:T(self.lid .. "SetMaxActiveSAMs")
    self.maxclassic = Classic or 6
    self.maxlongrange = Long or 1
    self.maxmidrange = Mid or 2
    self.maxshortrange = Short or 2
    return self
  end

  --- Function to set a new SAM firing engage range, use this method to adjust range while running MANTIS, e.g. for different setups day and night
  -- @param #MANTIS self
  -- @param #number range Percent of the max fire range
  function MANTIS:SetNewSAMRangeWhileRunning(range)
    self:T(self.lid .. "SetNewSAMRangeWhileRunning")
    local range = range or 95
    if range < 0 or range > 100 then
      range = 95
    end
    self.engagerange = range
    self:_RefreshSAMTable()
    self.mysead.EngagementRange = range
    return self
  end

  --- Function to set switch-on/off the debug state
  -- @param #MANTIS self
  -- @param #boolean onoff Set true to switch on
  function MANTIS:Debug(onoff)
    self:T(self.lid .. "SetDebug")
    local onoff = onoff or false
    self.debug = onoff
    if onoff then
      -- Debug trace.
      BASE:TraceOn()
      BASE:TraceClass("MANTIS")
      BASE:TraceLevel(1)
    else
      BASE:TraceOff()
    end
    return self
  end

  --- Function to get the HQ object for further use
  -- @param #MANTIS self
  -- @return Wrapper.GROUP#GROUP The HQ #GROUP object or *nil* if it doesn't exist
  function MANTIS:GetCommandCenter()
    self:T(self.lid .. "GetCommandCenter")
    if self.HQ_CC then
      return self.HQ_CC
    else
      return nil
    end
  end

  --- Function to set separate AWACS detection instance
  -- @param #MANTIS self
  -- @param #string prefix Name of the AWACS group in the mission editor
  function MANTIS:SetAwacs(prefix)
    self:T(self.lid .. "SetAwacs")
    if prefix ~= nil then
      if type(prefix) == "string" then
        self.AWACS_Prefix = prefix
        self.advAwacs = true
      end
    end
    return self
  end

  --- Function to set AWACS detection range. Defaults to 250.000m (250km) - use **before** starting your Mantis!
  -- @param #MANTIS self
  -- @param #number range Detection range of the AWACS group
  function MANTIS:SetAwacsRange(range)
    self:T(self.lid .. "SetAwacsRange")
    local range = range or 250000
    self.awacsrange = range
    return self
  end

  --- Function to set the HQ object for further use
  -- @param #MANTIS self
  -- @param Wrapper.GROUP#GROUP group The #GROUP object to be set as HQ
  function MANTIS:SetCommandCenter(group)
    self:T(self.lid .. "SetCommandCenter")
    local group = group or nil
    if group ~= nil then
      if type(group) == "string" then
        self.HQ_CC = GROUP:FindByName(group)
        self.HQ_Template_CC = group
      else
        self.HQ_CC = group
        self.HQ_Template_CC = group:GetName()
      end
    end
    return self
  end

  --- Function to set the detection interval
  -- @param #MANTIS self
  -- @param #number interval The interval in seconds
  function MANTIS:SetDetectInterval(interval)
    self:T(self.lid .. "SetDetectInterval")
    local interval = interval or 30
    self.detectinterval = interval
    return self
  end

  --- Function to set Advanded Mode
  -- @param #MANTIS self
  -- @param #boolean onoff If true, will activate Advanced Mode
  -- @param #number ratio [optional] Percentage to use for advanced mode, defaults to 100%
  -- @usage Advanced mode will *decrease* reactivity of MANTIS, if HQ and/or EWR network dies.  Set SAMs to RED state if both are dead.  Requires usage of an **HQ** object and the **dynamic** option.
  -- E.g. `mymantis:SetAdvancedMode(true, 90)`
  function MANTIS:SetAdvancedMode(onoff, ratio)
    self:T(self.lid .. "SetAdvancedMode")
    --self:T({onoff, ratio})
    local onoff = onoff or false
    local ratio = ratio or 100
    if (type(self.HQ_Template_CC) == "string") and onoff and self.dynamic then
      self.adv_ratio = ratio
      self.advanced = true
      self.adv_state = 0
      self.Adv_EWR_Group = SET_GROUP:New():FilterPrefixes(self.EWR_Templates_Prefix):FilterCoalitions(self.Coalition):FilterStart()
      self:I(string.format("***** Starting Advanced Mode MANTIS Version %s *****", self.version))
    else
      local text = self.lid.." Advanced Mode requires a HQ and dynamic to be set. Revisit your MANTIS:New() statement to add both."
      local m= MESSAGE:New(text,10,"MANTIS",true):ToAll()
      self:E(text)
    end
    return self
  end

  --- Set using Emissions on/off instead of changing alarm state
  -- @param #MANTIS self
  -- @param #boolean switch Decide if we are changing alarm state or Emission state
  function MANTIS:SetUsingEmOnOff(switch)
    self:T(self.lid .. "SetUsingEmOnOff")
    self.UseEmOnOff = switch or false
    return self
  end

  --- Set using your own #INTEL_DLINK object instead of #DETECTION
  -- @param #MANTIS self
  -- @param Ops.Intelligence#INTEL_DLINK DLink The data link object to be used.
  function MANTIS:SetUsingDLink(DLink)
    self:T(self.lid .. "SetUsingDLink")
    self.DLink = true
    self.Detection = DLink
    self.DLTimeStamp = timer.getAbsTime()
    return self
  end

  --- [Internal] Function to check if HQ is alive
  -- @param #MANTIS self
  -- @return #boolean True if HQ is alive, else false
  function MANTIS:_CheckHQState()
    self:T(self.lid .. "CheckHQState")
    local text = self.lid.." Checking HQ State"
    local m= MESSAGE:New(text,10,"MANTIS"):ToAllIf(self.debug)
    if self.verbose then self:I(text) end
    -- start check
    if self.advanced then
      local hq = self.HQ_Template_CC
      local hqgrp = GROUP:FindByName(hq)
      if hqgrp then
        if hqgrp:IsAlive() then -- ok we're on, hq exists and as alive
          --self:T(self.lid.." HQ is alive!")
          return true
        else
          --self:T(self.lid.." HQ is dead!")
          return false
        end
      end
    end
    return self
  end

  --- [Internal] Function to check if EWR is (at least partially) alive
  -- @param #MANTIS self
  -- @return #boolean True if EWR is alive, else false
  function MANTIS:_CheckEWRState()
    self:T(self.lid .. "CheckEWRState")
    local text = self.lid.." Checking EWR State"
    --self:T(text)
    local m= MESSAGE:New(text,10,"MANTIS"):ToAllIf(self.debug)
    if self.verbose then self:I(text) end
    -- start check
    if self.advanced then
      local EWR_Group = self.Adv_EWR_Group
      --local EWR_Set = EWR_Group.Set
      local nalive = EWR_Group:CountAlive()
      if self.advAwacs then
        local awacs = GROUP:FindByName(self.AWACS_Prefix)
        if awacs ~= nil then
          if awacs:IsAlive() then
            nalive = nalive+1
          end
        end
      end
      --self:T(self.lid..string.format(" No of EWR alive is %d", nalive))
      if nalive > 0 then
        return true
      else
        return false
      end
    end
    return self
  end

  --- [Internal] Function to determine state of the advanced mode
  -- @param #MANTIS self
  -- @return #number Newly calculated interval
  -- @return #number Previous state for tracking 0, 1, or 2
  function MANTIS:_CalcAdvState()
    self:T(self.lid .. "CalcAdvState")
    local m=MESSAGE:New(self.lid.." Calculating Advanced State",10,"MANTIS"):ToAllIf(self.debug)
    if self.verbose then self:I(self.lid.." Calculating Advanced State") end
    -- start check
    local currstate = self.adv_state -- save curr state for comparison later
    local EWR_State = self:_CheckEWRState()
    local HQ_State = self:_CheckHQState()
    -- set state
    if EWR_State and HQ_State then -- both alive
      self.adv_state = 0 --everything is fine
    elseif EWR_State or HQ_State then -- one alive
      self.adv_state = 1 --slow down level 1
    else -- none alive
      self.adv_state = 2 --slow down level 2
    end
    -- calculate new detectioninterval
    local interval = self.detectinterval -- e.g. 30
    local ratio = self.adv_ratio / 100 -- e.g. 80/100 = 0.8
    ratio = ratio * self.adv_state -- e.g 0.8*2 = 1.6
    local newinterval = interval + (interval * ratio) -- e.g. 30+(30*1.6) = 78
    if self.debug or self.verbose then
      local text = self.lid..string.format(" Calculated OldState/NewState/Interval: %d / %d / %d", currstate, self.adv_state, newinterval)
      --self:T(text)
      local m=MESSAGE:New(text,10,"MANTIS"):ToAllIf(self.debug)
      if self.verbose then self:I(text) end
    end
    return newinterval, currstate
  end

  --- Function to set autorelocation for HQ and EWR objects. Note: Units must be actually mobile in DCS!
  -- @param #MANTIS self
  -- @param #boolean hq If true, will relocate HQ object
  -- @param #boolean ewr If true, will relocate  EWR objects
  function MANTIS:SetAutoRelocate(hq, ewr)
    self:T(self.lid .. "SetAutoRelocate")
    --self:T({hq, ewr})
    local hqrel = hq or false
    local ewrel = ewr or false
    if hqrel or ewrel then
      self.autorelocate = true
      self.autorelocateunits = { HQ = hqrel, EWR = ewrel }
      --self:T({self.autorelocate, self.autorelocateunits})
    end
    return self
  end

  --- [Internal] Function to execute the relocation
  -- @param #MANTIS self
  -- @return #MANTIS self 
  function MANTIS:_RelocateGroups()
    self:T(self.lid .. "RelocateGroups")
    local text = self.lid.." Relocating Groups"
    local m= MESSAGE:New(text,10,"MANTIS",true):ToAllIf(self.debug)
    if self.verbose then self:I(text) end
    if self.autorelocate then
      -- relocate HQ
      local HQGroup = self.HQ_CC
      if self.autorelocateunits.HQ and self.HQ_CC and HQGroup:IsAlive() then --only relocate if HQ exists
        local _hqgrp = self.HQ_CC
        --self:T(self.lid.." Relocating HQ")
        local text = self.lid.." Relocating HQ"
        --local m= MESSAGE:New(text,10,"MANTIS"):ToAll()
          _hqgrp:RelocateGroundRandomInRadius(20,500,true,true)
      end
      --relocate EWR
      -- TODO: maybe dependent on AlarmState? Observed: SA11 SR only relocates if no objects in reach
      if self.autorelocateunits.EWR then
         -- get EWR Group
         local EWR_GRP = SET_GROUP:New():FilterPrefixes(self.EWR_Templates_Prefix):FilterCoalitions(self.Coalition):FilterOnce()
         local EWR_Grps = EWR_GRP.Set --table of objects in SET_GROUP
         for _,_grp in pairs (EWR_Grps) do
             if _grp:IsAlive() and _grp:IsGround() then
              --self:T(self.lid.." Relocating EWR ".._grp:GetName())
              local text = self.lid.." Relocating EWR ".._grp:GetName()
              local m= MESSAGE:New(text,10,"MANTIS"):ToAllIf(self.debug)
              if self.verbose then self:I(text) end
              _grp:RelocateGroundRandomInRadius(20,500,true,true)
             end
         end
      end
    end
    return self
  end
  
  --- [Internal] Function to check accept and reject zones
  -- @param #MANTIS self
  -- @param Core.Point#COORDINATE coord The coordinate to check
  -- @return #boolean outcome
  function MANTIS:_CheckCoordinateInZones(coord)
    -- DEBUG
    self:T(self.lid.."_CheckCoordinateInZones")
    local inzone = false
    -- acceptzones
    if #self.AcceptZones > 0 then
      for _,_zone in pairs(self.AcceptZones) do
        local zone = _zone -- Core.Zone#ZONE
        if zone:IsCoordinateInZone(coord) then
          inzone = true
          self:T(self.lid.."Target coord in Accept Zone!")
          break
        end
      end
    end
    -- rejectzones
    if #self.RejectZones > 0 and inzone then -- maybe in accept zone, but check the overlaps
      for _,_zone in pairs(self.RejectZones) do
        local zone = _zone -- Core.Zone#ZONE
        if zone:IsCoordinateInZone(coord) then
          inzone = false
          self:T(self.lid.."Target coord in Reject Zone!")
          break
        end
      end
    end
    -- conflictzones
    if #self.ConflictZones > 0 and not inzone then -- if not already accepted, might be in conflict zones
      for _,_zone in pairs(self.ConflictZones) do
        local zone = _zone -- Core.Zone#ZONE
        if zone:IsCoordinateInZone(coord) then
          inzone = true
          self:T(self.lid.."Target coord in Conflict Zone!")
          break
        end
      end
    end   
    return inzone
  end
  
  --- [Internal] Function to prefilter height based
  -- @param #MANTIS self
  -- @param #number height
  -- @return #table set
  function MANTIS:_PreFilterHeight(height)
    self:T(self.lid.."_PreFilterHeight")   
    local set = {}
    local dlink = self.Detection -- Ops.Intelligence#INTEL_DLINK
    local detectedgroups = dlink:GetContactTable()
    for _,_contact in pairs(detectedgroups) do
      local contact = _contact -- Ops.Intelligence#INTEL.Contact
      local grp = contact.group -- Wrapper.Group#GROUP
      if grp:IsAlive() then
        if grp:GetHeight(true) < height then
          local coord = grp:GetCoordinate()
          table.insert(set,coord)
        end
      end
    end
    return set
  end
  
  --- [Internal] Function to check if any object is in the given SAM zone
  -- @param #MANTIS self
  -- @param #table dectset Table of coordinates of detected items
  -- @param Core.Point#COORDINATE samcoordinate Coordinate object.
  -- @param #number radius Radius to check.
  -- @param #number height Height to check.
  -- @param #boolean dlink Data from DLINK.
  -- @return #boolean True if in any zone, else false
  -- @return #number Distance Target distance in meters or zero when no object is in zone
  function MANTIS:_CheckObjectInZone(dectset, samcoordinate, radius, height, dlink)
    self:T(self.lid.."_CheckObjectInZone")
    -- check if non of the coordinate is in the given defense zone
    local rad = radius or self.checkradius
    local set = dectset
    if dlink then
      -- DEBUG
      set = self:_PreFilterHeight(height)
    end
    for _,_coord in pairs (set) do
      local coord = _coord  -- get current coord to check
      -- output for cross-check
      local targetdistance = samcoordinate:DistanceFromPointVec2(coord)
      if not targetdistance then
        targetdistance = samcoordinate:Get2DDistance(coord)
      end
      -- check accept/reject zones
      local zonecheck = true
      if self.usezones then
        -- DONE
        zonecheck = self:_CheckCoordinateInZones(coord)
      end
      if self.verbose and self.debug then
        local dectstring = coord:ToStringLLDMS()
        local samstring = samcoordinate:ToStringLLDMS()
        local inrange = "false"
        if targetdistance <= rad then
          inrange = "true"
        end
        local text = string.format("Checking SAM at %s | Targetdist %d | Rad %d | Inrange %s", samstring, targetdistance, rad, inrange)
        local m = MESSAGE:New(text,10,"Check"):ToAllIf(self.debug)
        self:T(self.lid..text)
      end
      -- end output to cross-check
      if targetdistance <= rad and zonecheck then
        return true, targetdistance
      end
    end
    return false, 0
  end

  --- [Internal] Function to start the detection via EWR groups - if INTEL isn\'t available
  -- @param #MANTIS self
  -- @return Functional.Detection #DETECTION_AREAS The running detection set
  function MANTIS:StartDetection()
    self:T(self.lid.."Starting Detection")

    -- start detection
    local groupset = self.EWR_Group
    local grouping = self.grouping or 5000
    --local acceptrange = self.acceptrange or 80000
    local interval = self.detectinterval or 20
    
    local MANTISdetection = DETECTION_AREAS:New( groupset, grouping ) --[Internal] Grouping detected objects to 5000m zones
    MANTISdetection:FilterCategories({ Unit.Category.AIRPLANE, Unit.Category.HELICOPTER })
    --MANTISdetection:SetAcceptRange(acceptrange) -- deprecated - in range of SAMs is used anyway
    MANTISdetection:SetRefreshTimeInterval(interval)
    MANTISdetection:__Start(2)

    return MANTISdetection
  end
  
  --- [Internal] Function to start the detection with INTEL via EWR groups
  -- @param #MANTIS self
  -- @return Ops.Intel#INTEL_DLINK The running detection set
  function MANTIS:StartIntelDetection()
    self:T(self.lid.."Starting Intel Detection")
    -- DEBUG
    -- start detection
    local groupset = self.EWR_Group
    local samset = self.SAM_Group
    
    self.intelset = {}
    
    local IntelOne = INTEL:New(groupset,self.Coalition,self.name.." IntelOne")
    --IntelOne:SetClusterAnalysis(true,true)
    --IntelOne:SetClusterRadius(5000)
    IntelOne:Start()
    
    local IntelTwo = INTEL:New(samset,self.Coalition,self.name.." IntelTwo")
    --IntelTwo:SetClusterAnalysis(true,true)
    --IntelTwo:SetClusterRadius(5000)
    IntelTwo:Start()
    
    local IntelDlink = INTEL_DLINK:New({IntelOne,IntelTwo},self.name.." DLINK",22,300)
    IntelDlink:__Start(1)
    
    self:SetUsingDLink(IntelDlink)
    
    table.insert(self.intelset, IntelOne)
    table.insert(self.intelset, IntelTwo)
    
    return IntelDlink
  end
  
  --- [Internal] Function to start the detection via AWACS if defined as separate (classic)
  -- @param #MANTIS self
  -- @return Functional.Detection #DETECTION_AREAS The running detection set
  function MANTIS:StartAwacsDetection()
    self:T(self.lid.."Starting Awacs Detection")

    -- start detection
    local group = self.AWACS_Prefix
    local groupset = SET_GROUP:New():FilterPrefixes(group):FilterCoalitions(self.Coalition):FilterStart()
    local grouping = self.grouping or 5000
    --local acceptrange = self.acceptrange or 80000
    local interval = self.detectinterval or 60

    --@param Functional.Detection #DETECTION_AREAS _MANTISdetection [Internal] The MANTIS detection object
    local MANTISAwacs = DETECTION_AREAS:New( groupset, grouping ) --[Internal] Grouping detected objects to 5000m zones
    MANTISAwacs:FilterCategories({ Unit.Category.AIRPLANE, Unit.Category.HELICOPTER })
    MANTISAwacs:SetAcceptRange(self.awacsrange)  --250km
    MANTISAwacs:SetRefreshTimeInterval(interval)
    MANTISAwacs:Start()

    return MANTISAwacs
  end
  
   --- [Internal] Function to get SAM firing data from units types.
  -- @param #MANTIS self
  -- @param #string grpname Name of the group
  -- @param #boolean mod HDS mod flag
  -- @return #number range Max firing range
  -- @return #number height Max firing height
  -- @return #string type Long, medium or short range
  -- @return #number blind "blind" spot
  function MANTIS:_GetSAMDataFromUnits(grpname,mod)
    self:T(self.lid.."_GetSAMRangeFromUnits")
    local found = false
    local range = self.checkradius
    local height = 3000
    local type = MANTIS.SamType.MEDIUM
    local radiusscale = self.radiusscale[type]
    local blind = 0
    local group = GROUP:FindByName(grpname) -- Wrapper.Group#GROUP
    local units = group:GetUnits()
    local SAMData = self.SamData
    if mod then
      SAMData = self.SamDataHDS
    end
    --self:I("Looking to auto-match for "..grpname)
    for _,_unit in pairs(units) do
      local unit = _unit -- Wrapper.Unit#UNIT
      local type = string.lower(unit:GetTypeName())
      --self:I(string.format("Matching typename: %s",type))
      for idx,entry in pairs(SAMData) do
        local _entry = entry -- #MANTIS.SamData
        local _radar = string.lower(_entry.Radar)
        --self:I(string.format("Trying typename: %s",_radar))
        if string.find(type,_radar,1,true) then
          type = _entry.Type
          radiusscale = self.radiusscale[type]
          range = _entry.Range * 1000 * radiusscale -- max firing range used as switch-on
          height = _entry.Height * 1000 -- max firing height
          blind = _entry.Blindspot * 100 -- blind spot range 
          --self:I(string.format("Match: %s - %s",_radar,type))
          found = true
          break
        end
      end
      if found then break end
    end
    if not found then
      self:E(self.lid .. string.format("*****Could not match radar data for %s! Will default to midrange values!",grpname))
    end
    return range, height, type, blind
  end
  
  --- [Internal] Function to get SAM firing data
  -- @param #MANTIS self
  -- @param #string grpname Name of the group
  -- @return #number range Max firing range
  -- @return #number height Max firing height
  -- @return #string type Long, medium or short range
  -- @return #number blind "blind" spot
  function MANTIS:_GetSAMRange(grpname)
    self:T(self.lid.."_GetSAMRange")
    local range = self.checkradius
    local height = 3000
    local type = MANTIS.SamType.MEDIUM
    local radiusscale = self.radiusscale[type]
    local blind = 0
    local found = false
    local HDSmod = false
    if string.find(grpname,"HDS",1,true) then
      HDSmod = true
    end
    if self.automode then
      for idx,entry in pairs(self.SamData) do
        --self:I("ID = " .. idx)
        if string.find(grpname,idx,1,true) then
          local _entry = entry -- #MANTIS.SamData
          type = _entry.Type
          radiusscale = self.radiusscale[type]
          range = _entry.Range * 1000 * radiusscale -- max firing range
          height = _entry.Height * 1000 -- max firing height        
          blind = _entry.Blindspot 
          --self:I("Matching Groupname = " .. grpname .. " Range= " .. range)
          found = true
          break
        end
      end
    end
    -- secondary filter if not found
    if (not found and self.automode) or HDSmod then
      range, height, type = self:_GetSAMDataFromUnits(grpname,HDSmod)
    elseif not found then
      self:E(self.lid .. string.format("*****Could not match radar data for %s! Will default to midrange values!",grpname))
    end
    return range, height, type, blind
  end
  
  --- [Internal] Function to set the SAM start state
  -- @param #MANTIS self
  -- @return #MANTIS self
  function MANTIS:SetSAMStartState()
    -- DONE: if using dynamic filtering, update SAM_Table and the (active) SEAD groups, pull req #1405/#1406
    -- DONE: Auto mode
    self:T(self.lid.."Setting SAM Start States")
     -- get SAM Group
     local SAM_SET = self.SAM_Group
     local SAM_Grps = SAM_SET.Set --table of objects
     local SAM_Tbl = {} -- table of SAM defense zones
     local SAM_Tbl_lg = {} -- table of long range SAM defense zones
     local SAM_Tbl_md = {} -- table of mid range SAM defense zones
     local SAM_Tbl_sh = {} -- table of short range SAM defense zones
     local SEAD_Grps = {} -- table of SAM names to make evasive
     local engagerange = self.engagerange -- firing range in % of max
     --cycle through groups and set alarm state etc
     for _i,_group in pairs (SAM_Grps) do
      if _group:IsGround() and _group:IsAlive() then
        local group = _group -- Wrapper.Group#GROUP
        -- DONE: add emissions on/off
        if self.UseEmOnOff then
          group:OptionAlarmStateRed()
          group:EnableEmission(false)
          --group:SetAIOff()
        else
          group:OptionAlarmStateGreen() -- AI off
        end
        group:OptionEngageRange(engagerange)  --default engagement will be 95% of firing range
        local grpname = group:GetName()
        local grpcoord = group:GetCoordinate()
        local grprange,grpheight,type,blind  = self:_GetSAMRange(grpname)
        table.insert( SAM_Tbl, {grpname, grpcoord, grprange, grpheight, blind})
        --table.insert( SEAD_Grps, grpname )
        if type == MANTIS.SamType.LONG then
          table.insert( SAM_Tbl_lg, {grpname, grpcoord, grprange, grpheight, blind})
          table.insert( SEAD_Grps, grpname )
          --self:T("SAM "..grpname.." is type LONG")
        elseif type == MANTIS.SamType.MEDIUM then
         table.insert( SAM_Tbl_md, {grpname, grpcoord, grprange, grpheight, blind})
         table.insert( SEAD_Grps, grpname )
         --self:T("SAM "..grpname.." is type MEDIUM")
        elseif type == MANTIS.SamType.SHORT then
          table.insert( SAM_Tbl_sh, {grpname, grpcoord, grprange, grpheight, blind})
          --self:T("SAM "..grpname.." is type SHORT")
          self.ShoradGroupSet:Add(grpname,group)
          if not self.autoshorad then
            table.insert( SEAD_Grps, grpname )
          end
        end
        self.SamStateTracker[grpname] = "GREEN"
        end
     end
     self.SAM_Table = SAM_Tbl
     self.SAM_Table_Long = SAM_Tbl_lg
     self.SAM_Table_Medium = SAM_Tbl_md
     self.SAM_Table_Short = SAM_Tbl_sh
     -- make SAMs evasive
     local mysead = SEAD:New( SEAD_Grps, self.Padding ) -- Functional.Sead#SEAD
     mysead:SetEngagementRange(engagerange)
     mysead:AddCallBack(self)
     if self.UseEmOnOff then
      mysead:SwitchEmissions(true)
     end
     self.mysead = mysead
     return self
  end

  --- [Internal] Function to update SAM table and SEAD state
  -- @param #MANTIS self
  -- @return #MANTIS self
  function MANTIS:_RefreshSAMTable()
    self:T(self.lid.."RefreshSAMTable")
    -- Requires SEAD 0.2.2 or better
     -- get SAM Group
     local SAM_SET = self.SAM_Group
     local SAM_Grps = SAM_SET.Set --table of objects
     local SAM_Tbl = {} -- table of SAM defense zones
     local SAM_Tbl_lg = {} -- table of long range SAM defense zones
     local SAM_Tbl_md = {} -- table of mid range SAM defense zones
     local SAM_Tbl_sh = {} -- table of short range SAM defense zon
     local SEAD_Grps = {} -- table of SAM names to make evasive
     local engagerange = self.engagerange -- firing range in % of max
     --cycle through groups and set alarm state etc
     for _i,_group in pairs (SAM_Grps) do
        local group = _group -- Wrapper.Group#GROUP
        group:OptionEngageRange(engagerange)  --engagement will be 95% of firing range
        if group:IsGround() and group:IsAlive() then
          local grpname = group:GetName()
          local grpcoord = group:GetCoordinate()
          local grprange, grpheight,type,blind  = self:_GetSAMRange(grpname)
          table.insert( SAM_Tbl, {grpname, grpcoord, grprange, grpheight, blind}) -- make the table lighter, as I don't really use the zone here
          table.insert( SEAD_Grps, grpname )
          if type == MANTIS.SamType.LONG then
            table.insert( SAM_Tbl_lg, {grpname, grpcoord, grprange, grpheight, blind})
            --self:I({grpname,grprange, grpheight})
          elseif type == MANTIS.SamType.MEDIUM then
           table.insert( SAM_Tbl_md, {grpname, grpcoord, grprange, grpheight, blind})
           --self:I({grpname,grprange, grpheight})
          elseif type == MANTIS.SamType.SHORT then
            table.insert( SAM_Tbl_sh, {grpname, grpcoord, grprange, grpheight, blind})
            -- self:I({grpname,grprange, grpheight})
            self.ShoradGroupSet:Add(grpname,group)
            if self.autoshorad then
              self.Shorad.Groupset = self.ShoradGroupSet
            end
          end
        end
     end
     self.SAM_Table = SAM_Tbl
     self.SAM_Table_Long = SAM_Tbl_lg
     self.SAM_Table_Medium = SAM_Tbl_md
     self.SAM_Table_Short = SAM_Tbl_sh
     -- make SAMs evasive
     if self.mysead ~= nil then
      local mysead = self.mysead
      mysead:UpdateSet( SEAD_Grps )
     end
     return self
  end

  --- Function to link up #MANTIS with a #SHORAD installation
  -- @param #MANTIS self
  -- @param Functional.Shorad#SHORAD Shorad The #SHORAD object
  -- @param #number Shoradtime Number of seconds #SHORAD stays active post wake-up
  function MANTIS:AddShorad(Shorad,Shoradtime)
    self:T(self.lid.."AddShorad")
    local Shorad = Shorad or nil
    local ShoradTime = Shoradtime or 600
    local ShoradLink = true
    if Shorad:IsInstanceOf("SHORAD") then
      self.ShoradLink = ShoradLink
      self.Shorad = Shorad --#SHORAD
      self.ShoradTime = Shoradtime -- #number
    end
    return self
  end

  --- Function to unlink #MANTIS from a #SHORAD installation
  -- @param #MANTIS self
  function MANTIS:RemoveShorad()
    self:T(self.lid.."RemoveShorad")
    self.ShoradLink = false
    return self
  end

-----------------------------------------------------------------------
-- MANTIS main functions
-----------------------------------------------------------------------
  
  --- [Internal] Check detection function
  -- @param #MANTIS self
  -- @param #table samset Table of SAM data
  -- @param #table detset Table of COORDINATES
  -- @param #boolean dlink Using DLINK
  -- @param #limit limit of SAM sites to go active on a contact
  -- @return #MANTIS self
  function MANTIS:_CheckLoop(samset,detset,dlink,limit)
    self:T(self.lid .. "CheckLoop " .. #detset .. " Coordinates")
    local switchedon = 0
    for _,_data in pairs (samset) do
      local samcoordinate = _data[2]
      local name = _data[1]
      local radius = _data[3]
      local height = _data[4]
      local blind = _data[5] * 1.25 + 1
      local samgroup = GROUP:FindByName(name)
      local IsInZone, Distance = self:_CheckObjectInZone(detset, samcoordinate, radius, height, dlink)
      local suppressed = self.SuppressedGroups[name] or false
      local activeshorad = self.Shorad.ActiveGroups[name] or false
      if IsInZone and not suppressed and not activeshorad then --check any target in zone and not currently managed by SEAD
        if samgroup:IsAlive() then
          -- switch on SAM
          local switch = false
          if self.UseEmOnOff and switchedon < limit then
            -- DONE: add emissions on/off
            samgroup:EnableEmission(true)
            switchedon = switchedon + 1
            switch = true
          elseif (not self.UseEmOnOff) and switchedon < limit then
            samgroup:OptionAlarmStateRed()
            switchedon = switchedon + 1
            switch = true
          end
          if self.SamStateTracker[name] ~= "RED" and switch then
            self:__RedState(1,samgroup)
            self.SamStateTracker[name] = "RED"
          end
          -- link in to SHORAD if available
          -- DONE: Test integration fully
          if self.ShoradLink and (Distance < self.ShoradActDistance or Distance < blind ) then -- don't give SHORAD position away too early
            local Shorad = self.Shorad
            local radius = self.checkradius
            local ontime = self.ShoradTime
            Shorad:WakeUpShorad(name, radius, ontime)
            self:__ShoradActivated(1,name, radius, ontime)
          end
          -- debug output
          if (self.debug or self.verbose) and switch then
            local text = string.format("SAM %s in alarm state RED!", name)
            local m=MESSAGE:New(text,10,"MANTIS"):ToAllIf(self.debug)
            if self.verbose then self:I(self.lid..text) end
          end
        end --end alive
      else
        if samgroup:IsAlive() and not suppressed and not activeshorad then
          -- switch off SAM
          if self.UseEmOnOff  then
            samgroup:EnableEmission(false)
          else
            samgroup:OptionAlarmStateGreen()
          end
          if self.SamStateTracker[name] ~= "GREEN" then
            self:__GreenState(1,samgroup)
            self.SamStateTracker[name] = "GREEN"
          end
          if self.debug or self.verbose then
            local text = string.format("SAM %s in alarm state GREEN!", name)
            local m=MESSAGE:New(text,10,"MANTIS"):ToAllIf(self.debug)
            if self.verbose then self:I(self.lid..text) end
          end
        end --end alive
      end --end check
    end --for for loop
    return self
  end
  
  --- [Internal] Check detection function
  -- @param #MANTIS self
  -- @param Functional.Detection#DETECTION_AREAS detection Detection object
  -- @param #boolean dlink
  -- @return #MANTIS self
  function MANTIS:_Check(detection,dlink)
    self:T(self.lid .. "Check")
    --get detected set
    local detset = detection:GetDetectedItemCoordinates()
    --self:T("Check:", {detset})
    -- randomly update SAM Table
    local rand = math.random(1,100)
    if rand > 65 then -- 1/3 of cases
      self:_RefreshSAMTable()
    end
    -- switch SAMs on/off if (n)one of the detected groups is inside their reach
    if self.automode then
      local samset = self.SAM_Table_Long -- table of i.1=names, i.2=coordinates, i.3=firing range, i.4=firing height
      self:_CheckLoop(samset,detset,dlink,self.maxlongrange)
      local samset = self.SAM_Table_Medium -- table of i.1=names, i.2=coordinates, i.3=firing range, i.4=firing height
      self:_CheckLoop(samset,detset,dlink,self.maxmidrange)
      local samset = self.SAM_Table_Short -- table of i.1=names, i.2=coordinates, i.3=firing range, i.4=firing height
      self:_CheckLoop(samset,detset,dlink,self.maxshortrange)
    else
      local samset = self:_GetSAMTable() -- table of i.1=names, i.2=coordinates, i.3=firing range, i.4=firing height
      self:_CheckLoop(samset,detset,dlink,self.maxclassic)
    end
    return self
  end

  --- [Internal] Relocation relay function
  -- @param #MANTIS self
  -- @return #MANTIS self
  function MANTIS:_Relocate()
    self:T(self.lid .. "Relocate")
    self:_RelocateGroups()
    return self
  end

  --- [Internal] Check advanced state
  -- @param #MANTIS self
  -- @return #MANTIS self
  function MANTIS:_CheckAdvState()
    self:T(self.lid .. "CheckAdvSate")
    local interval, oldstate = self:_CalcAdvState()
    local newstate = self.adv_state
    if newstate ~= oldstate then
      -- deal with new state
      self:__AdvStateChange(1,oldstate,newstate,interval)
      if newstate == 2 then
        -- switch alarm state RED
        self.state2flag = true
        local samset = self:_GetSAMTable() -- table of i.1=names, i.2=coordinates
        for _,_data in pairs (samset) do
          local name = _data[1]
          local samgroup = GROUP:FindByName(name)
          if samgroup:IsAlive() then
            if self.UseEmOnOff then
              -- DONE: add emissions on/off
              --samgroup:SetAIOn()
              samgroup:EnableEmission(true)
            else
              samgroup:OptionAlarmStateRed()
            end
          end -- end alive
        end -- end for loop
      elseif newstate <= 1 then
        -- change MantisTimer to slow down or speed up
        self.detectinterval = interval
        self.state2flag = false
      end
    end -- end newstate vs oldstate
    return self
  end

  --- [Internal] Check DLink state
  -- @param #MANTIS self
  -- @return #MANTIS self
  function MANTIS:_CheckDLinkState()
    self:T(self.lid .. "_CheckDLinkState")
    local dlink = self.Detection -- Ops.Intelligence#INTEL_DLINK
    local TS = timer.getAbsTime()
    if not dlink:Is("Running") and (TS - self.DLTimeStamp > 29) then
      self.DLink = false
      self.Detection = self:StartDetection() -- fall back
      self:I(self.lid .. "Intel DLink not running - switching back to single detection!")
    end
  end

  --- [Internal] Function to set start state
  -- @param #MANTIS self
  -- @param #string From The From State
  -- @param #string Event The Event
  -- @param #string To The To State
  -- @return #MANTIS self
  function MANTIS:onafterStart(From, Event, To)
    self:T({From, Event, To})
    self:T(self.lid.."Starting MANTIS")
    self:SetSAMStartState()
    if not INTEL then
      self.Detection = self:StartDetection()
    else
      self.Detection = self:StartIntelDetection()
    end
    --[[
    if self.advAwacs and not self.automode then
      self.AWACS_Detection = self:StartAwacsDetection()
    end
    --]]
    if self.autoshorad then
      self.Shorad = SHORAD:New(self.name.."-SHORAD",self.name.."-SHORAD",self.SAM_Group,self.ShoradActDistance,self.ShoradTime,self.coalition,self.UseEmOnOff)
      self.Shorad:SetDefenseLimits(80,95)
      self.ShoradLink = true
      self.Shorad.Groupset=self.ShoradGroupSet
    end
    self:__Status(-math.random(1,10))
    return self
  end

  --- [Internal] Before status function for MANTIS
  -- @param #MANTIS self
  -- @param #string From The From State
  -- @param #string Event The Event
  -- @param #string To The To State
  -- @return #MANTIS self
  function MANTIS:onbeforeStatus(From, Event, To)
    self:T({From, Event, To})
    -- check detection
    if not self.state2flag then
      self:_Check(self.Detection,self.DLink)
    end

    --[[ check Awacs
    if self.advAwacs and not self.state2flag then
      self:_Check(self.AWACS_Detection,false)
    end
    --]]
    
    -- relocate HQ and EWR
    if self.autorelocate then
      local relointerval = self.relointerval
      local thistime = timer.getAbsTime()
      local timepassed = thistime - self.TimeStamp

      local halfintv = math.floor(timepassed / relointerval)

      --self:T({timepassed=timepassed, halfintv=halfintv})

      if halfintv >= 1 then
        self.TimeStamp = timer.getAbsTime()
        self:_Relocate()
        self:__Relocating(1)
      end
    end

    -- advanced state check
    if self.advanced then
      self:_CheckAdvState()
    end

    -- check DLink state
    if self.DLink then
      self:_CheckDLinkState()
    end

    return self
  end

  --- [Internal] Status function for MANTIS
  -- @param #MANTIS self
  -- @param #string From The From State
  -- @param #string Event The Event
  -- @param #string To The To State
  -- @return #MANTIS self
  function MANTIS:onafterStatus(From,Event,To)
    self:T({From, Event, To})
    -- Display some states
    if self.debug and self.verbose then
      self:I(self.lid .. "Status Report")
      for _name,_state in pairs(self.SamStateTracker) do
        self:I(string.format("Site %s\tStatus %s",_name,_state))
      end
    end
    local interval = self.detectinterval * -1
    self:__Status(interval)
    return self
  end

  --- [Internal] Function to stop MANTIS
  -- @param #MANTIS self
  -- @param #string From The From State
  -- @param #string Event The Event
  -- @param #string To The To State
  -- @return #MANTIS self
  function MANTIS:onafterStop(From, Event, To)
    self:T({From, Event, To})
    return self
  end

  --- [Internal] Function triggered by Event Relocating
  -- @param #MANTIS self
  -- @param #string From The From State
  -- @param #string Event The Event
  -- @param #string To The To State
  -- @return #MANTIS self
  function MANTIS:onafterRelocating(From, Event, To)
    self:T({From, Event, To})
    return self
  end

  --- [Internal] Function triggered by Event GreenState
  -- @param #MANTIS self
  -- @param #string From The From State
  -- @param #string Event The Event
  -- @param #string To The To State
  -- @param Wrapper.Group#GROUP Group The GROUP object whose state was changed
  -- @return #MANTIS self
  function MANTIS:onafterGreenState(From, Event, To, Group)
    self:T({From, Event, To, Group:GetName()})
    return self
  end

  --- [Internal] Function triggered by Event RedState
  -- @param #MANTIS self
  -- @param #string From The From State
  -- @param #string Event The Event
  -- @param #string To The To State
  -- @param Wrapper.Group#GROUP Group The GROUP object whose state was changed
  -- @return #MANTIS self
  function MANTIS:onafterRedState(From, Event, To, Group)
    self:T({From, Event, To, Group:GetName()})
    return self
  end

  --- [Internal] Function triggered by Event AdvStateChange
  -- @param #MANTIS self
  -- @param #string From The From State
  -- @param #string Event The Event
  -- @param #string To The To State
  -- @param #number Oldstate Old state - 0 = green, 1 = amber, 2 = red
  -- @param #number Newstate New state - 0 = green, 1 = amber, 2 = red
  -- @param #number Interval Calculated detection interval based on state and advanced feature setting
  -- @return #MANTIS self
  function MANTIS:onafterAdvStateChange(From, Event, To, Oldstate, Newstate, Interval)
    self:T({From, Event, To, Oldstate, Newstate, Interval})
    return self
  end

  --- [Internal] Function triggered by Event ShoradActivated
  -- @param #MANTIS self
  -- @param #string From The From State
  -- @param #string Event The Event
  -- @param #string To The To State
  -- @param #string Name Name of the GROUP which SHORAD shall protect
  -- @param #number Radius Radius around the named group to find SHORAD groups
  -- @param #number Ontime Seconds the SHORAD will stay active
  function MANTIS:onafterShoradActivated(From, Event, To, Name, Radius, Ontime)
    self:T({From, Event, To, Name, Radius, Ontime})
    return self
  end
  
    --- [Internal] Function triggered by Event SeadSuppressionStart
  -- @param #MANTIS self
  -- @param #string From The From State
  -- @param #string Event The Event
  -- @param #string To The To State
  -- @param Wrapper.Group#GROUP Group The suppressed GROUP object
  -- @param #string Name Name of the suppressed group
  -- @param Wrapper.Group#GROUP Attacker The attacking GROUP object
  function MANTIS:onafterSeadSuppressionStart(From, Event, To, Group, Name, Attacker)
    self:T({From, Event, To, Name})
    self.SuppressedGroups[Name] = true
    if self.ShoradLink then
      local Shorad = self.Shorad
      local radius = self.checkradius
      local ontime = self.ShoradTime
      Shorad:WakeUpShorad(Name, radius, ontime)
      self:__ShoradActivated(1,Name, radius, ontime)
    end
    return self
  end
  
    --- [Internal] Function triggered by Event SeadSuppressionEnd
  -- @param #MANTIS self
  -- @param #string From The From State
  -- @param #string Event The Event
  -- @param #string To The To State
  -- @param Wrapper.Group#GROUP Group The suppressed GROUP object
  -- @param #string Name Name of the suppressed group
  function MANTIS:onafterSeadSuppressionEnd(From, Event, To, Group, Name)
    self:T({From, Event, To, Name})
    self.SuppressedGroups[Name] = false
    return self
  end
  
    --- [Internal] Function triggered by Event SeadSuppressionPlanned
  -- @param #MANTIS self
  -- @param #string From The From State
  -- @param #string Event The Event
  -- @param #string To The To State
  -- @param Wrapper.Group#GROUP Group The suppressed GROUP object
  -- @param #string Name Name of the suppressed group
  -- @param #number SuppressionStartTime Model start time of the suppression from `timer.getTime()`
  -- @param #number SuppressionEndTime Model end time of the suppression from `timer.getTime()`
  -- @param Wrapper.Group#GROUP Attacker The attacking GROUP object
  function MANTIS:onafterSeadSuppressionPlanned(From, Event, To, Group, Name, SuppressionStartTime, SuppressionEndTime, Attacker)
    self:T({From, Event, To, Name})
    return self
  end
  
end
-----------------------------------------------------------------------
-- MANTIS end
-----------------------------------------------------------------------
