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

"""
numerical EC.6.2 consider large memory length 
name: Constr 
""" 
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
    Mlist = [4,5,6 ] 
    ratio_list = [0, 0.1, 0.2]  
    testn = 10   
    time_bound = 2*60*60
else
    Mlist = [4] 
    ratio_list = [0,0.1, 0.2]  
    testn = 10    
    time_bound = 2*60*60
end
# =======================================================


using JuMP,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", "LargeM_CuttingPlane.jl"))   

# generate historical assortments for gradient constraint 
function gene_his_set(M)
    xhis_set = []
    push!(xhis_set, zeros(M))
    for k in 1:M 
        h = zeros(M)
        h[k] = 1
        push!(xhis_set, h)
    end 
    for k in 1:M-1 
        for kk in k+1:M 
            h = zeros(M)
            h[k] = 1
            h[kk] = 1
            push!(xhis_set, h)
        end 
    end 
    push!(xhis_set,ones(M))
end 

# generate initial rankings for lovasz extension 
using Random
function gene_rank(M,num_rank)
    items = collect(1:M)
    rank_set = Set()
    while length(rank_set)< num_rank 
        new_rank = shuffle(items)
        if (new_rank in rank_set)
            nothing 
        else
            push!(rank_set, new_rank)
            push!(rank_set, reverse(new_rank))
        end  
    end  
    return rank_set 
end 


# ------------ define test name --------------
println("=" ^ 50)
println("Set optimization parameters ...")
name = "LargeM"

N = 20 
strong = 0  
posi = 0 
mix = 1   
# opt parameters 
v0 = 1 
stop_gap = 0.005
init = 0
init_X = Any[]
output = 1
IP = 1   
# constraint violate threshold 
epsilon = 0.0000001 
delta = 0.00000001    
# number of initial rankings under different M 
ranklist = [2,10,20]

println("Numerical study: ", name)
println("Number of products N = ", N)
println("Memory length M = ", Mlist)
println("Planning horizon T = M+1" ) 
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", "TableEC3_ec_6_2_LargeM")
logs_folder = joinpath(results_folder, "logs")
mkpath(logs_folder)  


 
for M in Mlist 
    for ratio in ratio_list
        T = 1+M   
        test_data = open( joinpath(PROJECT_ROOT, "data", "LargeM_ratio"*string(ratio*10)*"_M"*string(M)*".json"), "r") do io
            JSON.parse(read(io, String))
        end   

        num_rank = ranklist[M-3]  
        xhis_set = gene_his_set(M ) 
        rank_set = gene_rank(M,num_rank)   

        for tn in 1:testn 
            r = test_data[tn ][1]
            u = test_data[tn ][2]
            beta0 = test_data[tn][3] 
            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  
            
            log_filename = "M"*string(M)*"_ratio"*string(ratio)*"_inst"*string(tn)*".txt" 
            logname = joinpath(logs_folder, log_filename)
            cardinal = N  
            showtime = T  
            res = LargeM_Callback(T,N,M,r,beta,u,v0, init,init_X, IP, time_bound, stop_gap,output, logname, s,betanew,rank_set, xhis_set, epsilon, delta, cardinal, showtime   ) 
    
            push!(Result, [M,T,ratio, tn, res[1],res[2],res[3]  ])
            GC.gc()
        
        end 
    end 
end  
 
temp_matrix  = tupe2mat(Result) 
f1 = joinpath(results_folder,"result_detail.csv")
temp_data = DataFrame(temp_matrix, :auto) 
rename!(temp_data, ["M", "T", "ratio","k","obj", "runtime", "gap" ])
CSV.write(f1,  temp_data, writeheader=false)


# =================== compute ec3 ===================== 

summary = [ ] 
for M in Mlist
    T = M+1
    for ratio in ratio_list   
        filterdata = filter(row -> row.M == M && row.ratio == ratio , temp_data)

        solved = 0 
        total_time = 0 
        total_endgap = 0 
        for (index, row) in enumerate(eachrow(filterdata))
            if row.gap <= stop_gap 
                solved +=1 
                total_time += row.runtime 
            else 
                total_endgap += row.gap
            end
        end 
        if solved ==0 
            push!(summary, [M,T,ratio, solved, time_bound, round(100*total_endgap/testn, digits=4)] )  
        elseif solved == testn 
            push!(summary, [M,T,ratio, solved, round(total_time/testn, digits=4), round(100*total_endgap/testn ) ] )  
        else
            push!(summary, [M,T,ratio, solved, round(total_time/solved, digits=4), round(100*total_endgap/(testn-solved), digits=4)] ) 
        end
    end 
end 

summary = tupe2mat(summary) 
summary = DataFrame(summary, :auto)   
rename!(summary, ["M", "T", "ratio_theta", "solved","time", "end_gap" ])

# Get current time
t = Dates.now() 
timestr = Dates.format(t, "HHMM")
ec3 = joinpath(results_folder,"Table_ec3_"*timestr*".xlsx")
XLSX.writetable(ec3, summary)


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