%% =========================================================================
%  DIAGRAMMA DI BIFORCAZIONE per il sistema ODE (viroterapia oncolitica)
%  - Considera TUTTI e 3 gli equilibri:
%       E1: assenza di tumore           
%       E2: solo tumore non infetto      
%       E3: infezione attiva (coesistenza) 
%
%  - Classificazione stabilita' : nodo / fuoco / sella (indici 1 o 2) + casi critici
%  - Rilevamento Hopf via criteri di Routh-Hurwitz per sistemi 3x3
%  - Marcatori: HB (Hopf), TC (transcritica / soglie d'invasione)
%  - Messaggi, etichette e commenti interamente in italiano
%
%  ATTENZIONE:
%  * Personalizzare i parametri di base e gli intervalli di scansione in funzione
%    della tesi. I valori di default sono ragionevoli e servono come template.
%  * Il codice è strutturato per essere robusto ai casi limite; usare 'tol' con cura.

%% =========================================================================

clear; close all; clc;

%% -------------------- PARAMETRI DI BASE --------------------
par.p    = 1.87e-2;    % proliferazione tumore (h^-1)
par.q    = 8.34e-3;    % clearance cellule infette (h^-1)
par.q_z  = 7.50e-3;    % decay cellule immunitarie (h^-1)
par.S_z  = 5.00e-2;    % sorgente baseline immunitaria (cells/h)
par.K    = 1.00e4;     % capacita'  portante (cells)
par.zeta = 5.00e-1;    % forza citotossica immunitaria (h^-1)
par.alpha= 5.00e-2;    % reclutamento immunitario indotto da i (h^-1)
par.beta = 3.00e-2;    % efficienza d'infezione virale (h^-1)

% Tolleranze numeriche e opzioni grafiche
tol.zero   = 1e-10;   % soglia per "zero numerico"
tol.exists = 1e-12;   % soglia positivita'  (esistenza biologica)
tol.hopf   = 1e-6;    % soglia/criterio Hopf (gap di RH)
msz        = 7;       % dimensione marker

% Parametri di scansione (intervalli intorno ai valori di base):
% Nota: gli intervalli sono volutamente ampi; adattarli in tesi per mettere in evidenza i fenomeni.
scan.alpha = linspace(max(0, 0.00*par.alpha), 4.00*par.alpha, 800);
scan.zeta  = linspace(max(0, 0.10*par.zeta ), 6.00*par.zeta , 800);
scan.beta  = linspace(max(1e-6, 0.10*par.beta), 5.00*par.beta, 800);

% Elenco dei parametri da scansionare (ognuno genera una figura dedicata)
paramList = {'alpha','zeta','beta'};

%% -------------------- LOOP PRINCIPALE SUI PARAMETRI DI CONTROLLO --------------------
for ip = 1:numel(paramList)
    ctrlName = paramList{ip};
    switch ctrlName
        case 'alpha', ctrlVals = scan.alpha;
        case 'zeta',  ctrlVals = scan.zeta;
        case 'beta',  ctrlVals = scan.beta;
        otherwise, error('Parametro di controllo "%s" non riconosciuto.', ctrlName);
    end

    % Preallocazioni (per velocita'  e robustezza)
    n = numel(ctrlVals);
    % E1
    U1 = nan(1,n); I1 = nan(1,n); Z1 = nan(1,n);
    stab1 = strings(1,n); stable1 = false(1,n);
    % E2
    U2 = nan(1,n); I2 = nan(1,n); Z2 = nan(1,n);
    stab2 = strings(1,n); stable2 = false(1,n); exist2 = false(1,n);
    % E3
    U3 = nan(1,n); I3 = nan(1,n); Z3 = nan(1,n);
    stab3 = strings(1,n); stable3 = false(1,n); exist3 = false(1,n);
    % Diagnostica Routh-Hurwitz per E3
    T3 = nan(1,n); M23 = nan(1,n); D3 = nan(1,n); RHgap = nan(1,n);

    % Soglie/marker
    idxHB  = []; xHB = []; yHB = []; labHB = {};   % Hopf su E3
    idxTC1 = []; xTC1= []; yTC1= []; labTC1= {};   % Transcritica E1<->E2 sul piano i=0
    idxVI  = []; xVI = []; yVI = []; labVI = {};   % Soglia invasione virale su E2 (lambda_i=0)
    idxI0  = []; xI0 = []; yI0 = []; labI0 = {};   % Frontiera esistenza E3 (i*=0)

    % -------------------- Scansione --------------------
    for k = 1:n
        % Aggiorna parametro di controllo
        par_k = par;
        par_k.(ctrlName) = ctrlVals(k);

        % ========== Equilibrio E1 ==========
        U1(k) = 0;
        I1(k) = 0;
        Z1(k) = par_k.S_z/max(par_k.q_z,eps);
        % Autovalori analitici in E1
        lam1 = -par_k.q_z;
        lam2 = par_k.p - (par_k.zeta*par_k.S_z)/(par_k.K*par_k.q_z);
        lam3 = -par_k.q - (par_k.zeta*par_k.S_z)/(par_k.K*par_k.q_z);
        [stable1(k), stab1(k)] = classify_by_eigs([lam1,lam2,lam3], tol);

        % ========== Equilibrio E2 ==========
        U2(k) = par_k.K - (par_k.zeta*par_k.S_z)/(par_k.p*par_k.q_z);
        I2(k) = 0;
        Z2(k) = Z1(k);
        exist2(k) = (U2(k) > tol.exists); % esistenza biologica
        if exist2(k)
            % Autovalori analitici in E2
            lam_u = -par_k.p + (par_k.zeta*par_k.S_z)/(par_k.K*par_k.q_z); % = -lambda (nel testo)
            lam_i = par_k.beta - (par_k.beta*par_k.zeta*par_k.S_z)/(par_k.K*par_k.p*par_k.q_z) ...
                              - par_k.q - (par_k.zeta*par_k.S_z)/(par_k.K*par_k.q_z);
            lam_z = -par_k.q_z;
            [stable2(k), stab2(k)] = classify_by_eigs([lam_u,lam_i,lam_z], tol);

            % Soglia di invasione virale su E2: lam_i = 0
            if k>1
                prev = par; prev.(ctrlName) = ctrlVals(k-1);
                lam_i_prev = prev.beta - (prev.beta*prev.zeta*prev.S_z)/(prev.K*prev.p*prev.q_z) ...
                                       - prev.q - (prev.zeta*prev.S_z)/(prev.K*prev.q_z);
                if sign(lam_i_prev) ~= sign(lam_i)
                    % Interpolazione lineare del punto soglia
                    x0 = interp1([lam_i_prev, lam_i],[ctrlVals(k-1), ctrlVals(k)], 0);
                    idxVI(end+1) = k;
                    xVI(end+1) = x0;
                    yVI(end+1) = max(U2(k),0);
                    labVI{end+1} = 'VI: invasione virale (E2)';
                end
            end
        else
            stab2(k) = "non esiste (u*<=0)";
        end

        % ========== Equilibrio E3: infezione attiva (formule corrette) ==========
        [u3,i3,z3, ok3, msg3] = eq3_equilibrium(par_k, tol);
        if ok3
            U3(k) = u3; I3(k) = i3; Z3(k) = z3;
            exist3(k) = true;

            % Jacobiana in E3 e autovalori
            J3 = jacobian_at(u3,i3,z3, par_k);
            ev = eig(J3);
            [stable3(k), stab3(k)] = classify_by_eigs(ev, tol);

            % Invarianti e gap di Routh-Hurwitz per Hopf
            [T3(k), M23(k), D3(k)] = invariants_TMD(J3);
            a2 = -T3(k); a1 = M23(k); a0 = -D3(k);
            RHgap(k) = a2*a1 - a0; % condizione RH: a2>0, a1>0, a0>0, RHgap>0

            % Frontiera i*=0 (esistenza E3) è utile per transizioni E2<->E3
            if k>1
                [~,i3prev,~, ok3prev,~] = eq3_equilibrium(setfield(par, ctrlName, ctrlVals(k-1)), tol); %#ok<SFLD>
                if ok3prev && sign(i3prev) ~= sign(i3)
                    x0 = interp1([i3prev, i3],[ctrlVals(k-1), ctrlVals(k)], 0);
                    idxI0(end+1) = k; xI0(end+1) = x0; yI0(end+1) = max(i3,0);
                    labI0{end+1} = 'Frontiera i^*=0 (E3)';
                end
            end

            % Rilevamento Hopf: cambio di segno del RHgap con a2,a1,a0>0
            if k>1 && ~isnan(RHgap(k-1))
                a2p = -T3(k-1); a1p = M23(k-1); a0p = -D3(k-1);
                if (a2p>0 && a1p>0 && a0p>0 && a2>0 && a1>0 && a0>0) ...
                   && (RHgap(k-1) * RHgap(k) < 0)
                    x0 = interp1([RHgap(k-1), RHgap(k)],[ctrlVals(k-1), ctrlVals(k)], 0);
                    idxHB(end+1) = k;
                    xHB(end+1) = x0;
                    yHB(end+1) = max(i3,0);
                    labHB{end+1} = 'HB: biforcazione di Hopf (E3)';
                end
            end
        else
            % non esiste biologicamente o denominatore non valido
            stab3(k) = "non esiste (i*<=0 o u*<=0)";
            if ~isempty(msg3)
                % messaggio tecnico opzionale
                % fprintf('[E3] %s\n', msg3);
            end
        end

        % ========== Soglia transcr. E1<->E2 (piano i=0): lambda = 0 ==========
        lambda_k = par_k.p - (par_k.zeta*par_k.S_z)/(par_k.K*par_k.q_z);
        if k>1
            prev = par; prev.(ctrlName) = ctrlVals(k-1);
            lambda_prev = prev.p - (prev.zeta*prev.S_z)/(prev.K*prev.q_z);
            if sign(lambda_prev) ~= sign(lambda_k)
                x0 = interp1([lambda_prev, lambda_k],[ctrlVals(k-1), ctrlVals(k)], 0);
                idxTC1(end+1) = k; xTC1(end+1) = x0; yTC1(end+1) = 0;
                labTC1{end+1} = 'TC: transcritica (E1<->E2, i=0)';
            end
        end
    end % fine scansione

    % -------------------- PLOT: 3 pannelli (u*, i*, z*) --------------------
    f = figure('Color','w','Name',sprintf('Diagramma di biforcazione: %s', ctrlName));
    tl = tiledlayout(3,1,'TileSpacing','compact','Padding','compact');

    % ----- Pannello 1: u* -----
    nexttile; hold on; grid on;
    title(sprintf('Rami di equilibrio: u^*  (parametro di controllo: %s)', ctrlName),'FontWeight','bold');
    xlabel(sprintf('%s', ctrlName)); ylabel('u^* (cell)');

    % E1
    plot_branch(ctrlVals, U1, stable1, 'E1', 'r', 'k');
    % E2 (solo dove esiste)
    plot_branch(ctrlVals(exist2), U2(exist2), stable2(exist2), 'E2', 'r', 'k');
    % E3 (solo dove esiste)
    plot_branch(ctrlVals(exist3), U3(exist3), stable3(exist3), 'E3', 'r', 'k');

    % marker soglie su u*
    plot_markers(ctrlName, xTC1, yTC1, 'o', [0 0.45 0.74], msz, labTC1);    % TC
    plot_markers(ctrlName, xVI,  yVI,  's', [0.85 0.33 0.10], msz, labVI);   % VI
    plot_markers(ctrlName, xHB,  yHB,  '^', [0.13 0.55 0.13], msz, labHB);   % HB
    legend('show','Location','best'); box on;

    % ----- Pannello 2: i* -----
    nexttile; hold on; grid on;
    title(sprintf('Rami di equilibrio: i^*  (parametro di controllo: %s)', ctrlName),'FontWeight','bold');
    xlabel(sprintf('%s', ctrlName)); ylabel('i^* (cell)');

    plot_branch(ctrlVals, I1, stable1, 'E1', 'r', 'k');                 % i*=0
    plot_branch(ctrlVals(exist2), I2(exist2), stable2(exist2), 'E2', 'r', 'k'); % i*=0
    plot_branch(ctrlVals(exist3), I3(exist3), stable3(exist3), 'E3', 'r', 'k');

    plot_markers(ctrlName, xI0,  yI0,  'd', [0.49 0.18 0.56], msz, labI0);   % i*=0 (E3)
    plot_markers(ctrlName, xHB,  yHB,  '^', [0.13 0.55 0.13], msz, labHB);   % HB
    legend('show','Location','best'); box on;

    % ----- Pannello 3: z* -----
    nexttile; hold on; grid on;
    title(sprintf('Rami di equilibrio: z^*  (parametro di controllo: %s)', ctrlName),'FontWeight','bold');
    xlabel(sprintf('%s', ctrlName)); ylabel('z^* (cell)');

    plot_branch(ctrlVals, Z1, stable1, 'E1', 'r', 'k');
    plot_branch(ctrlVals(exist2), Z2(exist2), stable2(exist2), 'E2', 'r', 'k');
    plot_branch(ctrlVals(exist3), Z3(exist3), stable3(exist3), 'E3', 'r', 'k');

    plot_markers(ctrlName, xHB,  yHB,  '^', [0.13 0.55 0.13], msz, labHB);   % HB
    legend('show','Location','best'); box on;

    % Titolo generale
    title(tl, sprintf('Diagramma di biforcazione a parametro di controllo: %s', ctrlName), ...
        'FontSize', 12, 'FontWeight','bold');

end % fine loop parametri

%% ============================ FUNZIONI LOCALI ============================

function [ok, type] = classify_by_eigs(ev, tol)
% CLASSIFICA LA NATURA LOCALE IN BASE AGLI AUTOVALORI (reali/complessi)
% Restituisce:
%   ok   : vero se asintoticamente stabile
%   type : descrizione (nodo stabile, fuoco stabile, sella, ecc.)
    if ~isvector(ev); ev = diag(ev); end
    re = real(ev(:)); imv = imag(ev(:));
    pos = sum(re >  +1e-12);
    neg = sum(re <  -1e-12);
    zer = numel(re) - pos - neg;

    % Stabilita'  asintotica = tutte le parti reali < 0
    ok = all(re < -1e-12);

    if ok
        if any(abs(imv) > 1e-9)
            type = "fuoco stabile (parti reali negative)";
        else
            type = "nodo stabile (tutti reali negativi)";
        end
        return;
    end

    % Casi instabili o critici
    if zer>0
        type = "critico (non iperbolico: parte reale zero)";
        return;
    end
    if pos==1 && neg==2
        if any(abs(imv) > 1e-9)
            type = "sella (indice 1) con componente oscillatoria";
        else
            type = "sella (indice 1)";
        end
        return;
    end
    if pos==2 && neg==1
        type = "sella (indice 2)";
        return;
    end
    if pos==3
        if any(abs(imv) > 1e-9)
            type = "fuoco instabile (tutti con Re>0, coppia complessa)";
        else
            type = "nodo instabile (tutti reali positivi)";
        end
        return;
    end
    % Caso residuale
    type = "indeterminato (verificare)";
end


function [u3,i3,z3, ok, msg] = eq3_equilibrium(par, tol)
% EQUILIBRIO DI COESISTENZA (E3) con le FORMULE CORRETTE (errata corrige)

    msg = "";
    % Denominatore (positivo per parametri positivi)
    denom = (par.beta + par.p) * (par.alpha * par.zeta + par.beta * par.q_z);
    if denom <= tol.zero
        ok = false; u3=nan; i3=nan; z3=nan;
        msg = "Denominatore nullo o non positivo in i^* (E3).";
        return;
    end
    num = par.K * par.p * par.q_z * (par.beta - par.q) - par.zeta * par.S_z * (par.beta + par.p);
    i3 = num / denom;
    z3 = par.alpha * i3 / par.q_z + par.S_z / par.q_z;

    if par.beta <= tol.zero
        ok = false; u3=nan; z3=nan;
        msg = "beta troppo piccolo o nullo: u^* non definito.";
        return;
    end
    u3 = par.K * par.q / par.beta + (par.zeta/par.beta) * z3;

    % Esistenza biologica: u*, i*, z* > 0 (entro tolleranza)
    ok = (i3 > tol.exists) && (u3 > tol.exists) && (z3 > tol.exists);
    if ~ok
        msg = "E3 non biologico (i^*<=0 o u^*<=0 o z^*<=0).";
    end
end


function J = jacobian_at(u,i,z, par)
% JACOBIANA del sistema valutata in (u,i,z)

    J11 = par.p - (2*par.p*u)/par.K - (par.p*i)/par.K - (par.beta*i)/par.K - (par.zeta*z)/par.K;
    J12 = -(par.p/par.K)*u - (par.beta/par.K)*u;
    J13 = -(par.zeta/par.K)*u;

    J21 =  (par.beta/par.K)*i;
    J22 =  (par.beta/par.K)*u - par.q - (par.zeta/par.K)*z;
    J23 = -(par.zeta/par.K)*i;

    J31 = 0;
    J32 = par.alpha;
    J33 = -par.q_z;

    J = [J11 J12 J13; J21 J22 J23; J31 J32 J33];
end


function [T, M2, D] = invariants_TMD(J)
% INVARIANTI del polinomio caratteristico:
% chi(lambda) = lambda^3 - (tr J) lambda^2 + (somma minori principali 2x2) lambda - det J
% Routh-Hurwitz: a2=-T>0, a1=M2>0, a0=-D>0, e a2*a1>a0
    T  = trace(J);
    % M2: 1/2[(tr J)^2 - tr(J^2)]
    M2 = 0.5*((trace(J))^2 - trace(J*J));
    D  = det(J);
end


function plot_branch(x, y, stableMask, labelName, colStable, colUnst)
% TRACCIA un ramo di equilibrio distinguendo tratti stabili/instabili
% y puo' contenere NaN: verranno ignorati
    y = y(:)'; x = x(:)'; stableMask = logical(stableMask(:)');
    % Segmenti stabili
    xs = x; ys = y; xs(~stableMask) = NaN; ys(~stableMask) = NaN;
    p1 = plot(xs, ys, '-', 'Color', colStable, 'LineWidth', 2.0, ...
        'DisplayName', sprintf('%s (stabile)', labelName));
    % Segmenti instabili
    xu = x; yu = y; xu(stableMask) = NaN; yu(stableMask) = NaN;
    p2 = plot(xu, yu, '--', 'Color', colUnst, 'LineWidth', 1.5, ...
        'DisplayName', sprintf('%s (instabile)', labelName));
    % Per evitare doppioni in legenda quando i tratti non esistono
    if all(isnan(xs)), set(p1,'HandleVisibility','off'); end
    if all(isnan(xu)), set(p2,'HandleVisibility','off'); end
end


function plot_markers(ctrlName, xvec, yvec, mkr, rgb, msz, labels)
% PLOT dei marcatori (HB, TC, VI, i*=0) con etichetta
    if isempty(xvec), return; end
    for j=1:numel(xvec)
        if ~isnan(xvec(j)) && ~isnan(yvec(j))
            plot(xvec(j), yvec(j), mkr, 'MarkerSize', msz, ...
                 'MarkerFaceColor', rgb, 'MarkerEdgeColor', 'k', ...
                 'DisplayName', labels{j});
            % Etichetta spostata leggermente
            dx = 0.01*(max(xvec)-min(xvec)+eps);
            dy = 0.02*(max(yvec)-min(yvec)+1);
            text(xvec(j)+dx, yvec(j)+dy, labels{j}, 'Color', rgb, ...
                'FontSize', 9, 'FontAngle','italic');
        end
    end
end

