#!/usr/bin/env bash
# configure - Configuration script for ft_ping
# Generates build.mk with build configuration

set -e

# CONSTANTS

SCRIPT_VERSION="1.0.0"
PROJECT_NAME="ft_ping"
BUILD_MK="build.mk"
LIBICMP_DIR="libicmp"
LIBICMP_MAKEFILE="Makefile"

# Default values
DEFAULT_PREFIX="/usr/local"
DEFAULT_CC="clang"
DEFAULT_LIBICMP_MODE="auto"
DEFAULT_LIBRARY_TYPE="shared"
DEFAULT_ENABLE_TESTS="no"
DEFAULT_ENABLE_DEBUG="no"
DEFAULT_ENABLE_SANITIZERS="no"
DEFAULT_ENABLE_LTO="no"
DEFAULT_ENABLE_STRICT="no"
DEFAULT_SUDO_TESTS="yes"
DEFAULT_INSTALL_LIBICMP="no"

# Current configuration
PREFIX="$DEFAULT_PREFIX"
CC="$DEFAULT_CC"
LIBICMP_MODE="$DEFAULT_LIBICMP_MODE"
LIBRARY_TYPE="$DEFAULT_LIBRARY_TYPE"
LIBRARY_PREFERENCE="prefer" # prefer, only
ENABLE_TESTS="$DEFAULT_ENABLE_TESTS"
ENABLE_DEBUG="$DEFAULT_ENABLE_DEBUG"
ENABLE_SANITIZERS="$DEFAULT_ENABLE_SANITIZERS"
ENABLE_LTO="$DEFAULT_ENABLE_LTO"
ENABLE_STRICT="$DEFAULT_ENABLE_STRICT"
SUDO_TESTS="$DEFAULT_SUDO_TESTS"
INSTALL_LIBICMP="$DEFAULT_INSTALL_LIBICMP"
VERBOSE="no"
QUIET="no"

# User-provided flags
USER_CFLAGS=""
USER_LDFLAGS=""
USER_CPPFLAGS=""

# Derived paths
BINDIR=""
LIBDIR=""
INCLUDEDIR=""
LIBICMP_PREFIX=""

# HELPER FUNCTIONS

info() {
	if [ "$QUIET" != "yes" ]; then
		echo -e "[INFO] $*"
	fi
}

success() {
	if [ "$QUIET" != "yes" ]; then
		echo -e "[OK] $*"
	fi
}

warn() {
	echo -e "[WARN] $*" >&2
}

error() {
	echo -e "[ERROR] $*" >&2
}

fatal() {
	error "$@"
	exit 1
}

verbose() {
	if [ "$VERBOSE" = "yes" ]; then
		echo -e "[VERBOSE] $*"
	fi
}

# HELP

