--- **Wrapper** - Markers On the F10 map.
--
-- **Main Features:**
--
--    * Convenient handling of markers via multiple user API functions.
--    * Update text and position of marker easily via scripting.
--    * Delay creation and removal of markers via (optional) parameters.
--    * Retrieve data such as text and coordinate.
--    * Marker specific FSM events when a marker is added, removed or changed.
--    * Additional FSM events when marker text or position is changed.
--
-- ===
--
-- ### Author: **funkyfranky**
-- @module Wrapper.Marker
-- @image MOOSE_Core.JPG

--- Marker class.
-- @type MARKER
-- @field #string ClassName Name of the class.
-- @field #boolean Debug Debug mode. Messages to all about status.
-- @field #string lid Class id string for output to DCS log file.
-- @field #number mid Marker ID.
-- @field Core.Point#COORDINATE coordinate Coordinate of the mark.
-- @field #string text Text displayed in the mark panel.
-- @field #string message Message displayed when the mark is added.
-- @field #boolean readonly Marker is read-only.
-- @field #number coalition Coalition to which the marker is displayed.
-- @extends Core.Fsm#FSM

--- Just because...
--
-- ===
--
-- # The MARKER Class Idea
--
-- The MARKER class simplifies creating, updating and removing of markers on the F10 map.
--
-- # Create a Marker
--
--     -- Create a MARKER object at Batumi with a trivial text.
--     local Coordinate = AIRBASE:FindByName( "Batumi" ):GetCoordinate()
--     mymarker = MARKER:New( Coordinate, "I am Batumi Airfield" )
--
-- Now this does **not** show the marker yet. We still need to specify to whom it is shown. There are several options, i.e.
-- show the marker to everyone, to a specific coalition only, or only to a specific group.
--
-- ## For Everyone
--
-- If the marker should be visible to everyone, you can use the :ToAll() function.
--
--     mymarker = MARKER:New( Coordinate, "I am Batumi Airfield" ):ToAll()
--
-- ## For a Coalition
--
-- If the maker should be visible to a specific coalition, you can use the :ToCoalition() function.
--
--     mymarker = MARKER:New( Coordinate , "I am Batumi Airfield" ):ToCoalition( coalition.side.BLUE )
--     
-- This would show the marker only to the Blue coalition.
--
-- ## For a Group
--
--     mymarker = MARKER:New( Coordinate , "Target Location" ):ToGroup( tankGroup )
--
-- # Removing a Marker
--     mymarker:Remove(60)
-- This removes the marker after 60 seconds
--
-- # Updating a Marker
--
-- The marker text and coordinate can be updated easily as shown below.
--
-- However, note that **updating involves to remove and recreate the marker if either text or its coordinate is changed**.
-- *This is a DCS scripting engine limitation.*
--
-- ## Update Text
--
-- If you created a marker "mymarker" as shown above, you can update the displayed test by
--
--     mymarker:UpdateText( "I am the new text at Batumi" )
--
-- The update can also be delayed by, e.g. 90 seconds, using
--
--     mymarker:UpdateText( "I am the new text at Batumi", 90 )
--
-- ## Update Coordinate
--
-- If you created a marker "mymarker" as shown above, you can update its coordinate on the F10 map by
--
--     mymarker:UpdateCoordinate( NewCoordinate )
--
-- The update can also be delayed by, e.g. 60 seconds, using
--
--     mymarker:UpdateCoordinate( NewCoordinate , 60 )
--
-- # Retrieve Data
--
-- The important data as the displayed text and the coordinate of the marker can be retrieved easily.
--
-- ## Text
--
--     local text  =mymarker:GetText()
--     env.info( "Marker Text = " .. text )
--
-- ## Coordinate
--
--     local Coordinate = mymarker:GetCoordinate()
--     env.info( "Marker Coordinate LL DSM = " .. Coordinate:ToStringLLDMS() )
--
--
-- # FSM Events
--
-- Moose creates additional events, so called FSM event, when markers are added, changed, removed, and text or the coordinate is updated.
--
-- These events can be captured and used for processing via OnAfter functions as shown below.
--
-- ## Added
--
-- ## Changed
--
-- ## Removed
--
-- ## TextUpdate
--
-- ## CoordUpdate
--
--
-- # Examples
--
-- @field #MARKER
MARKER = {
  ClassName = "MARKER",
  Debug = false,
  lid = nil,
  mid = nil,
  coordinate = nil,
  text = nil,
  message = nil,
  readonly = nil,
  coalition = nil,
}

--- Marker ID. Running number.
_MARKERID = 0

