#!/bin/bash

# Run all pi calculation programs and measure performance and memory
# Run each program 3 times and take the average

set -e

SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
cd "$SCRIPT_DIR"

# Check if argument provided
if [ $# -eq 0 ]; then
    echo "Usage: $0 <decimaler>"
    echo "Example: $0 100"
    exit 1
fi

DECIMALS=$1

# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color

# Function to verify result against facit
verify() {
    local result="$1"
    local decimals="$2"
    local expected=$(head -c $((decimals + 2)) facit.txt)

    if [ "$result" = "$expected" ]; then
        return 0
    else
        return 1
    fi
}

# Function to get metrics from /usr/bin/time output
get_time_metrics() {
    local output_file=$1
    local metric=$2
    
    if [ ! -f "$output_file" ]; then
        echo "0"
        return
    fi
    
    case "$metric" in
        "real")
            grep "real" "$output_file" 2>/dev/null | awk '{print $1}' || echo "0"
            ;;
        "user")
            grep "real" "$output_file" 2>/dev/null | awk '{print $3}' || echo "0"
            ;;
        "sys")
            grep "real" "$output_file" 2>/dev/null | awk '{print $5}' || echo "0"
            ;;
        "memory")
            grep "maximum resident set size" "$output_file" 2>/dev/null | awk '{print $1}' || echo "0"
            ;;
        "instructions")
            grep "instructions retired" "$output_file" 2>/dev/null | awk '{print $1}' || echo "0"
            ;;
        "cycles")
            grep "cycles elapsed" "$output_file" 2>/dev/null | awk '{print $1}' || echo "0"
            ;;
        *)
            echo "0"
            ;;
    esac
}

# Function to profile memory and CPU during execution (optimized for macOS)
profile_resources() {
    local pid=$1
    local csv_file=$2
    local peak_mem=0
    local peak_cpu=0
    local current_mem
    local current_cpu
    local start_time=$(date +%s%N)
    local sample_count=0
    
    # Create CSV header
    echo "timestamp_ms,memory_bytes,cpu_percent" > "$csv_file"
    
    # Sample resources as fast as possible using ps
    # Use a single ps call with multiple iterations to reduce overhead
    while kill -0 "$pid" 2>/dev/null; do
        # Use ps with -o pid,rss,%cpu for faster parsing
        local ps_output=$(ps -o pid,rss,%cpu -p "$pid" 2>/dev/null | tail -n 1)
        
        if [ -n "$ps_output" ]; then
            # Parse ps output: "1234 5678 12.3" -> PID=1234, RSS=5678, CPU=12.3
            local kb=$(echo "$ps_output" | awk '{print $2}')
            local cpu_raw=$(echo "$ps_output" | awk '{print $3}' | awk -F'.' '{print $1}')
            
            # Ensure kb is a valid number
            if ! [[ "$kb" =~ ^[0-9]+$ ]]; then
                kb=0
            fi
            current_mem=$((kb * 1024))
            
            # Ensure cpu is a valid number
            if ! [[ "$cpu_raw" =~ ^[0-9]+$ ]]; then
                cpu_raw=0
            fi
            current_cpu=$cpu_raw
            
            current_time=$(date +%s%N)
            elapsed_ms=$(( (current_time - start_time) / 1000000 ))
            
            if [ "$current_mem" -gt "$peak_mem" ]; then
                peak_mem=$current_mem
            fi
            if [ "$current_cpu" -gt "$peak_cpu" ]; then
                peak_cpu=$current_cpu
            fi
            
            # Store time-series data in CSV format
            echo "$elapsed_ms,$current_mem,$current_cpu" >> "$csv_file"
            
            sample_count=$((sample_count + 1))
        fi
    done
    
    echo "$peak_mem $peak_cpu $sample_count"
}

echo "=== Pi-beräkning med $DECIMALS decimaler (4 körningar, genomsnitt av 3 efter warmup) ==="
echo ""

# Array to store results
declare -a results