show_help() {
	cat << EOF
Usage: ./configure [OPTIONS]

Configuration script for $PROJECT_NAME v$SCRIPT_VERSION

libicmp Options:
  --with-system-libicmp   Use system-installed libicmp (fail if not found)
  --with-local-libicmp    Build local libicmp (ignore system version)
  --auto-libicmp          Auto-detect libicmp (default)

Library Type Options:
  --prefer-static         Prefer static library (.a) with fallback (default if local)
  --prefer-shared         Prefer shared library (.so) with fallback (default if system)
  --static-only           Require static library (fail if unavailable)
  --shared-only           Require shared library (fail if unavailable)

Build Options:
  --enable-debug          Enable debug symbols (-g -O0)
  --disable-debug         Disable debug (default, -O2)
  --enable-sanitizers     Enable AddressSanitizer and UndefinedBehaviorSanitizer
  --enable-lto            Enable Link Time Optimization
  --strict                Enable strict warnings (-Wpedantic -Wconversion etc.)

Installation Paths:
  --prefix=PREFIX         Installation prefix (default: $DEFAULT_PREFIX)
  --bindir=DIR            Binary directory (default: PREFIX/bin)
  --libdir=DIR            Library directory (default: PREFIX/lib)
  --includedir=DIR        Include directory (default: PREFIX/include)
  --libicmp-prefix=PREFIX libicmp installation prefix (default: same as --prefix)

Test Options:
  --enable-tests          Build tests (requires criterion)
  --disable-tests         Don't build tests (default)
  --enable-sudo-tests     Tests require sudo (default)
  --disable-sudo-tests    Tests without sudo (requires capabilities)

libicmp Installation:
  --install-libicmp       Install libicmp to system (if built locally)
  --no-install-libicmp    Don't install libicmp (default)

Compiler Options:
  CC=compiler             Specify compiler (default: $DEFAULT_CC)
  CFLAGS="flags"          Additional compilation flags
  CPPFLAGS="flags"        Additional preprocessor flags
  LDFLAGS="flags"         Additional linker flags

Other Options:
  --help, -h              Show this help message
  --version, -v           Show version
  --verbose               Verbose output during configuration
  --quiet, -q             Quiet mode (minimal output)

Examples:
  ./configure
  ./configure             --with-local-libicmp --static-only
  ./configure             --enable-debug --enable-sanitizers --enable-tests
  ./configure             --prefix=/opt/ft_ping --install-libicmp
  ./configure             CC=gcc --strict

EOF
	exit 0
}

show_version() {
	echo "$PROJECT_NAME configure script version $SCRIPT_VERSION"
	exit 0
}

# ARGUMENT PARSING

parse_arguments() {
	while [ $# -gt 0 ]; do
		case "$1" in
			--help|-h)
				show_help
				;;
			--version|-v)
				show_version
				;;
			--verbose)
				VERBOSE="yes"
				;;
			--quiet|-q)
				QUIET="yes"
				;;
			
			# libicmp mode
			--with-system-libicmp)
				LIBICMP_MODE="system"
				;;
			--with-local-libicmp)
				LIBICMP_MODE="local"
				;;
			--auto-libicmp)
				LIBICMP_MODE="auto"
				;;
			
			# Library type
			--prefer-static)
				LIBRARY_TYPE="static"
				LIBRARY_PREFERENCE="prefer"
				;;
			--prefer-shared)
				LIBRARY_TYPE="shared"
				LIBRARY_PREFERENCE="prefer"
				;;
			--static-only)
				LIBRARY_TYPE="static"
				LIBRARY_PREFERENCE="only"
				;;
			--shared-only)
				LIBRARY_TYPE="shared"
				LIBRARY_PREFERENCE="only"
				;;
			
			# Build options
			--enable-debug)
				ENABLE_DEBUG="yes"
				;;
			--disable-debug)
				ENABLE_DEBUG="no"
				;;
			--enable-sanitizers)
				ENABLE_SANITIZERS="yes"
				;;
			--enable-lto)
				ENABLE_LTO="yes"
				;;
			--strict)
				ENABLE_STRICT="yes"
				;;
			
			# Installation paths
			--prefix=*)
				PREFIX="${1#*=}"
				;;
			--bindir=*)
				BINDIR="${1#*=}"
				;;
			--libdir=*)
				LIBDIR="${1#*=}"
				;;
			--includedir=*)
				INCLUDEDIR="${1#*=}"
				;;
			--libicmp-prefix=*)
				LIBICMP_PREFIX="${1#*=}"
				;;
			
			# Tests
			--enable-tests)
				ENABLE_TESTS="yes"
				;;
			--disable-tests)
				ENABLE_TESTS="no"
				;;
			--enable-sudo-tests)
				SUDO_TESTS="yes"
				;;
			--disable-sudo-tests)
				SUDO_TESTS="no"
				;;
			
			# libicmp installation
			--install-libicmp)
				INSTALL_LIBICMP="yes"
				;;
			--no-install-libicmp)
				INSTALL_LIBICMP="no"
				;;
			
			# Compiler variables
			CC=*)
				CC="${1#*=}"
				;;
			CFLAGS=*)
				USER_CFLAGS="${1#*=}"
				;;
			CPPFLAGS=*)
				USER_CPPFLAGS="${1#*=}"
				;;
			LDFLAGS=*)
				USER_LDFLAGS="${1#*=}"
				;;
			
			*)
				fatal "Unknown option: $1\nTry './configure --help' for more information."
				;;
		esac
		shift
	done
}

