# SPDX-FileCopyrightText: 2026 Yating Zhang
# SPDX-License-Identifier: MIT

"""
numerical 6.2 compare env and heuristics 
name: CompareHeuristic
"""
function enable_stay_awake()
    if Sys.iswindows()
        # Windows: Call Kernel32 API
        # ES_CONTINUOUS (0x80000000) | ES_SYSTEM_REQUIRED (0x00000001)
        # Tells the system that this thread requires the system to be in the working state.
        try
            ccall((:SetThreadExecutionState, "kernel32"), UInt32, (UInt32,), 0x80000001)
            println(">> Windows: System sleep prevention enabled.")
        catch
            println(">> Windows: Failed to set sleep prevention via API.")
        end
    elseif Sys.isapple()
        # macOS: Use caffeinate
        # -i: prevent idle sleep
        # -w: wait for the process with the given PID to exit
        run(`caffeinate -i -w $(getpid())`, wait=false)
        println(">> macOS: System sleep prevention enabled (caffeinate).")
    elseif Sys.islinux()
        # Linux: Use systemd-inhibit
        # It creates a lock that prevents sleep/idle.
        try
            run(`systemd-inhibit --why="Numerical Experiments" --who="Julia" --what=idle:sleep sleep infinity`, wait=false)
            println(">> Linux: System sleep prevention enabled (systemd-inhibit).")
        catch
            println(">> Linux: systemd-inhibit not found. System may still sleep.")
        end
    end
end

enable_stay_awake()

# ================== set parameters ==================
mode_idx = findfirst(x -> x == "--mode", ARGS)
is_full = mode_idx !== nothing && mode_idx < length(ARGS) && ARGS[mode_idx+1] == "full"

if is_full
    @info "RUNNING IN FULL MODE: This will reproduce the full tables."
else
    @warn "RUNNING IN TEST MODE (Default): Only a subset of instances will be reproduced. To reproduce full results, add '--mode full' at the end of the command."
end

if is_full
    Nlist = [30,50,70 ]  
    Tlist = [5,10]   
    ratio_list = [0,0.1,0.2]  
    testn = 5
    time_bound = 60*60
else
    Nlist = [30]  
    Tlist = [5]   
    ratio_list = [0,0.1,0.2]  
    testn = 5
    time_bound = 60*60
end

# =======================================================

using JuMP,Pkg,CSV,DataFrames,Gurobi,BenchmarkTools,MosekTools, XLSX
import LinearAlgebra 
import Random
Random.seed!(314)
import MathOptInterface as MOI 
using Dates, JSON  
 
# --- PATH SETUP ---
const SCRIPT_DIR = @__DIR__
const PROJECT_ROOT = dirname(SCRIPT_DIR) 
# ------------------

include(joinpath(PROJECT_ROOT, "src", "Gene_data.jl"))  
include(joinpath(PROJECT_ROOT, "src", "Conic_Env.jl"))
include(joinpath(PROJECT_ROOT, "src", "Heuristics.jl")) 

# -------------- Set parameters --------------

println("=" ^ 50)
println("Set optimization parameters ...")

testname = "CompareHeuristic" 

M = 2  
posi = 0 
mix = 1 # consider mix historical effects 
strong = 1 # strong satiation effects  
rank_set = [ [1,2], [2,1]]  # M = 2  
# opt parameters 
v0 = 1  
stop_gap = 0.005
init = 0
init_X = Any[]
output = 1
IP = 1   

println("Numerical study: ", testname)
println("Number of products N = ", Nlist)
println("Memory length M = ", M)
println("Planning horizon T = ", Tlist ) 
println("Time limit = ", time_bound, " seconds ")
println("Stop optimality gap = ", stop_gap*100, "%")

println("Start solving HAP problems...")
println("=" ^ 50)

Result = [ ]

# This ensures code doesn't crash if folder is missing
results_folder = joinpath(PROJECT_ROOT, "results", "Table5_Sec6_2_Heuristic")
logs_folder = joinpath(results_folder, "logs")
mkpath(logs_folder)  

