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

"""
Example1: compute revenue loss of (M+1)-cyclic policies
"""
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 ==================
# Check if "--mode" exists and is followed by "full"
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
    Mlist = [1,2] 
    Tlist = [10,30,50]
    strong_list = [0,1]  # weak and strong satiation 
    testn = 5
    N = 30
    time_bound_bmk = 2*60*60
    time_bound_cycle = 2*60*60
else
    Mlist = [1,2] 
    Tlist = [10] 
    strong_list = [0,1]
    testn = 5
    N = 30
    time_bound_bmk = 2*60*60
    time_bound_cycle = 2*60*60
end



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

using JuMP,CSV,DataFrames,Gurobi,BenchmarkTools,MosekTools, Dates, JSON , XLSX, Mosek, MosekTools
import LinearAlgebra, Random 
Random.seed!(314)
import MathOptInterface as MOI    

# --- 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", "Project_CuttingPlane.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 = "Example"  
posi = 0 # consider satiation case 
mix = 0 
posi_ratio = 0 
# 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 = ", N)
println("Memory length M = ", Mlist)
println("Planning horizon T = ", Tlist ) 
println("Time limit for benchmark = ", time_bound_bmk, " seconds ") 
println("Time limit for cyclic policy = ", time_bound_cycle, " 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", "Table1_Sec4_3")
logs_folder = joinpath(results_folder, "logs")
mkpath(logs_folder)  



# ==================== Start solving ====================
GC.gc() 
 
for M in Mlist  
    if M == 1
        rank_set = [1]
    else
        rank_set = [ [1,2], [2,1]]
    end 
    # satiation case, use the same s 
    s = zeros(N,M)

    for strong in strong_list 
        json_path = joinpath(PROJECT_ROOT, "data", "Satiation_M"*string(M)*"_strong"*string(strong)*"_v2.json")
        test_data = open(json_path, "r") do io
        JSON.parse(read(io, String))
        end  

        for k in 1:testn  
            r = test_data[k][1]
            u = test_data[k][2]
            beta0 = test_data[k][3]  
            beta = zeros(N,M)
            # set up parameters 
            for i in 1:N 
                for m in 1:M 
                    beta[i,m] = beta0[m][i]
                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  
                # solve M+1 cyclic  
                cycle_res = cycle_policy(T,N,M,r,beta,u,v0,time_bound_cycle,IP,stop_gap,cardinal,0)

                # solve conic for benchmark   
                log_filename = "M"*string(M)*"_strong"*string(strong)*"_inst"*string(k)*"_T"*string(T)*".txt"
                opt_logname = joinpath(logs_folder, log_filename)
                cone = MIP(T,N,M,r,beta,u,v0,IP, time_bound_bmk, stop_gap,init,init_X, output, opt_logname, s, betanew,rank_set, cardinal, showtime ,1 )  
                
                push!(Result, [M, strong, k, T, cycle_res[1],cone[1], cone[2] ])
                GC.gc()
            end 
        end  
    end 
    println("\n" * "=" ^ 50)
    println("Finish solving memory length = ", M) 
    println("\n" * "=" ^ 50)
end   

temp_matrix  = tupe2mat(Result)  
f1 = joinpath(results_folder, "result_detail.csv")
temp_data = DataFrame(temp_matrix, :auto) 
rename!(temp_data, ["M", "beta", "k","T","cycle_obj","opt", "opt_time"])
CSV.write(f1,  temp_data, writeheader=false)


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

println("=" ^ 50)
println("Compute revenue loss ...")


temp_data.cycle = (temp_data.opt  .- temp_data.cycle_obj ) ./ temp_data.opt      

sumdata = [ ] 
 
for M in Mlist      
    for strong in strong_list
        for T in Tlist
            filterdata = filter(row -> row.M == M && row.beta == strong  && row.T == T, temp_data)
            push!(sumdata, [M, strong, T, round(100*mean(filterdata.cycle),digits=4) ])
        end 
    end 
end 


sumdata = tupe2mat(sumdata) 
sumdata = DataFrame(sumdata, :auto)   
rename!(sumdata, ["M", "beta", "T", "revenue_gap" ])
# Replace and reassign
sumdata.beta = replace(sumdata.beta, 0 => "W", 1 => "S")

# Get current time
t = Dates.now() 
timestr = Dates.format(t, "HHMM")
tb_filename = "Table1_revgap_"*timestr*".xlsx" 
tb = joinpath(results_folder, tb_filename)
XLSX.writetable(tb, sumdata)


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