function out = apply_map_event(arg1, arg2, arg3)
%APPLY_MAP_EVENT  Evento mappa a runtime: spawn di un ostacolo ellittico.
%
% Simula un oggetto che "cade dal cielo" (albero, masso) al tempo T_EVENT.
% DUE modi d'uso alternativi (stessi parametri, stessa logica):
%
% MODO 1 - blocco dedicato (funzione estrinseca, scrive nel BASE workspace):
%     fired = apply_map_event(t)
%   da un MATLAB Function block nel top model, con in ingresso il Clock:
%     function fired = MapEventInjector(t)
%     %#codegen
%     coder.extrinsic('apply_map_event');
%     fired = 0;
%     fired = apply_map_event(t);
%
% MODO 2 - nessun blocco nuovo (chiamata da dentro swarm_planner_helper2):
%     map = apply_map_event(map, scenario, t_sim)
%   due righe da incollare in helper2 PRIMA dell'overlay celle unknown:
%     t_sim = double(step_counter) * scenario.sim.dtastar;
%     map   = apply_map_event(map, scenario, t_sim);
%   In questo modo muta la COPIA LOCALE di map che helper2 riscrive lui
%   stesso nel base workspace a fine chiamata (assignin riga ~357).
%   NON usare il MODO 1 da dentro helper2: helper2 tiene una copia locale
%   di map letta a inizio chiamata e la riscrive alla fine, quindi una
%   scrittura diretta sul base workspace a meta' funzione verrebbe
%   sovrascritta (persa) dal suo assignin finale.
%
% Cosa aggiorna (e perchè):
%   map.obstacle / map.traversable  -> ray-check follower + brake + planner
%   map.semantic_class              -> coerenza semantica (e visual, se allineata)
%   map.mu / map.mu_base = -1       -> convenzione "ostacolo" di generate_terrain_map
%   map.cost_all = cost_obstacle    -> FONDAMENTALE: la cost map e' precalcolata
%                                      una volta sola in init_swarm_scenario;
%                                      senza questo override A* ci passerebbe sopra
%   map.known (opzionale)           -> se MAKE_KNOWN=true il swarm "vede" subito
%                                      l'evento; se false lo scopre col raggio
%                                      visivo (piu' realistico). NB: le celle gia'
%                                      scoperte (es. tra leader e follower) reagiscono
%                                      comunque subito, perche' known=true e' gia' loro.
%   map_truth (se esiste)           -> coerenza con la mappa-verita'
%
% Cosa NON aggiorna (volutamente):
%   - le LUT del plant nei model workspace (mu/slope sotto le ruote): vengono
%     copiate una sola volta in demo_swarm_phase4 e Simulink le blocca durante
%     la run. Irrilevante per un ostacolo da EVITARE; non usare questo
%     meccanismo per eventi di attrito (chiazza di ghiaccio) senza rivedere
%     anche il plant.
%   - il gradiente semantico di build_cost_map: l'override a cost_obstacle
%     domina comunque qualsiasi blending.
%
% Simulation-only, non codegen (gira interpretata come extrinsic).

%% ------------------- PARAMETRI EVENTO (modifica qui) --------------------
T_EVENT    = 15.0;     % [s]   istante in cui "cade" l'ostacolo
X0         = 115.0;    % [m]   centro ellisse, x
Y0         = 120.0;     % [m]   centro ellisse, y
SEMI_A     = 18.0;      % [m]   semiasse lungo
SEMI_B     = 4.0;      % [m]   semiasse corto
THETA_DEG  = -30.0;     % [deg] orientamento ellisse (antiorario da x)
CLASS_ID   = 8;        % 8 = obstacle rigido (1e6); 7 = vegetation_obstacle
MAKE_KNOWN = false;    % false (consigliato): l'ostacolo e' percepito SUBITO solo dove
                       %   la mappa e' GIA' known (celle gia' scoperte: li' known=true
                       %   c'e' gia', e obstacle+cost aggiornati agiscono al replan).
                       %   Le celle unknown dell'ellisse restano unknown: il costo vero
                       %   (1e6) resta mascherato dall'overlay cost_unknown e il brake
                       %   non si arma; verranno scoperte normalmente col raggio visivo.
                       % true: rivela l'intera ellisse (known=true ovunque), come se
                       %   tutti "vedessero cadere" l'oggetto anche in zone mai esplorate.
%% ------------------------------------------------------------------------

persistent applied t_prev
if isempty(applied), applied = false; t_prev = -inf; end

%% Selezione modo
base_mode = (nargin == 1);
if base_mode
    t_now = arg1;                 % MODO 1: fired = apply_map_event(t)
else
    map      = arg1;              % MODO 2: map = apply_map_event(map, scenario, t)
    scenario = arg2;
    t_now    = arg3;
end

% reset automatico a inizio di una nuova run (il tempo riparte da zero)
if t_now < t_prev
    applied = false;
    % pulizia del record evento della run precedente (per il replay)
    evalin('base', 'clear map_event_t map_event_rgb_pre');
end
t_prev = t_now;

% uscita di default
if base_mode
    out = double(applied);
else
    out = map;
end
if applied || t_now < T_EVENT
    return;
end

%% Recupero dati dal base workspace (solo MODO 1; stesso guard di obstacle_brake_from_base)
if base_mode
    if ~evalin('base', 'exist(''map'',''var'') && exist(''scenario'',''var'')')
        return;
    end
    map      = evalin('base', 'map');
    scenario = evalin('base', 'scenario');
end

if ~isfield(map, 'X') || ~isfield(map, 'obstacle')
    return;
end

%% Maschera ellittica sulla griglia (map.X/map.Y sono [ny x nx], meshgrid)
th = THETA_DEG * pi/180;
dx = map.X - X0;
dy = map.Y - Y0;
xr = ( dx*cos(th) + dy*sin(th)) / max(SEMI_A, eps);
yr = (-dx*sin(th) + dy*cos(th)) / max(SEMI_B, eps);
E  = (xr.^2 + yr.^2) <= 1;

if ~any(E(:))
    warning('apply_map_event: ellisse fuori mappa, nessuna cella modificata.');
    applied = true;   % evita di riprovare ogni step
    if base_mode, out = 1; end
    return;
end

%% 0. Record dell'evento per il replay (sfondo pre-evento + istante)
%     visualize_swarm_phase4 commuta tra due sfondi statici a t_event:
%     niente storia della mappa per ogni istante, costo O(1).
if isfield(map, 'semantic_rgb') && ~isempty(map.semantic_rgb)
    assignin('base', 'map_event_rgb_pre', map.semantic_rgb);
end
assignin('base', 'map_event_t', t_now);

%% 1. Verita' fisica/logica
map.obstacle(E)    = true;
map.traversable(E) = false;
if isfield(map, 'semantic_class')
    map.semantic_class(E) = uint8(CLASS_ID);
end
mu_cls = 0.0;
if isfield(map, 'classes') && numel(map.classes) >= CLASS_ID
    mu_cls = map.classes(CLASS_ID).mu; %#ok<NASGU> % info; sotto usiamo la convenzione -1
end
if isfield(map, 'mu'),      map.mu(E)      = -1; end   % convenzione: -1 dove ostacolo
if isfield(map, 'mu_base'), map.mu_base(E) = -1; end

%% 2. Costo: la cost map e' PRECALCOLATA in init -> override manuale
if isfield(map, 'cost_all')
    map.cost_all(E) = scenario.planning.cost_obstacle;   % 1e6
end

%% 3. Percezione (scelta di design)
if MAKE_KNOWN && isfield(map, 'known')
    map.known(E) = true;
end

%% 4. Visual: patch del background semantico solo se allineato alla griglia
if isfield(map, 'semantic_rgb') && ~isempty(map.semantic_rgb) && ...
        size(map.semantic_rgb,1) == size(map.obstacle,1) && ...
        size(map.semantic_rgb,2) == size(map.obstacle,2) && ...
        isfield(map, 'classes') && numel(map.classes) >= CLASS_ID
    col = map.classes(CLASS_ID).color_rgb;
    for ch = 1:3
        plane = map.semantic_rgb(:,:,ch);
        plane(E) = col(ch);
        map.semantic_rgb(:,:,ch) = plane;
    end
end

% MODO 1: riscrive subito nel base workspace.
% MODO 2: NON scrive (la copia locale viene riscritta da helper2 a fine chiamata).
if base_mode
    assignin('base', 'map', map);
end

%% 5. Coerenza con map_truth, se presente (in entrambi i modi via base workspace:
%    anche helper2 gira lato MATLAB, quindi evalin/assignin sono leciti qui)
if evalin('base', 'exist(''map_truth'',''var'')')
    mt = evalin('base', 'map_truth');
    if isfield(mt, 'obstacle') && isequal(size(mt.obstacle), size(E))
        mt.obstacle(E)    = true;
        if isfield(mt, 'traversable'), mt.traversable(E) = false; end
        if isfield(mt, 'mu'),          mt.mu(E)          = -1;    end
        assignin('base', 'map_truth', mt);
    end
end

applied = true;
if base_mode
    out = 1;
else
    out = map;
end
fprintf('[apply_map_event] t = %.2f s: ostacolo ellittico (%.0f x %.0f m) in (%.1f, %.1f), %d celle, known=%d.\n', ...
    t_now, 2*SEMI_A, 2*SEMI_B, X0, Y0, nnz(E), MAKE_KNOWN);

end