# Run each program 4 times, discard first run (warmup), take average of last 3
run_program() {
    local name="$1"
    shift

    printf "%-12s " "$name"

    local total_time=0
    local total_memory=0
    local total_cpu=0
    local total_real_time=0
    local total_user_time=0
    local total_sys_time=0
    local total_instructions=0
    local total_cycles=0
    local success_count=0
    local result
    local peak_memory=0
    local peak_cpu=0
    local timeline_dir="timelines/$name"
    local data_dir="data/$name"
    
    # Create directories
    mkdir -p "$timeline_dir"
    mkdir -p "$data_dir"

    # Run 4 times, discard first run (warmup)
    for i in 1 2 3 4; do
        local timeline_file="$timeline_dir/run_$i.tsv"
        local csv_file="$data_dir/run_$i.csv"
        local time_output_file="/tmp/time_output_$$_$i.txt"
        local start=$(date +%s%N)
        
        # Run program with /usr/bin/time -l (macOS) or -v (Linux) for accurate memory measurement
        # This works for all programs, including very fast ones
        if command -v /usr/bin/time >/dev/null 2>&1; then
            # Detect if we're on macOS or Linux
            if [[ "$OSTYPE" == "darwin"* ]]; then
                # macOS uses -l flag
                /usr/bin/time -l "$@" > /dev/null 2> "$time_output_file" &
                local pid=$!
            else
                # Linux uses -v flag
                /usr/bin/time -v "$@" > /dev/null 2> "$time_output_file" &
                local pid=$!
            fi
        else
            # Fallback to regular execution if /usr/bin/time not available
            "$@" 2>/dev/null &
            local pid=$!
        fi
        
        # Profile resources in background (for CPU and timeline)
        local resources=$(profile_resources "$pid" "$csv_file")
        local peak_mem_ps=$(echo "$resources" | awk '{print $1}')
        local peak_cpu_val=$(echo "$resources" | awk '{print $2}')
        
        # Wait for process to complete
        wait "$pid" 2>/dev/null
        local exit_code=$?
        
        local end=$(date +%s%N)
        local elapsed=$(( (end - start) / 1000000 ))
        
        # Get all metrics from /usr/bin/time output
        local real_time=$(get_time_metrics "$time_output_file" "real")
        local user_time=$(get_time_metrics "$time_output_file" "user")
        local sys_time=$(get_time_metrics "$time_output_file" "sys")
        local peak_mem_time=$(get_time_metrics "$time_output_file" "memory")
        local instructions=$(get_time_metrics "$time_output_file" "instructions")
        local cycles=$(get_time_metrics "$time_output_file" "cycles")
        
        # Use the larger of the two memory measurements (time -v is more reliable)
        local peak_mem=$peak_mem_time
        if [ "$peak_mem_ps" -gt "$peak_mem" ]; then
            peak_mem=$peak_mem_ps
        fi
        
        # Clean up temp file
        rm -f "$time_output_file"
        
        if [ $exit_code -eq 0 ]; then
            # Get result for verification
            result=$("$@" 2>/dev/null)
            
            if [ "$i" -gt 1 ]; then
                # Only count runs 2-4 for averages
                total_time=$((total_time + elapsed))
                total_memory=$((total_memory + peak_mem))
                total_cpu=$((total_cpu + peak_cpu_val))
                total_real_time=$(echo "$total_real_time + $real_time" | bc)
                total_user_time=$(echo "$total_user_time + $user_time" | bc)
                total_sys_time=$(echo "$total_sys_time + $sys_time" | bc)
                total_instructions=$((total_instructions + instructions))
                total_cycles=$((total_cycles + cycles))
                
                if [ "$peak_mem" -gt "$peak_memory" ]; then
                    peak_memory=$peak_mem
                fi
                if [ "$peak_cpu_val" -gt "$peak_cpu" ]; then
                    peak_cpu=$peak_cpu_val
                fi
                
                if verify "$result" "$DECIMALS"; then
                    ((success_count++))
                fi
            fi
        else
            echo -e "${RED}ERROR${NC}"
            results+=("999999 $name ERROR")
            return
        fi
    done

    # Calculate averages
    local avg_time=$((total_time / 3))
    local avg_memory=$((total_memory / 3))
    local avg_cpu=$((total_cpu / 3))
    local avg_real_time=$(echo "scale=3; $total_real_time / 3" | bc)
    local avg_user_time=$(echo "scale=3; $total_user_time / 3" | bc)
    local avg_sys_time=$(echo "scale=3; $total_sys_time / 3" | bc)
    local avg_instructions=$((total_instructions / 3))
    local avg_cycles=$((total_cycles / 3))
    local ipc=$(echo "scale=2; $avg_instructions / $avg_cycles" | bc)

    # Save summary to CSV
    local summary_file="$data_dir/summary.csv"
    echo "metric,value" > "$summary_file"
    echo "time_ms,$avg_time" >> "$summary_file"
    echo "memory_bytes,$avg_memory" >> "$summary_file"
    echo "peak_memory_bytes,$peak_memory" >> "$summary_file"
    echo "real_time_s,$avg_real_time" >> "$summary_file"
    echo "user_time_s,$avg_user_time" >> "$summary_file"
    echo "sys_time_s,$avg_sys_time" >> "$summary_file"
    echo "instructions,$avg_instructions" >> "$summary_file"
    echo "cycles,$avg_cycles" >> "$summary_file"
    echo "ipc,$ipc" >> "$summary_file"

    if [ $success_count -eq 3 ]; then
        echo -e "${GREEN}SUCCESS${NC} $avg_time ms, ${BLUE}${avg_memory} bytes avg / ${YELLOW}${peak_memory} bytes peak, ${YELLOW}${avg_cpu}% CPU avg / ${peak_cpu}% CPU peak${NC}"
        echo "  Real: ${avg_real_time}s, User: ${avg_user_time}s, Sys: ${avg_sys_time}s"
        echo "  Instructions: ${avg_instructions}, Cycles: ${avg_cycles}, IPC: $ipc"
        echo "  Data saved to: $data_dir/"
        results+=("$avg_time $name SUCCESS $avg_memory $peak_memory $avg_cpu $peak_cpu $avg_real_time $avg_user_time $avg_sys_time $avg_instructions $avg_cycles")
    else
        echo -e "${RED}FAILED${NC} $avg_time ms, ${BLUE}${avg_memory} bytes avg / ${YELLOW}${peak_memory} bytes peak, ${YELLOW}${avg_cpu}% CPU avg / ${peak_cpu}% CPU peak${NC}"
        echo "  Real: ${avg_real_time}s, User: ${avg_user_time}s, Sys: ${avg_sys_time}s"
        echo "  Instructions: ${avg_instructions}, Cycles: ${avg_cycles}, IPC: $ipc"
        echo "  Data saved to: $data_dir/"
        results+=("$avg_time $name FAILED $avg_memory $peak_memory $avg_cpu $peak_cpu $avg_real_time $avg_user_time $avg_sys_time $avg_instructions $avg_cycles")
    fi
}