# VALIDATION

validate_configuration() {
	verbose "Validating configuration..."
	
	# Check for conflicting options
	if [ "$LIBICMP_MODE" = "system" ] && [ "$INSTALL_LIBICMP" = "yes" ]; then
		warn "Option --install-libicmp ignored when using system libicmp"
		INSTALL_LIBICMP="no"
	fi
	
	# Set derived paths
	[ -z "$BINDIR" ] && BINDIR="$PREFIX/bin"
	[ -z "$LIBDIR" ] && LIBDIR="$PREFIX/lib"
	[ -z "$INCLUDEDIR" ] && INCLUDEDIR="$PREFIX/include"
	[ -z "$LIBICMP_PREFIX" ] && LIBICMP_PREFIX="$PREFIX"
	
	verbose "PREFIX=$PREFIX"
	verbose "BINDIR=$BINDIR"
	verbose "LIBDIR=$LIBDIR"
	verbose "INCLUDEDIR=$INCLUDEDIR"
}

# DETECTION

check_command() {
	local cmd="$1"
	if command -v "$cmd" >/dev/null 2>&1; then
		return 0
	else
		return 1
	fi
}

check_compiler() {
	info "Checking for compiler: $CC"
	
	if ! check_command "$CC"; then
		fatal "Compiler '$CC' not found"
	fi
	
	# Get compiler version
	local cc_version
	cc_version=$($CC --version 2>&1 | head -n1)
	success "Found $cc_version"
	
	verbose "Compiler: $CC"
	verbose "Version: $cc_version"
}

detect_system_libicmp() {
	local system_libdir="${PREFIX}/lib"
	local system_includedir="${PREFIX}/include"
	
	verbose "Searching for system libicmp in $system_libdir"
	
	SYSTEM_LIBICMP_STATIC="${system_libdir}/libicmp.a"
	SYSTEM_LIBICMP_SHARED="${system_libdir}/libicmp.so"
	SYSTEM_ICMP_H="${system_includedir}/icmp.h"
	
	HAS_STATIC="no"
	HAS_SHARED="no"
	HAS_HEADERS="no"
	
	if [ -f "$SYSTEM_LIBICMP_STATIC" ]; then
		HAS_STATIC="yes"
		verbose "Found static library: $SYSTEM_LIBICMP_STATIC"
	fi
	
	if [ -f "$SYSTEM_LIBICMP_SHARED" ]; then
		HAS_SHARED="yes"
		verbose "Found shared library: $SYSTEM_LIBICMP_SHARED"
	fi
	
	if [ -f "$SYSTEM_ICMP_H" ]; then
		HAS_HEADERS="yes"
		verbose "Found headers: $SYSTEM_ICMP_H"
	fi
	
	if [ "$HAS_HEADERS" = "yes" ] && { [ "$HAS_STATIC" = "yes" ] || [ "$HAS_SHARED" = "yes" ]; }; then
		return 0 # System libicmp found
	else
		return 1 # System libicmp not found
	fi
}

