using System; using System.Collections.Generic; using System.Linq; using System.Threading; using CounterDrone.Core.Models; using CounterDrone.Core.Repository; namespace CounterDrone.Core.Services { public class ScenarioService : IScenarioService { private readonly ScenarioRepository _scenarioRepo; private readonly CombatSceneRepository _sceneRepo; private readonly ControlZoneRepository _zoneRepo; private readonly ScenarioDroneRepository _droneRepo; private readonly ScenarioUnitRepository _unitRepo; private readonly CloudDispersalRepository _cloudRepo; private readonly RoutePlanRepository _routeRepo; private readonly WaypointRepository _waypointRepo; public ScenarioService( ScenarioRepository scenarioRepo, CombatSceneRepository sceneRepo, ControlZoneRepository zoneRepo, ScenarioDroneRepository targetRepo, ScenarioUnitRepository equipRepo, CloudDispersalRepository cloudRepo, RoutePlanRepository routeRepo, WaypointRepository waypointRepo) { _scenarioRepo = scenarioRepo; _sceneRepo = sceneRepo; _zoneRepo = zoneRepo; _droneRepo = targetRepo; _unitRepo = equipRepo; _cloudRepo = cloudRepo; _routeRepo = routeRepo; _waypointRepo = waypointRepo; } // ========== scenario CRUD ========== public Scenario CreateScenario(string name, string ScenarioNumber) { if (string.IsNullOrWhiteSpace(name)) throw new ArgumentException("任务名称不能为空"); if (string.IsNullOrWhiteSpace(ScenarioNumber)) ScenarioNumber = GenerateScenarioNumber(); var existing = _scenarioRepo.GetByScenarioNumber(ScenarioNumber); if (existing != null) throw new ArgumentException($"任务编号 {ScenarioNumber} 已存在"); var scenario = new Scenario { Name = name, ScenarioNumber = ScenarioNumber, Status = (int)ScenarioStatus.Configuring, CurrentStep = 1, }; _scenarioRepo.Insert(scenario); return scenario; } public void DeleteScenario(string id) { _scenarioRepo.Delete(id); } public PagedResult SearchScenarios(string keyword, string? dateFrom, string? dateTo, int page, int pageSize) { if (page < 1) page = 1; if (pageSize < 1) pageSize = 10; var offset = (page - 1) * pageSize; var items = _scenarioRepo.Search(keyword, dateFrom, dateTo, offset, pageSize, out var total); return new PagedResult { Items = items, TotalCount = total, Page = page, PageSize = pageSize, }; } public ScenarioConfig? GetScenarioDetail(string id) { var scenario = _scenarioRepo.GetById(id); if (scenario == null) return null; return new ScenarioConfig { Info = scenario, Scene = _sceneRepo.GetById(id) ?? new CombatScene { ScenarioId = id }, ControlZones = _zoneRepo.GetByScenarioId(id), Drones = _droneRepo.GetByScenarioId(id), Units = _unitRepo.GetByScenarioId(id), Cloud = _cloudRepo.GetById(id) ?? new CloudDispersal { ScenarioId = id }, Routes = _routeRepo.GetByScenarioId(id), WaypointGroups = _waypointRepo.GetByScenarioId(id) .GroupBy(w => w.WaveId) .ToDictionary(g => g.Key, g => g.OrderBy(w => w.OrderIndex).ToList()), }; } // ========== Step Save ========== public void SaveScene(string scenarioId, CombatScene scene) { scene.ScenarioId = scenarioId; var existing = _sceneRepo.GetById(scenarioId); if (existing != null) _sceneRepo.Update(scene); else _sceneRepo.Insert(scene); TouchScenario(scenarioId); } public void SaveControlZones(string scenarioId, List zones) { _zoneRepo.DeleteByScenarioId(scenarioId); for (int i = 0; i < zones.Count; i++) { zones[i].ScenarioId = scenarioId; zones[i].OrderIndex = i; } _zoneRepo.InsertAll(zones); TouchScenario(scenarioId); } public ScenarioDrone SaveScenarioDrone(string scenarioId, ScenarioDrone target) { target.ScenarioId = scenarioId; if (string.IsNullOrEmpty(target.Id)) target.Id = Guid.NewGuid().ToString(); var existing = _droneRepo.GetById(target.Id); if (existing != null) _droneRepo.Update(target); else _droneRepo.Insert(target); TouchScenario(scenarioId); return target; } public void SaveDeployment(string scenarioId, List equips) { _unitRepo.DeleteByScenarioId(scenarioId); foreach (var e in equips) { e.ScenarioId = scenarioId; if (string.IsNullOrEmpty(e.Id)) e.Id = Guid.NewGuid().ToString(); } _unitRepo.InsertAll(equips); TouchScenario(scenarioId); } public void AddDetection(string scenarioId, ScenarioUnit detection) { detection.ScenarioId = scenarioId; detection.EquipmentRole = (int)EquipmentRole.Detection; if (string.IsNullOrEmpty(detection.Id)) detection.Id = Guid.NewGuid().ToString(); _unitRepo.Insert(detection); TouchScenario(scenarioId); } public void DeleteDetection(string detectionId) { _unitRepo.Delete(detectionId); } public List GetDetections(string scenarioId) { return _unitRepo.GetByScenarioIdAndRole(scenarioId, (int)EquipmentRole.Detection); } public void SaveCloudDispersal(string scenarioId, CloudDispersal cloud) { cloud.ScenarioId = scenarioId; var existing = _cloudRepo.GetById(scenarioId); if (existing != null) _cloudRepo.Update(cloud); else _cloudRepo.Insert(cloud); TouchScenario(scenarioId); } public void SaveRoute(string scenarioId, string waveId, RoutePlan route, List waypoints) { route.ScenarioId = scenarioId; route.WaveId = waveId; var existing = _routeRepo.GetByScenarioAndWave(scenarioId, waveId); if (existing != null) { route.Id = existing.Id; _routeRepo.Update(route); } else _routeRepo.Insert(route); // 删除该批次的旧航路点,插入新的 var oldWps = _waypointRepo.GetByScenarioAndWave(scenarioId, waveId); foreach (var w in oldWps) _waypointRepo.Delete(w.Id); for (int i = 0; i < waypoints.Count; i++) { waypoints[i].ScenarioId = scenarioId; waypoints[i].WaveId = waveId; waypoints[i].OrderIndex = i; if (string.IsNullOrEmpty(waypoints[i].Id)) waypoints[i].Id = Guid.NewGuid().ToString(); } _waypointRepo.InsertAll(waypoints); TouchScenario(scenarioId); } public void UpdateStep(string scenarioId, int step) { var scenario = _scenarioRepo.GetById(scenarioId); if (scenario == null) return; if (step < 1) step = 1; if (step > 5) step = 5; scenario.CurrentStep = step; _scenarioRepo.Update(scenario); } public EnumMetadata GetEnums() { return new EnumMetadata { DroneType = EnumToDict(), PowerType = EnumToDict(), PlatformType = EnumToDict(), AerosolType = EnumToDict(), WeatherType = EnumToDict(), WindDirection = EnumToDict(), SceneType = EnumToDict(), FormationMode = EnumToDict(), TriggerMode = EnumToDict(), ReleaseMode = EnumToDict(), EquipmentRole = EnumToDict(), }; } private static Dictionary EnumToDict() where T : Enum { var dict = new Dictionary(); foreach (T v in Enum.GetValues(typeof(T))) dict[v.ToString()] = Convert.ToInt32(v); return dict; } // ========== Scenario CRUD ========== public Scenario UpdateScenario(Scenario scenario) { var existing = _scenarioRepo.GetById(scenario.Id); if (existing == null) throw new ArgumentException($"想定 {scenario.Id} 不存在"); scenario.UpdatedAt = DateTime.UtcNow.ToString("o"); _scenarioRepo.Update(scenario); return scenario; } // ========== CombatScene ========== public CombatScene GetScene(string scenarioId) { return _sceneRepo.GetById(scenarioId) ?? new CombatScene { ScenarioId = scenarioId }; } // ========== ControlZone ========== public List GetControlZones(string scenarioId) => _zoneRepo.GetByScenarioId(scenarioId); public void DeleteControlZone(string id) => _zoneRepo.Delete(id); // ========== ScenarioDrone ========== public ScenarioDrone AddScenarioDrone(string scenarioId, ScenarioDrone drone) => SaveScenarioDrone(scenarioId, drone); public void UpdateScenarioDrone(ScenarioDrone drone) { _droneRepo.Update(drone); TouchScenario(drone.ScenarioId); } public void DeleteScenarioDrone(string id) => _droneRepo.Delete(id); public List GetScenarioDrones(string scenarioId) => _droneRepo.GetByScenarioId(scenarioId); // ========== ScenarioUnit ========== public ScenarioUnit AddDeploymentUnit(string scenarioId, ScenarioUnit unit) { unit.ScenarioId = scenarioId; if (string.IsNullOrEmpty(unit.Id)) unit.Id = Guid.NewGuid().ToString(); _unitRepo.Insert(unit); TouchScenario(scenarioId); return unit; } public void UpdateDeploymentUnit(ScenarioUnit unit) => _unitRepo.Update(unit); public void DeleteDeploymentUnit(string id) => _unitRepo.Delete(id); public List GetDeploymentUnits(string scenarioId) => _unitRepo.GetByScenarioId(scenarioId); // ========== CloudDispersal ========== public CloudDispersal GetCloudDispersal(string scenarioId) => _cloudRepo.GetById(scenarioId) ?? new CloudDispersal { ScenarioId = scenarioId }; public void DeleteCloudDispersal(string scenarioId) => _cloudRepo.Delete(scenarioId); // ========== Route & Waypoint ========== public List GetRoutes(string scenarioId) => _routeRepo.GetByScenarioId(scenarioId); public void DeleteRoute(string id) { var r = _routeRepo.GetById(id); if (r == null) return; var wps = _waypointRepo.GetByScenarioAndWave(r.ScenarioId, r.WaveId); foreach (var w in wps) _waypointRepo.Delete(w.Id); _routeRepo.Delete(id); } public List GetWaypoints(string scenarioId) => _waypointRepo.GetByScenarioId(scenarioId); public List GetWaypointsByWave(string scenarioId, string waveId) => _waypointRepo.GetByScenarioAndWave(scenarioId, waveId); public void DeleteWaypoint(string id) => _waypointRepo.Delete(id); // ========== Helpers ========== private void TouchScenario(string scenarioId) { var scenario = _scenarioRepo.GetById(scenarioId); if (scenario != null) { scenario.UpdatedAt = DateTime.UtcNow.ToString("o"); _scenarioRepo.Update(scenario); } } private static int _counter; public static string GenerateScenarioNumber() { var now = DateTime.Now; var datePart = now.ToString("yyyyMMdd"); var seq = Interlocked.Increment(ref _counter); return $"SIM-{datePart}-{seq:D3}"; } } }