--- Marker class version.
-- @field #string version
MARKER.version="0.1.1"

-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- TODO list
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

-- TODO: User "Get" functions. E.g., :GetCoordinate()
-- DONE: Add delay to user functions.
-- DONE: Handle events.
-- DONE: Create FSM events.

-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Constructor
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

--- Create a new MARKER class object.
-- @param #MARKER self
-- @param Core.Point#COORDINATE Coordinate Coordinate where to place the marker.
-- @param #string Text Text displayed on the mark panel.
-- @return #MARKER self
function MARKER:New( Coordinate, Text )

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

  self.coordinate=UTILS.DeepCopy(Coordinate)

  self.text = Text

  -- Defaults
  self.readonly = false
  self.message = ""

  -- New marker ID. This is not the one of the actual marker.
  _MARKERID = _MARKERID + 1

  self.myid = _MARKERID

  -- Log ID.
  self.lid = string.format( "Marker #%d | ", self.myid )

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

  -- Add FSM transitions.
  --                 From State  -->   Event      -->     To State
  self:AddTransition( "Invisible", "Added", "Visible" ) -- Marker was added.
  self:AddTransition( "Visible", "Removed", "Invisible" ) -- Marker was removed.
  self:AddTransition( "*", "Changed", "*" ) -- Marker was changed.

  self:AddTransition( "*", "TextUpdate", "*" ) -- Text updated.
  self:AddTransition( "*", "CoordUpdate", "*" ) -- Coordinates updated.

  --- Triggers the FSM event "Added".
  -- @function [parent=#MARKER] Added
  -- @param #MARKER self
  -- @param Core.Event#EVENTDATA EventData Event data table.

  --- Triggers the delayed FSM event "Added".
  -- @function [parent=#MARKER] __Added
  -- @param #MARKER self
  -- @param Core.Event#EVENTDATA EventData Event data table.

  --- On after "Added" event user function.
  -- @function [parent=#MARKER] OnAfterAdded
  -- @param #MARKER self
  -- @param #string From From state.
  -- @param #string Event Event.
  -- @param #string To To state.
  -- @param Core.Event#EVENTDATA EventData Event data table.

  --- Triggers the FSM event "Removed".
  -- @function [parent=#MARKER] Removed
  -- @param #MARKER self
  -- @param Core.Event#EVENTDATA EventData Event data table.

  --- Triggers the delayed FSM event "Removed".
  -- @function [parent=#MARKER] __Removed
  -- @param #MARKER self
  -- @param Core.Event#EVENTDATA EventData Event data table.

  --- On after "Removed" event user function.
  -- @function [parent=#MARKER] OnAfterRemoved
  -- @param #MARKER self
  -- @param #string From From state.
  -- @param #string Event Event.
  -- @param #string To To state.
  -- @param Core.Event#EVENTDATA EventData Event data table.

  --- Triggers the FSM event "Changed".
  -- @function [parent=#MARKER] Changed
  -- @param #MARKER self
  -- @param Core.Event#EVENTDATA EventData Event data table.

  --- Triggers the delayed FSM event "Changed".
  -- @function [parent=#MARKER] __Changed
  -- @param #MARKER self
  -- @param Core.Event#EVENTDATA EventData Event data table.

  --- On after "Changed" event user function.
  -- @function [parent=#MARKER] OnAfterChanged
  -- @param #MARKER self
  -- @param #string From From state.
  -- @param #string Event Event.
  -- @param #string To To state.
  -- @param Core.Event#EVENTDATA EventData Event data table.

  --- Triggers the FSM event "TextUpdate".
  -- @function [parent=#MARKER] TextUpdate
  -- @param #MARKER self
  -- @param #string Text The new text.

  --- Triggers the delayed FSM event "TextUpdate".
  -- @function [parent=#MARKER] __TextUpdate
  -- @param #MARKER self
  -- @param #string Text The new text.

  --- On after "TextUpdate" event user function.
  -- @function [parent=#MARKER] OnAfterTextUpdate
  -- @param #MARKER self
  -- @param #string From From state.
  -- @param #string Event Event.
  -- @param #string To To state.
  -- @param #string Text The new text.

  --- Triggers the FSM event "CoordUpdate".
  -- @function [parent=#MARKER] CoordUpdate
  -- @param #MARKER self
  -- @param Core.Point#COORDINATE Coordinate The new Coordinate.

  --- Triggers the delayed FSM event "CoordUpdate".
  -- @function [parent=#MARKER] __CoordUpdate
  -- @param #MARKER self
  -- @param Core.Point#COORDINATE Coordinate The updated Coordinate.

  --- On after "CoordUpdate" event user function.
  -- @function [parent=#MARKER] OnAfterCoordUpdate
  -- @param #MARKER self
  -- @param #string From From state.
  -- @param #string Event Event.
  -- @param #string To To state.
  -- @param Core.Point#COORDINATE Coordinate The updated Coordinate.

  -- Handle events.
  self:HandleEvent( EVENTS.MarkAdded )
  self:HandleEvent( EVENTS.MarkRemoved )
  self:HandleEvent( EVENTS.MarkChange )

  return self
end

-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- User API Functions
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

--- Marker is readonly. Text cannot be changed and marker cannot be removed. The will not update the marker in the game, Call MARKER:Refresh to update state.
-- @param #MARKER self
-- @return #MARKER self
function MARKER:ReadOnly()

  self.readonly = true

  return self
end

--- Marker is read and write. Text cannot be changed and marker cannot be removed. The will not update the marker in the game, Call MARKER:Refresh to update state.
-- @param #MARKER self
-- @return #MARKER self
function MARKER:ReadWrite()

  self.readonly=false

  return self
end

--- Set message that is displayed on screen if the marker is added.
-- @param #MARKER self
-- @param #string Text Message displayed when the marker is added.
-- @return #MARKER self
function MARKER:Message( Text )

  self.message = Text or ""

  return self
end

--- Place marker visible for everyone.
-- @param #MARKER self
-- @param #number Delay (Optional) Delay in seconds, before the marker is created.
-- @return #MARKER self
function MARKER:ToAll( Delay )

  if Delay and Delay > 0 then
    self:ScheduleOnce( Delay, MARKER.ToAll, self )
  else

    self.toall = true
    self.tocoalition = nil
    self.coalition = nil
    self.togroup = nil
    self.groupname = nil
    self.groupid = nil

    -- First remove an existing mark.
    if self.shown then
      self:Remove()
    end

    self.mid = UTILS.GetMarkID()

    -- Call DCS function.
    trigger.action.markToAll( self.mid, self.text, self.coordinate:GetVec3(), self.readonly, self.message )

  end

  return self
end

--- Place marker visible for a specific coalition only.
-- @param #MARKER self
-- @param #number Coalition Coalition 1=Red, 2=Blue, 0=Neutral. See `coalition.side.RED`.
-- @param #number Delay (Optional) Delay in seconds, before the marker is created.
-- @return #MARKER self
function MARKER:ToCoalition( Coalition, Delay )

  if Delay and Delay > 0 then
    self:ScheduleOnce( Delay, MARKER.ToCoalition, self, Coalition )
  else

    self.coalition = Coalition

    self.tocoalition = true
    self.toall = false
    self.togroup = false
    self.groupname = nil
    self.groupid = nil

    -- First remove an existing mark.
    if self.shown then
      self:Remove()
    end

    self.mid = UTILS.GetMarkID()

    -- Call DCS function.
    trigger.action.markToCoalition( self.mid, self.text, self.coordinate:GetVec3(), self.coalition, self.readonly, self.message )

  end

  return self
end

--- Place marker visible for the blue coalition only.
-- @param #MARKER self
-- @param #number Delay (Optional) Delay in seconds, before the marker is created.
-- @return #MARKER self
function MARKER:ToBlue( Delay )
  self:ToCoalition( coalition.side.BLUE, Delay )
  return self
end

--- Place marker visible for the blue coalition only.
-- @param #MARKER self
-- @param #number Delay (Optional) Delay in seconds, before the marker is created.
-- @return #MARKER self
function MARKER:ToRed( Delay )
  self:ToCoalition( coalition.side.RED, Delay )
  return self
end

--- Place marker visible for the neutral coalition only.
-- @param #MARKER self
-- @param #number Delay (Optional) Delay in seconds, before the marker is created.
-- @return #MARKER self
function MARKER:ToNeutral( Delay )
  self:ToCoalition( coalition.side.NEUTRAL, Delay )
  return self
end

--- Place marker visible for a specific group only.
-- @param #MARKER self
-- @param Wrapper.Group#GROUP Group The group to which the marker is displayed.
-- @param #number Delay (Optional) Delay in seconds, before the marker is created.
-- @return #MARKER self
function MARKER:ToGroup( Group, Delay )

  if Delay and Delay > 0 then
    self:ScheduleOnce( Delay, MARKER.ToGroup, self, Group )
  else

    -- Check if group exists.
    if Group and Group:IsAlive() ~= nil then

      self.groupid = Group:GetID()

      if self.groupid then

        self.groupname = Group:GetName()

        self.togroup = true
        self.tocoalition = nil
        self.coalition = nil
        self.toall = nil

        -- First remove an existing mark.
        if self.shown then
          self:Remove()
        end

        self.mid = UTILS.GetMarkID()

        -- Call DCS function.
        trigger.action.markToGroup( self.mid, self.text, self.coordinate:GetVec3(), self.groupid, self.readonly, self.message )

      end

    else
      -- TODO: Warning!
    end

  end

  return self
end

--- Update the text displayed on the mark panel.
-- @param #MARKER self
-- @param #string Text Updated text.
-- @param #number Delay (Optional) Delay in seconds, before the marker is created.
-- @return #MARKER self
function MARKER:UpdateText( Text, Delay )

  if Delay and Delay > 0 then
    self:ScheduleOnce( Delay, MARKER.UpdateText, self, Text )
  else

    self.text = tostring( Text )

    self:Refresh()

    self:TextUpdate( tostring( Text ) )

  end

  return self
end

--- Update the coordinate where the marker is displayed.
-- @param #MARKER self
-- @param Core.Point#COORDINATE Coordinate The new coordinate.
-- @param #number Delay (Optional) Delay in seconds, before the marker is created.
-- @return #MARKER self
function MARKER:UpdateCoordinate( Coordinate, Delay )

  if Delay and Delay > 0 then
    self:ScheduleOnce( Delay, MARKER.UpdateCoordinate, self, Coordinate )
  else

    self.coordinate = Coordinate

    self:Refresh()

    self:CoordUpdate( Coordinate )

  end

  return self
end

--- Refresh the marker.
-- @param #MARKER self
-- @param #number Delay (Optional) Delay in seconds, before the marker is created.
-- @return #MARKER self
function MARKER:Refresh( Delay )

  if Delay and Delay > 0 then
    self:ScheduleOnce( Delay, MARKER.Refresh, self )
  else

    if self.toall then

      self:ToAll()

    elseif self.tocoalition then

      self:ToCoalition( self.coalition )

    elseif self.togroup then

      local group = GROUP:FindByName( self.groupname )

      self:ToGroup( group )

    else
      self:E( self.lid .. "ERROR: unknown To in :Refresh()!" )
    end

  end

  return self
end

--- Remove a marker.
-- @param #MARKER self
-- @param #number Delay (Optional) Delay in seconds, before the marker is removed.
-- @return #MARKER self
function MARKER:Remove( Delay )

  if Delay and Delay > 0 then
    self:ScheduleOnce( Delay, MARKER.Remove, self )
  else

    if self.shown then

      -- Call DCS function.
      trigger.action.removeMark( self.mid )

    end

  end

  return self
end

--- Get position of the marker.
-- @param #MARKER self
-- @return Core.Point#COORDINATE The coordinate of the marker.
function MARKER:GetCoordinate()
  return self.coordinate
end

--- Get text that is displayed in the marker panel.
-- @param #MARKER self
-- @return #string Marker text.
function MARKER:GetText()
  return self.text
end

--- Set text that is displayed in the marker panel. Note this does not show the marker.
-- @param #MARKER self
-- @param #string Text Marker text. Default is an empty string "".
-- @return #MARKER self
function MARKER:SetText( Text )
  self.text = Text and tostring( Text ) or ""
  return self
end

--- Check if marker is currently visible on the F10 map.
-- @param #MARKER self
-- @return #boolean True if the marker is currently visible.
function MARKER:IsVisible()
  return self:Is( "Visible" )
end

--- Check if marker is currently invisible on the F10 map.
-- @param #MARKER self
-- @return
function MARKER:IsInvisible()
  return self:Is( "Invisible" )
end

-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Event Functions
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

--- Event function when a MARKER is added.
-- @param #MARKER self
-- @param Core.Event#EVENTDATA EventData
function MARKER:OnEventMarkAdded( EventData )

  if EventData and EventData.MarkID then

    local MarkID = EventData.MarkID

    self:T3( self.lid .. string.format( "Captured event MarkAdded for Mark ID=%s", tostring( MarkID ) ) )

    if MarkID == self.mid then

      self.shown = true

      self:Added( EventData )

    end

  end

end

--- Event function when a MARKER is removed.
-- @param #MARKER self
-- @param Core.Event#EVENTDATA EventData
function MARKER:OnEventMarkRemoved( EventData )

  if EventData and EventData.MarkID then

    local MarkID = EventData.MarkID

    local MarkID=EventData.MarkID

    self:T3(self.lid..string.format("Captured event MarkRemoved for Mark ID=%s", tostring(MarkID)))

    if MarkID == self.mid then

      self.shown = false

      self:Removed( EventData )

    end

  end

end

--- Event function when a MARKER changed.
-- @param #MARKER self
-- @param Core.Event#EVENTDATA EventData
function MARKER:OnEventMarkChange( EventData )

  if EventData and EventData.MarkID then

    local MarkID = EventData.MarkID

    self:T3( self.lid .. string.format( "Captured event MarkChange for Mark ID=%s", tostring( MarkID ) ) )

    if MarkID == self.mid then

    local MarkID=EventData.MarkID

    self:T3(self.lid..string.format("Captured event MarkChange for Mark ID=%s", tostring(MarkID)))

    if MarkID==self.mid then

      self.text=tostring(EventData.MarkText)

      self:Changed(EventData)

    end

  end

end
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- FSM Event Functions
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

--- On after "Added" event.
-- @param #MARKER self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
-- @param Core.Event#EVENTDATA EventData Event data table.
function MARKER:onafterAdded( From, Event, To, EventData )

  -- Debug info.
  local text = string.format( "Captured event MarkAdded for myself:\n" )
  text = text .. string.format( "Marker ID  = %s\n", tostring( EventData.MarkID ) )
  text = text .. string.format( "Coalition  = %s\n", tostring( EventData.MarkCoalition ) )
  text = text .. string.format( "Group  ID  = %s\n", tostring( EventData.MarkGroupID ) )
  text = text .. string.format( "Initiator  = %s\n", EventData.IniUnit and EventData.IniUnit:GetName() or "Nobody" )
  text = text .. string.format( "Coordinate = %s\n", EventData.MarkCoordinate and EventData.MarkCoordinate:ToStringLLDMS() or "Nowhere" )
  text = text .. string.format( "Text:          \n%s", tostring( EventData.MarkText ) )
  self:T2( self.lid .. text )

end

--- On after "Removed" event.
-- @param #MARKER self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
-- @param Core.Event#EVENTDATA EventData Event data table.
function MARKER:onafterRemoved( From, Event, To, EventData )

  -- Debug info.
  local text = string.format( "Captured event MarkRemoved for myself:\n" )
  text = text .. string.format( "Marker ID  = %s\n", tostring( EventData.MarkID ) )
  text = text .. string.format( "Coalition  = %s\n", tostring( EventData.MarkCoalition ) )
  text = text .. string.format( "Group  ID  = %s\n", tostring( EventData.MarkGroupID ) )
  text = text .. string.format( "Initiator  = %s\n", EventData.IniUnit and EventData.IniUnit:GetName() or "Nobody" )
  text = text .. string.format( "Coordinate = %s\n", EventData.MarkCoordinate and EventData.MarkCoordinate:ToStringLLDMS() or "Nowhere" )
  text = text .. string.format( "Text:          \n%s", tostring( EventData.MarkText ) )
  self:T2( self.lid .. text )

end

--- On after "Changed" event.
-- @param #MARKER self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
-- @param Core.Event#EVENTDATA EventData Event data table.
function MARKER:onafterChanged( From, Event, To, EventData )

  -- Debug info.
  local text = string.format( "Captured event MarkChange for myself:\n" )
  text = text .. string.format( "Marker ID  = %s\n", tostring( EventData.MarkID ) )
  text = text .. string.format( "Coalition  = %s\n", tostring( EventData.MarkCoalition ) )
  text = text .. string.format( "Group  ID  = %s\n", tostring( EventData.MarkGroupID ) )
  text = text .. string.format( "Initiator  = %s\n", EventData.IniUnit and EventData.IniUnit:GetName() or "Nobody" )
  text = text .. string.format( "Coordinate = %s\n", EventData.MarkCoordinate and EventData.MarkCoordinate:ToStringLLDMS() or "Nowhere" )
  text = text .. string.format( "Text:          \n%s", tostring( EventData.MarkText ) )
  self:T2( self.lid .. text )

end

--- On after "TextUpdate" event.
-- @param #MARKER self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
-- @param #string Text The updated text, displayed in the mark panel.
function MARKER:onafterTextUpdate( From, Event, To, Text )

  self:T( self.lid .. string.format( "New Marker Text:\n%s", Text ) )

end

--- On after "CoordUpdate" event.
-- @param #MARKER self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
-- @param Core.Point#COORDINATE Coordinate The updated coordinates.
function MARKER:onafterCoordUpdate( From, Event, To, Coordinate )

  self:T( self.lid .. string.format( "New Marker Coordinate in LL DMS: %s", Coordinate:ToStringLLDMS() ) )

end