decide_libicmp_source() {
	info "Determining libicmp source..."
	
	case "$LIBICMP_MODE" in
		system)
			verbose "Mode: force system libicmp"
			if detect_system_libicmp; then
				success "Using system-installed libicmp from $PREFIX/lib"
				USE_SYSTEM_LIBICMP="yes"
				BUILD_LOCAL_LIBICMP="no"
			else
				fatal "System libicmp not found but --with-system-libicmp specified"
			fi
			;;
		
		local)
			verbose "Mode: force local build"
			success "Building local libicmp from $LIBICMP_DIR/"
			USE_SYSTEM_LIBICMP="no"
			BUILD_LOCAL_LIBICMP="yes"
			;;
		
		auto)
			verbose "Mode: auto-detect"
			if detect_system_libicmp; then
				success "Using system-installed libicmp from $PREFIX/lib"
				USE_SYSTEM_LIBICMP="yes"
				BUILD_LOCAL_LIBICMP="no"
			else
				info "System libicmp not found, will build local version"
				USE_SYSTEM_LIBICMP="no"
				BUILD_LOCAL_LIBICMP="yes"
			fi
			;;
	esac
}

determine_library_type() {
	info "Determining library type..."
	
	if [ "$USE_SYSTEM_LIBICMP" = "yes" ]; then
		# System library - check what's available
		case "$LIBRARY_TYPE" in
			static)
				if [ "$HAS_STATIC" = "yes" ]; then
					FINAL_LIBRARY_TYPE="static"
					LIBICMP_PATH="$SYSTEM_LIBICMP_STATIC"
					success "Using static system library"
				elif [ "$LIBRARY_PREFERENCE" = "only" ]; then
					fatal "Static library required but only shared library found"
				elif [ "$HAS_SHARED" = "yes" ]; then
					warn "Static library not found, falling back to shared"
					FINAL_LIBRARY_TYPE="shared"
					LIBICMP_PATH="$SYSTEM_LIBICMP_SHARED"
				else
					fatal "No suitable library found"
				fi
				;;
			shared)
				if [ "$HAS_SHARED" = "yes" ]; then
					FINAL_LIBRARY_TYPE="shared"
					LIBICMP_PATH="$SYSTEM_LIBICMP_SHARED"
					success "Using shared system library"
				elif [ "$LIBRARY_PREFERENCE" = "only" ]; then
					fatal "Shared library required but only static library found"
				elif [ "$HAS_STATIC" = "yes" ]; then
					warn "Shared library not found, falling back to static"
					FINAL_LIBRARY_TYPE="static"
					LIBICMP_PATH="$SYSTEM_LIBICMP_STATIC"
				else
					fatal "No suitable library found"
				fi
				;;
		esac
	else
		# Local build - we control what to build
		FINAL_LIBRARY_TYPE="$LIBRARY_TYPE"
		
		if [ "$FINAL_LIBRARY_TYPE" = "static" ]; then
			LIBICMP_BUILD_STATIC="yes"
			LIBICMP_BUILD_SHARED="no"
			LIBICMP_PATH="$LIBICMP_DIR/libicmp.a"
			success "Will build static library"
		else
			LIBICMP_BUILD_STATIC="no"
			LIBICMP_BUILD_SHARED="yes"
			LIBICMP_PATH="$LIBICMP_DIR/libicmp.so"
			success "Will build shared library"
		fi
	fi
	
	verbose "Final library type: $FINAL_LIBRARY_TYPE"
	verbose "Library path: $LIBICMP_PATH"
}

check_test_dependencies() {
	if [ "$ENABLE_TESTS" = "yes" ]; then
		info "Checking test dependencies..."
		
		# Check for criterion library
		if ! $CC -lcriterion -o /dev/null -xc - >/dev/null 2>&1 <<< "int main(){return 0;}"; then
			warn "Criterion library not found, tests may fail to build"
			warn "Install criterion: https://github.com/Snaipe/Criterion"
		else
			success "Found criterion library"
		fi
	fi
}

# BUILD.MK GENERATION

