local gKH = {}; --Dummy up LOCAL gKH namespace, avoid staying in memory after execution. 
gKH.Unit = {}; -- Dummy up Unit API namespace.
gKH.Side = {}; -- Dummy up Side API namespace.

-- Function to return true if a it's very likely a aircraft unit is currently in the air.
-- takes in unit wrapper of unit to check
function gKH.Unit:isAircraftAirborne(u)
  if ((u.condition_v == "Airborne") or u.condition_V == "RTB") or u.condition_V == "Landing_PreTouchdown" or
   u.condition_V == "ManoeuveringToRefuel" or u.condition_V == "Refuelling " or u.condition_V == "OffloadingFuel" or 
   u.condition_V == "DeployingDippingSonar" or u.condition_V == "EmergencyLanding" or  u.condition_V == "BVRAttack" or 
   u.condition_V == "BVRCrank" or u.condition_V == "Dogfight" or u.condition_V == "BVRDrag" or u.condition_V == "TransferringCargo" then
    return true;
  end
  return false;
end

-- Function to return true if a it's very likely a ship\sub unit is docked at a port.
-- takes in unit wrapper of unit to check
function gKH.Unit:isShipSubAtDock(u)
  if (u.condition_v == "Docked" or u.altitude > 0.0) or u.condition_V == "Docking" or u.condition_V == "DeployingUnderway" then
    return true;
  end
  return false;
end


-- Function to return true if a unit is determinted to be 99% likely to be "in a base or port", other wise false.
-- takes in unit wrapper of unit to check
-- Known external gKHApi Uses:  gKH.Side:ReplaceSingleUnit()
function gKH.Unit:isUnitInAnyBase(u)
  retval = false;
  if u.type == 'Aircraft' then
    if (u.airbornetime == 0) or gKH.Unit:isAircraftAirborne(u) == false then
      return true;
    end
  elseif u.type =='Submarine' or 'Ship' then
    return gKH.Unit:isShipSubAtDock(u);
  elseif u.type == 'Facility' then
    return false;  --?? may have to reconsider in future
  elseif u.type == 'Group' then
    return false;  --?? may have to reconsider in future
  end
  return false;
end