GC.gc() 
for tn in 1:length(Nlist)
    N = Nlist[tn]   
    test_data = open( joinpath(PROJECT_ROOT, "data", "CompareHeuristic_N"*string(N)*".json"), "r") do io  
        JSON.parse(read(io, String))
    end   
    for j in 1:length(ratio_list)  
        posi_ratio = ratio_list[j]

        for k in 1:testn 
            r = test_data[(j-1)*testn + k ][1]
            u = test_data[(j-1)*testn + k ][2]
            beta0 = test_data[(j-1)*testn + k ][3] 
            beta = zeros(N,M)
            beta = zeros(N,M)
            for i in 1:N 
                for m in 1:M 
                    beta[i,m] = beta0[m][i]
                end 
            end  
            
            s = zeros(N,M)
            for i in 1:N 
                for m in 1:M 
                    if beta[i,m]>0 
                        s[i,m]=1
                    end 
                end
            end 
        
            betanew = zeros(N,M,M+1)
            for t in 2:M+1
                for i in 1:N  
                    if (t<=M) 
                        for m in 1:t-1 
                            betanew[i,m,t] = beta[i,m]
                        end 
                    else 
                        for m in 1:M 
                            betanew[i,m,t] =  beta[i,m] 
                        end 
                    end 
                end 
            end  
        
            for T in Tlist
                # unconstrained 
                cardinal = N 
                showtime = T 

                # env   
                log_filename = "bmk_ratio"*string(posi_ratio)*"_N"*string(N)*"_T"*string(T)*"_inst"*string(k)*".txt"
                logname2 = joinpath(logs_folder, log_filename)
                env = MIP(T,N,M,r,beta,u,v0,IP, time_bound,stop_gap,init,init_X, output, logname2, s, betanew,rank_set, cardinal, showtime , 0 )

                # sequential ro 
                ro = SequentialRO(v0,u,beta,r,N,M,T)
                
                # sequential lospo
                oneout = ro_oneout(N,M,T, r, u, beta, v0 )

                push!(Result,[posi_ratio, N, k , T, cardinal, showtime, env[1], env[2], env[3], ro[1], oneout[1] ])
                GC.gc()
            end 
        end 
    end 
end  

temp_matrix  = tupe2mat(Result) 
f1 =  joinpath(results_folder, "result_detail.csv")
temp_data = DataFrame(temp_matrix, :auto) 
rename!(temp_data, ["ratio", "N", "k","T", "cardsize","showtime", "obj","runtime","gap","ro","oneout" ])
CSV.write(f1,  temp_data, writeheader=false)

# ============== compute table 5 ========================  
 

# changed version 
temp_data.rogap = (temp_data.obj  .- temp_data.ro) ./  temp_data.obj
temp_data.oneoutgap = (temp_data.obj  .- temp_data.oneout) ./  temp_data.obj

# final DataFrame columns 
col_names = ["N", "T"]
for ratiov in ratio_list 
    push!(col_names, "Sequential - RO (θ=$(ratiov))") 
end
for ratiov in ratio_list  
    push!(col_names, "Sequential - LOSPO (θ=$(ratiov))")
end
final_table_data = []
 
for N in Nlist
    for T in Tlist
        row_data = []
        push!(row_data, N)
        push!(row_data, T)  
        for ratiov in ratio_list
            filterdata = filter(row -> row.T == T && row.N == N && row.ratio == ratiov, temp_data)
            
            # compute average gap 
            mean_rogap = round(100*mean(filterdata.rogap), digits=4) 
            push!(row_data, mean_rogap) 
        end
        for ratiov in ratio_list
            filterdata = filter(row -> row.T == T && row.N == N && row.ratio == ratiov, temp_data)
            
            # compute average gap  
            mean_oneoutgap =  round(100*mean(filterdata.oneoutgap), digits=4) 
            push!(row_data, mean_oneoutgap)
        end
        push!(final_table_data, row_data)
    end
end
 
final_table_temp = tupe2mat(final_table_data)  
final_table_DF = DataFrame(final_table_temp, :auto) 
rename!(final_table_DF, col_names)

# Get current time
t = Dates.now()
timestr = Dates.format(t, "HHMM")

table5 = joinpath(results_folder, "Table5_RO_revgap_"*timestr*".xlsx")
XLSX.writetable(table5, final_table_DF, overwrite=true)

println("Finish numerical study ", testname, "!")