generate_build_mk() {
	info "Generating $BUILD_MK..."
	
	local timestamp
	timestamp=$(date '+%Y-%m-%d %H:%M:%S')
	
	# Prepare compiler flags
	local cflags="-Wall -Wextra -Werror -pipe"
	local cppflags="-std=c99 -I includes -D_GNU_SOURCE"
	local ldflags=""
	local debug_flags=""
	local sanitizer_flags=""
	local lto_flags=""
	local strict_flags=""
	
	# Debug flags
	if [ "$ENABLE_DEBUG" = "yes" ]; then
		debug_flags="-g -O0 -DDEBUG"
	else
		debug_flags="-O2 -DNDEBUG"
	fi
	
	# Sanitizer flags
	if [ "$ENABLE_SANITIZERS" = "yes" ]; then
		sanitizer_flags="-fsanitize=address,undefined -fno-omit-frame-pointer"
	fi
	
	# LTO flags
	if [ "$ENABLE_LTO" = "yes" ]; then
		lto_flags="-flto"
	fi
	
	# Strict flags
	if [ "$ENABLE_STRICT" = "yes" ]; then
		strict_flags="-Wpedantic -Wconversion -Wshadow -Wcast-align -Wstrict-prototypes"
	fi
	
	# libicmp-specific configuration
	if [ "$USE_SYSTEM_LIBICMP" = "yes" ]; then
		LIBICMP_DEP=""
		ldflags="-licmp"
	else
		LIBICMP_DEP="$LIBICMP_PATH"
		cppflags="$cppflags -I $LIBICMP_DIR/includes"
		ldflags="-L$LIBICMP_DIR -licmp"

		if [ "$FINAL_LIBRARY_TYPE" = "shared" ] && [ "$INSTALL_LIBICMP" = "yes" ]; then
			ldflags="$ldflags -Wl,-rpath,$LIBDIR"
		fi
	fi
	
	# User-provided flags
	if [ -n "$USER_CFLAGS" ]; then
		cflags="$cflags $USER_CFLAGS"
	fi
	if [ -n "$USER_CPPFLAGS" ]; then
		cppflags="$cppflags $USER_CPPFLAGS"
	fi
	if [ -n "$USER_LDFLAGS" ]; then
		ldflags="$ldflags $USER_LDFLAGS"
	fi
	
	# Write build.mk
	cat > "$BUILD_MK" << EOF
# $BUILD_MK - Generated by configure on $timestamp
# DO NOT EDIT - This file is automatically generated


# libicmp Configuration

USE_SYSTEM_LIBICMP = $USE_SYSTEM_LIBICMP
BUILD_LOCAL_LIBICMP = $BUILD_LOCAL_LIBICMP
LIBICMP_TYPE = $FINAL_LIBRARY_TYPE
LIBICMP_DIR = $LIBICMP_DIR
LIBICMP_PATH = $LIBICMP_PATH
LIBICMP_DEP = $LIBICMP_DEP

EOF

	if [ "$BUILD_LOCAL_LIBICMP" = "yes" ]; then
		local libicmp_cflags="-Wall -Wextra -Werror -pipe -Wpedantic -Wconversion"
		local libicmp_cppflags="-std=c99 -I includes -D_POSIX_C_SOURCE=199309L"

		libicmp_cflags="$libicmp_cflags $debug_flags"

		if [ "$ENABLE_SANITIZERS" = "yes" ]; then
			libicmp_cflags="$libicmp_cflags $sanitizer_flags"
		fi

		if [ "$ENABLE_LTO" = "yes" ]; then
			libicmp_cflags="$libicmp_cflags $lto_flags"
		fi

		cat >> "$BUILD_MK" << EOF
# libicmp build configuration
LIBICMP_BUILD_STATIC = $LIBICMP_BUILD_STATIC
LIBICMP_BUILD_SHARED = $LIBICMP_BUILD_SHARED
LIBICMP_CC = $CC
LIBICMP_CPPFLAGS = $libicmp_cppflags
LIBICMP_CFLAGS = $libicmp_cflags
LIBICMP_BUILD_FLAGS = BUILD_STATIC=\$(LIBICMP_BUILD_STATIC) BUILD_SHARED=\$(LIBICMP_BUILD_SHARED) CC=\$(LIBICMP_CC) CPPFLAGS="\$(LIBICMP_CPPFLAGS)" CFLAGS="\$(LIBICMP_CFLAGS)"
LIBICMP_MAKEFILE = $LIBICMP_MAKEFILE

EOF
	fi

	cat >> "$BUILD_MK" << EOF

# Compiler Configuration

CC = $CC
CPPFLAGS = $cppflags
CFLAGS = $cflags
LDFLAGS = $ldflags

# Individual flag components (for reference)
DEBUG_FLAGS = $debug_flags
SANITIZER_FLAGS = $sanitizer_flags
LTO_FLAGS = $lto_flags
STRICT_FLAGS = $strict_flags

# Combined flags
CFLAGS += \$(DEBUG_FLAGS) \$(SANITIZER_FLAGS) \$(LTO_FLAGS) \$(STRICT_FLAGS)
LDFLAGS += \$(SANITIZER_FLAGS) \$(LTO_FLAGS)


# Installation Paths

PREFIX = $PREFIX
BINDIR = $BINDIR
LIBDIR = $LIBDIR
INCLUDEDIR = $INCLUDEDIR


# Test Configuration

ENABLE_TESTS = $ENABLE_TESTS
SUDO_TESTS = $SUDO_TESTS
TEST_LDFLAGS = -lcriterion


# Build Options

ENABLE_DEBUG = $ENABLE_DEBUG
ENABLE_SANITIZERS = $ENABLE_SANITIZERS
ENABLE_LTO = $ENABLE_LTO
ENABLE_STRICT = $ENABLE_STRICT


# libicmp Installation (if built locally)

INSTALL_LIBICMP = $INSTALL_LIBICMP
LIBICMP_PREFIX = $LIBICMP_PREFIX

EOF

	success "Generated $BUILD_MK"
}