-- Function to replace an individual single unit with another unit, of similar type is assumed though in theory it doesn't have to be.
-- src: source unit as a unit wrapper
-- tdest: table object that contains at least {dbid=xxx,loadoutid=xxx} , if it contains more some of those will be overridden
-- reuseGUID: default is false if missing.  If true the function will attempt to reuse the source's guid in the new object.
-- reuseGUID: **note** this is risky because the old unit must be deleted first before the attempt to create the new unit,
-- reuseGUID: so upon some sort of failure of the unit creation, you will will be left with missing old unit and no new unit.
--
-- Returns 2 values, true|false for overall success|failure, and on Success the new unit's wrapper, on fail, nil;
-- Note it can return false and a unit wrapper, such that new unit was replaced but there were other more minor problems.
function gKH.Side:ReplaceSingleUnit(src,tdest,reuseGUID)
  local fn = "gKH.Side:ReplaceSingleUnit(): ";
  if reuseGUID == nil then reuseGUID = false end;
  local retval = false;
  local newUnit;
  local tmp = {};
  if src~=nil and tdest ~=nil then
    if reuseGUID then 
      tdest.guid = src.guid; -- if trying to reuse guid then set a guid property in the table to old one. otherwise it should be missing.
    end --reuse guid
    tdest.type = src.type;
    tdest.name = src.name; --swap in the name
    tdest.side = src.side; --swap in the side
    tdest.heading = math.floor(src.heading);
    tdest.latitude = src.latitude;
    tdest.longitude = src.longitude;
    
    local hadBase, hadGroup, hadMission,hadCourse = false,false,false,false;
    local isAtABase = gKH.Unit:isUnitInAnyBase(src) ;
    
    if (src.base ~= nil) and src.base.guid ~=nil then
      hadBase = true;
      tmp.baseguid = src.base.guid; --store case we need it. addunit will ignore entries not named what it's looking for.
      tmp.basename = src.base.name; --store for logging.
    end
    if (src.group ~=nil) and src.group.name ~= nil then
      hadGroup = true;
      tmp.groupname = src.group.name;
      tmp.groupguid = src.group.guid;
    end
    if (src.mission ~=nil) and src.mission.name ~= nil then
      hadMission = true;
      tmp.missionname = src.mission.name;
      tmp.missionguid = src.mission.guid;
    end
    if (src.course ~=nil) and #src.course > 0 then
      hadCourse = true;
      tmp.coursetable = {}
      for k,v in pairs(src.course) do --copy table.
        table.insert(tmp.coursetable,v);
      end
    end

    
    --do different things based on type
    local updTable = {}
    updTable.autodetectable = src.autodetectable;
    updTable.proficiency = src.proficiency;

    --make adjustments for setunit calls.
    if isAtABase == false then
      updTable.heading = src.heading;
      updTable.latitude = src.latitude;
      updTable.longitude = src.longitude;
      updTable.altitude = src.altitude;
      --updTable.throttle = src.throttle;
    end
    if src.type == 'Aircraft' then
      tdest.altitude = src.altitude;
    elseif src.type == 'Submarine' then  --we need to set depth
      updTable.depth = src.altitude;
      updTable.moveto=false;
    elseif src.type == 'Ship' then
      updTable.depth = 0;
      updTable.altitude = 0;
    end
    --critical fix up before addunit call for docked ships and subs that have altitude > 0 
    if (isAtABase == true) and (src.type == 'Ship' or src.type == 'Submarine') then
      tdest.latitude = 0.5 --water off africa
      tdest.longitude = 0.5 --water off africa
      print(fn.. "Ship or Sub was in a port, adjusting lat and lon for ocean position during create.");   
    end

    local retval1,setUnit;
    if reuseGUID ==true then
      print(fn .. "Deleting original unit because guid-reuse is enabled. upon further failure original unit will be gone. " .. src.guid .. " - " .. src.name);
      src:delete();  --delete the original. we have to do it before trying to create the new one if using the same guid.
    end
    print( fn .. "debug: values going to AddUnit():")
    print(tdest);

    retval1,newUnit = pcall(ScenEdit_AddUnit,tdest);
    if (retval1 ~=nil) and retval1 ==true and newUnit ~=nil then --success new unit created.
      print(fn .. "Successfully created new unit. newguid: " .. tostring(newUnit.guid));

      --wipe old unit, we do this to prereserve space in tight loops on bases.
      --could leave this up the calling function but given we have to delete it above for guid
      --we should probably be the one to do it for success case.
      if reuseGUID == false then
        print(fn .. "Removing old unit: " .. tostring(src.guid) .. " - " .. tostring(src.name));
        src:delete();
      end

      --Now handle post creation fixup.
      updTable.guid = newUnit.guid; --insert new units guid.
      
      --debuginfo
      if isAtABase == false then  print( fn.. "debug: values potentially going to SetUnit():");  print(updTable); end

      
      if isAtABase == false then  --update unit 
        retval1,setUnit = pcall(ScenEdit_SetUnit,updTable);
        if (retval1 ~=nil) and retval1 == true and setUnit ~= nil then
          print(fn .. "Successfully updated new unit.");
          newUnit = setUnit; --update the obj with new one returned
          retval = true;
        else
          print(fn .. "New unit update call failed.");
          retval = false;
        end
      else  --host unit.
        if (newUnit ~=nil) and newUnit.type ~= 'Facility' then
          retval1 = pcall(ScenEdit_HostUnitToParent,{HostedUnitNameOrID = newUnit.guid, SelectedHostNameOrID = tmp.baseguid});
          if (retval1 ~=nil) and retval1 == true then
            print(fn .. "Successfully Hosted new unit to base: " .. tostring(tmp.basename));
            retval = true;
          else
            print(fn .. "Failed to host new unit to base: " .. tostring(tmp.basename));
            retval = false;
          end
        elseif (newUnit ~=nil) and hadBase == false then
          print(fn .. "debug: Unit is facility or other type skipping rehost process");
          retval = true;
        end
      end

      --Assign group if it had one.
      if hadGroup then
        newUnit.group = tmp.groupname;
        print(fn.. "Assigned new unit to old group of " .. tostring(tmp.groupname));
      end

      --Assign old Mission if it had one  DOES NOT HANDLE ESCORT SITUATION, don't think we can actually.
      if hadMission then
        retval1 = pcall(ScenEdit_AssignUnitToMission, newUnit.guid,tmp.missionguid,false);
        if (retval1 ~=nil) and retval1 == true then
          print(fn .. "Successfully assigned new unit to old mission of " .. tostring(tmp.missionname));
        else
          print(fn .. "Failed! to assign new unit to old mission of " .. tostring(tmp.missionname));
        end
      end

      --inject old course if had one.
      if hadCourse then
        newUnit.course = tmp.coursetable;
        print(fn .. "Updated course table on new unit to that of old unit." );
        --ScenEdit_SetUnit({guid=newUnit.guid, course=tdest.coursetable});
      end
      
      
      print(fn .. " Completed.");

     else
      print(fn .. "Error: Could not generate new unit for source unit: " .. tostring(src.guid) .. " - " .. tostring(src.name))
      retval = false;
    end
  else
    print(fn .. "Error: src or tdest parameters were nil or invalid. exiting.");
  end

  return retval,newUnit;
end

--Function to compare to do unit selection matching for 
--returns true on match, false on no match.
--notes: internal\specific function to gKH.Side:ReplaceUnitsByDBID not for extneral use.
function gKH.Side:internalReplaceUnits_IsMatch(u,dbid,loadoutid,bflag)
  if bflag == nil then bflag =false; end
  if bflag then
    if (u.dbid == dbid) and u.loadoutdbid == loadoutid then return true; end
  else
    if u.dbid == dbid then return true; end
  end 
  return false;
end