# Run all programs (no timeouts)
run_program Bash bash bash/bin/print_hej "$DECIMALS"
run_program Brainfuck brainfuck/bin/print_hej "$DECIMALS"
run_program C c/bin/print_hej "$DECIMALS"
run_program C++ cpp/bin/print_hej "$DECIMALS"
run_program Crystal crystal/bin/print_hej "$DECIMALS"
run_program CSharp csharp/bin/print_hej "$DECIMALS"
run_program D d/bin/print_hej "$DECIMALS"
run_program Dart dart/bin/print_hej "$DECIMALS"
run_program Elixir elixir/bin/print_hej "$DECIMALS"
run_program Erlang erlang/bin/print_hej "$DECIMALS"
run_program Fortran fortran/bin/print_hej "$DECIMALS"
run_program Go go/bin/print_hej "$DECIMALS"
run_program Haskell haskell/bin/print_hej "$DECIMALS"
run_program Java java/bin/print_hej "$DECIMALS"
run_program JavaScript javascript/bin/print_hej "$DECIMALS"
run_program Julia julia/bin/print_hej "$DECIMALS"
run_program Kotlin kotlin/bin/print_hej "$DECIMALS"
run_program Objective-C objective-c/bin/print_hej "$DECIMALS"
run_program Scala scala/bin/print_hej "$DECIMALS"
run_program TypeScript typescript/bin/print_hej "$DECIMALS"
run_program Lua lua/bin/print_hej "$DECIMALS"
run_program Nim nim/bin/print_hej "$DECIMALS"
run_program Odin odin/bin/print_hej "$DECIMALS"
run_program Perl perl/bin/print_hej "$DECIMALS"
run_program PHP php/bin/print_hej "$DECIMALS"
run_program Python python/bin/print_hej "$DECIMALS"
run_program R r/bin/print_hej "$DECIMALS"
run_program Ruby ruby/bin/print_hej "$DECIMALS"
run_program Rust rust/bin/print_hej "$DECIMALS"
run_program Swift swift/bin/print_hej "$DECIMALS"
run_program Zig zig/bin/print_hej "$DECIMALS"
run_program Assembly assembly/bin/print_hej "$DECIMALS"
run_program Wolfram wolfram/bin/print_hej "$DECIMALS"
run_program Vimscript vimscript/bin/print_hej "$DECIMALS"

echo ""
echo "=== Sammanfattning (sorterad efter prestanda) ==="
echo ""

# Sort and print results
IFS=$'\n' sorted=($(printf '%s\n' "${results[@]}" | sort -n))
unset IFS

echo "Språk        Status      Tid (ms)    Minne (bytes)    CPU (%)"
echo "================================================================"

for entry in "${sorted[@]}"; do
    time_ms=$(echo "$entry" | awk '{print $1}')
    name=$(echo "$entry" | awk '{print $2}')
    status=$(echo "$entry" | awk '{print $3}')
    avg_mem=$(echo "$entry" | awk '{print $4}')
    peak_mem=$(echo "$entry" | awk '{print $5}')
    avg_cpu=$(echo "$entry" | awk '{print $6}')
    peak_cpu=$(echo "$entry" | awk '{print $7}')

    printf "%-12s " "$name"

    if [ "$status" = "SUCCESS" ]; then
        echo -e "${GREEN}$status${NC}    $time_ms ms    ${BLUE}${avg_mem} bytes avg / ${YELLOW}${peak_mem} bytes peak    ${avg_cpu}% avg / ${peak_cpu}% peak${NC}"
    else
        echo -e "${RED}$status${NC}      $time_ms ms    ${BLUE}${avg_mem} bytes avg / ${YELLOW}${peak_mem} bytes peak    ${avg_cpu}% avg / ${peak_cpu}% peak${NC}"
    fi
done

echo ""