# SUMMARY


show_summary() {
	if [ "$QUIET" = "yes" ]; then
		return
	fi
	
	cat << EOF

------------------------------------------------------------------------
Configuration Summary for $PROJECT_NAME
------------------------------------------------------------------------

libicmp Source:          $([ "$USE_SYSTEM_LIBICMP" = "yes" ] && echo "system ($PREFIX/lib)" || echo "local ($LIBICMP_DIR/)")
Library Type:            $FINAL_LIBRARY_TYPE
Compiler:                $CC
Installation Prefix:     $PREFIX

Build Options:
  Debug:                 $ENABLE_DEBUG
  Sanitizers:            $ENABLE_SANITIZERS
  LTO:                   $ENABLE_LTO
  Strict:                $ENABLE_STRICT
  Tests:                 $ENABLE_TESTS$([ "$ENABLE_TESTS" = "yes" ] && echo " ($([ "$SUDO_TESTS" = "yes" ] && echo "with sudo" || echo "without sudo"))" || echo "")

EOF

	if [ "$BUILD_LOCAL_LIBICMP" = "yes" ]; then
		cat << EOF
libicmp Installation:    $([ "$INSTALL_LIBICMP" = "yes" ] && echo "yes (to $LIBICMP_PREFIX)" || echo "no (local use only)")

EOF
	fi

	cat << EOF
Compiler Flags:
  CPPFLAGS:              $cppflags
  CFLAGS:                -Wall -Wextra -Werror -pipe $debug_flags $sanitizer_flags $lto_flags $strict_flags
  LDFLAGS:               $ldflags

------------------------------------------------------------------------

Configuration saved to $BUILD_MK

EOF
}

# MAIN

main() {
	parse_arguments "$@"
	validate_configuration
	check_compiler
	decide_libicmp_source
	determine_library_type
	check_test_dependencies
	generate_build_mk
	show_summary
	
	exit 0
}

main "$@"