-- Function to replace all units of one dbid with another.
-- Enumerates though a given side, filtered by generic unit type looking for a match, for each match calls
-- replacement process.
-- theside: the string side name to operate on, only validation is that it's a string and non-nil. special side "_ALL_" not implimented yet.
-- thetype: the string unit type code 'Aircraft','Submarine','Ship','Facility' etc. Validation is only it non-nil and string.
-- thedbid: the database id# for the unit being replaced.  must exist and be a number.
-- theloadoutid:  The loadoutid you watch to restrict matching on, or 0 == none
-- tParams: the table of parameters that you would normally feed to ScenEdit_addunit(), only thing required is {dbid=###,loadoutid=###}
-- themode: either nil\empty or one of "Normal" or "KeepGUIDS" Not fully tested yet, exists for future enhancement purposes.
-- themode: default if left empty is normal, replacement units get new guids, keep guids attempts to reuse old unit guids.
--
-- Returns false on critical error, otherwise true if the process completes (even if there are unsuccessful replacements.)
function gKH.Side:ReplaceUnitsByDBID(theside, thetype,thedbid,theloadoutid, tParams,themode)
  --local retval = false;
  local fn = "gKH.Side.ReplaceUnitsByDBID(): "
  local bFilterOnLoadout = false;
  if ((themode ==nil) or type(themode) ~= 'string') or string.len(themode) < 2 then
    themode = 'Normal'; --Normal ,AirBase,Port,Group ??
  end
  if (theside ==nil) or type(theside) ~= 'string' then 
    print(fn .. "theside is missing or nil or not a string. should be the side you want checked."); return false;
  end
  if (thetype ==nil) or type(thetype) ~= 'string' then 
    print(fn .. "thetype is missing or nil or not a string. should be a unit type such as Aircraft,Submarine etc."); return false;
  end
  if (thedbid ==nil) or type(thedbid) ~= 'number' then 
    print(fn .. "thedbid is missing or nil or not a number. should be the dbid of what you want to replace"); return false;
  end
  if (theloadoutid ==nil) or type(theloadoutid) ~= 'number' then 
    print(fn .. "theloadoutid is missing or nil or not a string. should be a loadout dbid# or 0 to match all."); return false;
  end
  if (tParams ==nil) or type(tParams) ~= 'table' then 
    print(fn .. "tParams is missing or nil or not a table. should be the table {} you would feed to addunit."); return false;
  end
  if (tParams.dbid ==nil) or type(tParams.dbid) ~= 'number' then 
    print(fn .. "The table you supplied for the replacement unit did not contain a numeric dbid entry. aborting."); return false;
  end
  if ((theloadoutid ~=nil) and type(theloadoutid) == 'number') and theloadoutid > 0 then
    bFilterOnLoadout = true;
  end

  local s; --side
  local ulist; --table of units.
  local u; --hold temp unit wrapper object.
  local nu; --hold temp new unit wrapper object.

  if theside == '_ALL_' then 
    --SE_GetUnit({side=theside,name=unitname})
  else
    s = VP_GetSide({side=theside});
    ulist = s:unitsBy(thetype);
    local bret = false --call return value
    local count,scount = 0,0;
    local sname = "";
    for k,v in pairs(ulist) do
      bret,u = pcall(ScenEdit_GetUnit,{guid=v.guid}); --suppress errors.
      if bret==true and u ~= nil then
        if gKH.Side:internalReplaceUnits_IsMatch(u,thedbid,theloadoutid,bFilterOnLoadout) then
          count = count + 1;
          sname = u.name; --make a copy as unit will be deleted.
          if themode == "Normal" then
            bret,nu  = gKH.Side:ReplaceSingleUnit(u,tParams,false);
          elseif themode=="KeepGUID" then
            bret,nu  = gKH.Side:ReplaceSingleUnit(u,tParams,true);
          end
          if bret==true and nu~=nil then
            print(string.format("%s Replaced unit %s -  %s with dbid# %s with new #%s unit %s ",
            fn,tostring(v.guid),tostring(sname),tostring(tParams.dbid),tostring(tParams.dbid),tostring(nu.guid)));
            scount = scount + 1;
            --ScenEdit_DeleteUnit({guid=v.guid});
          elseif (bret==false and nu ~= nil) and type(nu) ~= 'string' then
            print(string.format("%s Warning Created replacement unit %s -  %s with dbid# %s with new #%s unit %s - BUT failed to duplicate some values.",
            fn,tostring(v.guid),tostring(sname),tostring(tParams.dbid),tostring(tParams.dbid),tostring(nu.guid)));
            scount = scount + 1;
          else 
            print(fn .. "FAILED to replace unit " .. tostring(v.guid) .. " - " .. tostring(sname));
          end
          nu = nil;
        end
      else
        print(fn .. "Could not obtain unit object for guid " .. tostring(v.guid) .. " skipping.")
      end
      u = nil; bret =false;sname = "";
    end
    print("Units matched count: " .. count)
    print("Units replaced count: " .. scount)
  end
  collectgarbage();
  return true;
end
---- end localized version---
