|  |  | 1 |  | using LGDXRobotCloud.API.Exceptions; | 
|  |  | 2 |  | using LGDXRobotCloud.API.Services.Administration; | 
|  |  | 3 |  | using LGDXRobotCloud.Data.DbContexts; | 
|  |  | 4 |  | using LGDXRobotCloud.Data.Entities; | 
|  |  | 5 |  | using LGDXRobotCloud.Data.Models.Business.Administration; | 
|  |  | 6 |  | using LGDXRobotCloud.Data.Models.Business.Navigation; | 
|  |  | 7 |  | using LGDXRobotCloud.Utilities.Enums; | 
|  |  | 8 |  | using Microsoft.EntityFrameworkCore; | 
|  |  | 9 |  | using Microsoft.Extensions.Caching.Memory; | 
|  |  | 10 |  |  | 
|  |  | 11 |  | namespace LGDXRobotCloud.API.Services.Navigation; | 
|  |  | 12 |  |  | 
|  |  | 13 |  | public record WaypointsTraffic | 
|  |  | 14 |  | { | 
|  | 0 | 15 |  |   public Dictionary<int, Waypoint> Waypoints { get; set; } = null!; | 
|  | 0 | 16 |  |   public Dictionary<int, HashSet<int>> WaypointTraffics { get; set; } = null!; | 
|  |  | 17 |  | } | 
|  |  | 18 |  |  | 
|  |  | 19 |  | public interface IMapEditorService | 
|  |  | 20 |  | { | 
|  |  | 21 |  |   Task<MapEditorBusinessModel> GetMapAsync(int realmId); | 
|  |  | 22 |  |   Task<bool> UpdateMapAsync(int realmId, MapEditorUpdateBusinessModel MapEditUpdateBusinessModel); | 
|  |  | 23 |  |  | 
|  |  | 24 |  |   Task<WaypointsTraffic> GetWaypointTrafficAsync(int realmId); | 
|  |  | 25 |  | } | 
|  |  | 26 |  |  | 
|  |  | 27 |  | public class MapEditorService( | 
|  |  | 28 |  |     IActivityLogService activityLogService, | 
|  |  | 29 |  |     IMemoryCache memoryCache, | 
|  |  | 30 |  |     LgdxContext context | 
|  |  | 31 |  |   ) : IMapEditorService | 
|  |  | 32 |  | { | 
|  |  | 33 |  |   private readonly IActivityLogService _activityLogService = activityLogService ?? throw new ArgumentNullException(nameo | 
|  |  | 34 |  |   private readonly IMemoryCache _memoryCache = memoryCache ?? throw new ArgumentNullException(nameof(memoryCache)); | 
|  |  | 35 |  |   private readonly LgdxContext _context = context ?? throw new ArgumentNullException(nameof(context)); | 
|  |  | 36 |  |  | 
|  |  | 37 |  |  | 
|  |  | 38 |  |   public async Task<MapEditorBusinessModel> GetMapAsync(int realmId) | 
|  |  | 39 |  |   { | 
|  |  | 40 |  |     // Check if realm exists | 
|  |  | 41 |  |     var realm = await _context.Realms.AsNoTracking() | 
|  |  | 42 |  |       .Where(r => r.Id == realmId) | 
|  |  | 43 |  |       .FirstOrDefaultAsync() | 
|  |  | 44 |  |         ?? throw new LgdxNotFound404Exception(); | 
|  |  | 45 |  |  | 
|  |  | 46 |  |     var waypoints = await _context.Waypoints.AsNoTracking() | 
|  |  | 47 |  |       .Where(w => w.RealmId == realmId) | 
|  |  | 48 |  |       .Select(w => new WaypointListBusinessModel | 
|  |  | 49 |  |       { | 
|  |  | 50 |  |         Id = w.Id, | 
|  |  | 51 |  |         Name = w.Name, | 
|  |  | 52 |  |         RealmId = w.RealmId, | 
|  |  | 53 |  |         RealmName = w.Realm.Name, | 
|  |  | 54 |  |         X = w.X, | 
|  |  | 55 |  |         Y = w.Y, | 
|  |  | 56 |  |         Rotation = w.Rotation, | 
|  |  | 57 |  |       }) | 
|  |  | 58 |  |       .ToListAsync(); | 
|  |  | 59 |  |     var waypointTraffics = await _context.WaypointTraffics.AsNoTracking() | 
|  |  | 60 |  |       .Where(w => w.RealmId == realmId) | 
|  |  | 61 |  |       .Select(w => new WaypointTrafficBusinessModel | 
|  |  | 62 |  |       { | 
|  |  | 63 |  |         Id = w.Id, | 
|  |  | 64 |  |         WaypointFromId = w.WaypointFromId, | 
|  |  | 65 |  |         WaypointToId = w.WaypointToId, | 
|  |  | 66 |  |       }) | 
|  |  | 67 |  |       .ToListAsync(); | 
|  |  | 68 |  |     return new MapEditorBusinessModel | 
|  |  | 69 |  |     { | 
|  |  | 70 |  |       Waypoints = waypoints, | 
|  |  | 71 |  |       WaypointTraffics = waypointTraffics, | 
|  |  | 72 |  |     }; | 
|  |  | 73 |  |   } | 
|  |  | 74 |  |  | 
|  |  | 75 |  |   public async Task<bool> UpdateMapAsync(int realmId, MapEditorUpdateBusinessModel mapEditorUpdateBusinessModel) | 
|  |  | 76 |  |   { | 
|  |  | 77 |  |     _memoryCache.Remove($"MapEditorService_InternalWaypointsTraffic_{realmId}"); | 
|  |  | 78 |  |  | 
|  |  | 79 |  |     // Sort traffics | 
|  |  | 80 |  |     var inputWaypointTraffics = mapEditorUpdateBusinessModel.WaypointTraffics | 
|  |  | 81 |  |       .OrderBy(w => w.WaypointFromId) | 
|  |  | 82 |  |       .ThenBy(w => w.WaypointToId) | 
|  |  | 83 |  |       .ToList(); | 
|  |  | 84 |  |  | 
|  |  | 85 |  |     // Get all traffics | 
|  |  | 86 |  |     var databaseWaypointTraffics = await _context.WaypointTraffics.AsNoTracking() | 
|  |  | 87 |  |       .Where(w => w.RealmId == realmId) | 
|  |  | 88 |  |       .OrderBy(w => w.WaypointFromId) | 
|  |  | 89 |  |       .ThenBy(w => w.WaypointToId) | 
|  |  | 90 |  |       .ToListAsync(); | 
|  |  | 91 |  |  | 
|  |  | 92 |  |     // Get traffic to add and remove | 
|  |  | 93 |  |     List<WaypointTraffic> trafficsToAdd = []; | 
|  |  | 94 |  |     int i = 0; | 
|  |  | 95 |  |     int j = 0; | 
|  |  | 96 |  |     while (i < inputWaypointTraffics.Count && j < databaseWaypointTraffics.Count) | 
|  |  | 97 |  |     { | 
|  |  | 98 |  |       // Same traffic, remove from list | 
|  |  | 99 |  |       if (inputWaypointTraffics[i].WaypointFromId == databaseWaypointTraffics[j].WaypointFromId | 
|  |  | 100 |  |         && inputWaypointTraffics[i].WaypointToId == databaseWaypointTraffics[j].WaypointToId) | 
|  |  | 101 |  |       { | 
|  |  | 102 |  |         inputWaypointTraffics.RemoveAt(i); | 
|  |  | 103 |  |         databaseWaypointTraffics.RemoveAt(j); | 
|  |  | 104 |  |       } | 
|  |  | 105 |  |       else | 
|  |  | 106 |  |       { | 
|  |  | 107 |  |         if (inputWaypointTraffics[i].WaypointFromId > databaseWaypointTraffics[j].WaypointFromId | 
|  |  | 108 |  |           && inputWaypointTraffics[i].WaypointToId > databaseWaypointTraffics[j].WaypointToId) | 
|  |  | 109 |  |         { | 
|  |  | 110 |  |           j++; | 
|  |  | 111 |  |         } | 
|  |  | 112 |  |         else | 
|  |  | 113 |  |         { | 
|  |  | 114 |  |           i++; | 
|  |  | 115 |  |         } | 
|  |  | 116 |  |       } | 
|  |  | 117 |  |     } | 
|  |  | 118 |  |  | 
|  |  | 119 |  |     // inputWaypointTraffics contains the traffics to add | 
|  |  | 120 |  |     await _context.WaypointTraffics.AddRangeAsync(inputWaypointTraffics.Select(w => new WaypointTraffic | 
|  |  | 121 |  |     { | 
|  |  | 122 |  |       RealmId = realmId, | 
|  |  | 123 |  |       WaypointFromId = w.WaypointFromId, | 
|  |  | 124 |  |       WaypointToId = w.WaypointToId, | 
|  |  | 125 |  |     })); | 
|  |  | 126 |  |     // databaseWaypointTraffics contains the traffics to remove | 
|  |  | 127 |  |     _context.WaypointTraffics.RemoveRange(databaseWaypointTraffics); | 
|  |  | 128 |  |     await _context.SaveChangesAsync(); | 
|  |  | 129 |  |  | 
|  |  | 130 |  |     await _activityLogService.CreateActivityLogAsync(new ActivityLogCreateBusinessModel | 
|  |  | 131 |  |     { | 
|  |  | 132 |  |       EntityName = nameof(Realm), | 
|  |  | 133 |  |       EntityId = realmId.ToString(), | 
|  |  | 134 |  |       Action = ActivityAction.RealmTrafficUpdated, | 
|  |  | 135 |  |     }); | 
|  |  | 136 |  |  | 
|  |  | 137 |  |     return true; | 
|  |  | 138 |  |   } | 
|  |  | 139 |  |  | 
|  |  | 140 |  |   public async Task<WaypointsTraffic> GetWaypointTrafficAsync(int realmId) | 
|  |  | 141 |  |   { | 
|  |  | 142 |  |     if (_memoryCache.TryGetValue($"MapEditorService_InternalWaypointsTraffic_{realmId}", out WaypointsTraffic? t)) | 
|  |  | 143 |  |     { | 
|  |  | 144 |  |       return t ?? new(); | 
|  |  | 145 |  |     } | 
|  |  | 146 |  |  | 
|  |  | 147 |  |     var waypoints = await _context.Waypoints.AsNoTracking() | 
|  |  | 148 |  |       .Where(w => w.RealmId == realmId) | 
|  |  | 149 |  |       .ToListAsync(); | 
|  |  | 150 |  |     var waypointTraffics = await _context.WaypointTraffics.AsNoTracking() | 
|  |  | 151 |  |       .Where(w => w.RealmId == realmId) | 
|  |  | 152 |  |       .ToListAsync(); | 
|  |  | 153 |  |  | 
|  |  | 154 |  |     var waypointsDict = waypoints.ToDictionary(w => w.Id); | 
|  |  | 155 |  |     var waypointTrafficsDict = new Dictionary<int, HashSet<int>>(); | 
|  |  | 156 |  |     foreach (var traffic in waypointTraffics) | 
|  |  | 157 |  |     { | 
|  |  | 158 |  |       if (waypointTrafficsDict.TryGetValue(traffic.WaypointFromId, out HashSet<int>? neighbors)) | 
|  |  | 159 |  |       { | 
|  |  | 160 |  |         neighbors.Add(traffic.WaypointToId); | 
|  |  | 161 |  |         waypointTrafficsDict[traffic.WaypointFromId] = neighbors; | 
|  |  | 162 |  |       } | 
|  |  | 163 |  |       else | 
|  |  | 164 |  |       { | 
|  |  | 165 |  |         waypointTrafficsDict[traffic.WaypointFromId] = [traffic.WaypointToId]; | 
|  |  | 166 |  |       } | 
|  |  | 167 |  |     } | 
|  |  | 168 |  |  | 
|  |  | 169 |  |     var internalWaypointsTraffic = new WaypointsTraffic | 
|  |  | 170 |  |     { | 
|  |  | 171 |  |       Waypoints = waypointsDict, | 
|  |  | 172 |  |       WaypointTraffics = waypointTrafficsDict, | 
|  |  | 173 |  |     }; | 
|  |  | 174 |  |     _memoryCache.Set($"MapEditorService_InternalWaypointsTraffic_{realmId}", internalWaypointsTraffic); | 
|  |  | 175 |  |     return internalWaypointsTraffic; | 
|  |  | 176 |  |   } | 
|  |  | 177 |  | } |