#!/usr/bin/env bash
. global/common.sh
noimpl() {
	say "$1 is not currently implemented on your system. an exciting chance for you to contribute, no?"
	exit 1
}

if test "$os$arch$bits" = ""; then
	say "set the following environment variables to the appropriate atoms describing your system. for help determining the appropriate atoms, see libk.md"
	say ' - $os={lin|fbsd|hai|osx…}'
	say ' - $arch={x86|arm|ia64|mips…}'
	say ' - $bits={|32|64…}'
	exit 1
fi

# TODO: make it possible for user to change
#       default set with environment vars
modules=(kcore kmem kstr kio kgraft kfile)

target=$arch.$os
if test "$bits" != ""; then
	target=$target.$bits
fi

case $os in
	lin|?bsd|and|dar|osx) posix=yes; unix=yes;;
	hai) posix=yes; unix=no;;
	*) posix=no; unix=no;;
esac

case $os.$bits in
	win.32) bin_fmt=win32;;
	win.64) bin_fmt=win64;;
	osx.32|dar.32) bin_fmt=macho32;;
	osx.64|dar.64) bin_fmt=macho64;;
	dos.*) bin_fmt=dosexe;;
	none.*) bin_fmt=bin;;
	32) bin_fmt=elf32;;
	64) bin_fmt=elf64;;
esac

# first, we establish the
# parameters of the build
has gcc && _cc=gcc
has clang && _cc=clang
has cc && _cc=cc
cc=${cc:-$_cc}
has m4 && _m4=m4
m4=${m4:-$_m4}
has yasm && asm=yasm
has nasm && asm=nasm
export out=${out:-out}
export gen=${gen:-gen}
library=${libkind:-static} # {static|shared|both}

case $library in
	static) build_static_library=yes
	        build_shared_library=no;;

	shared) build_static_library=no
	        build_shared_library=yes;;

	  both) build_static_library=yes
	        build_shared_library=yes;;
esac

check cc "your C compiler of choice"
check asm "an assembler that takes Intel syntax and nasm-style-macros"
check m4 "the path to your m4 installation"

export build=$(global/build-id.sh)

case $os in
	lin) p_headers_syscall=${p_headers_syscall:-/usr/include/asm/unistd_${bits}.h}
	p_headers_errno=${p_headers_errno:-/usr/include/asm-generic/errno.h /usr/include/asm-generic/errno-base.h};;

	fbsd) p_headers_syscall=${p_headers_syscall:-/usr/include/sys/syscall.h}
	p_headers_errno=${p_headers_errno:-/usr/include/errno.h};;
esac

check p_headers_syscall \
	'the location of a header defining your syscall numbers'
check p_headers_errno \
	'the location of a header defining the values of each errno symbol'

macro_compile_env="-Datom_target_arch=$arch -Datom_target_os=$os -Dtarget_posix=$posix -Dtarget_unix=$unix"

if test "$bits" != ""; then
	macro_compile_env="$macro_compile_env -Datom_target_bits=$bits"
fi

for mod in ${modules[@]}; do
	say "building [1mlibk[21m with [1m$mod[21m"
done

comp_mac() { $m4 $macro_compile_env -I "$gen" $3 "$1" > "$2"; }
comp_asm() { $asm "-f$bin_fmt" "-i$gen" $1 -o "$2"; }
comp_co()  { comp_clib $1 $2 -c; }
comp_clib(){
	src=$1
	output=$2
	flags=$3
	$cc $src $3 -std=c11 -isystem "$out" -isystem "$gen" -isystem "arch/" -fPIC -nostdlib "-L$out" "-o$output"
}

scan() { find "$1" -name "$2"; }

# now we make sure all the appropriate
# directories exist

set -x

# get type data
mkdir -p $gen
$cc -D_emit_m4_include arch/typesize.c -o $gen/typesize 
$gen/typesize > gen/typesize.m

# generate syscall tables
case $os in
	lin) grep -h "#define __NR_" $p_headers_syscall | sed 's;^#define __NR_;;' > $gen/calls.tbl;;
	fbsd) grep -h "#define	SYS_" $p_headers_syscall | sed 's;^#define	SYS_;;' | sed 's;[\t ]\+; ;' > $gen/calls.tbl;;
	*) noimpl 'system call table generation';;
esac

awk -f arch/syscall.awk -v out=s <$gen/calls.tbl >$gen/system_calls.s
awk -f arch/syscall.awk -v out=h <$gen/calls.tbl >$gen/system_calls.h

# generate errno tables
grep -h "#[ 	]*define[ 	]\+E" $p_headers_errno | sed 's;^#[\t ]*define[\t ]\+\(E[A-Z0-9]\+\).*$;k_platform_error_\1 \1;' > $gen/error_names.tbl
cat $p_headers_errno $gen/error_names.tbl | cpp -P >$gen/error_numbers.tbl
awk -f arch/errtbl.awk <$gen/error_numbers.tbl >$gen/error_table.h

# generate symbol tables for error handling functions
mkdir -p $out/k
awk -f global/gen-conds.awk <global/modules >$out/k/internal.egroup.h
awk -f global/gen-ident.awk <global/modules >$gen/internal.ident.c
comp_co $gen/internal.ident.c $out/internal.ident.o

# first pass: copy all raw headers into place
for mod in ${modules[@]}; do
	for h in $(scan $mod '*.h'); do
		base=$(basename $h)
		cp "$h" "$out/k/$base"
	done
done

# second pass: run macro headers through m4 and
# copy the resulting file into place

for mod in ${modules[@]}; do
	for h in $(scan $mod '*.h.m'); do
		base=$(basename $h)
		dest=${base%%.m}
		comp_mac "$h" "$out/k/$dest"
	done
done

# third pass: generate manpage, html, and pdf
# versions of the documentation from the md src

mkdir -p $out/doc/{man,html,pdf}
global/build-manpage.sh libk.md
for mod in ${modules[@]}; do
	for doc in $(scan $mod '*.md'); do
		base="$(basename $doc)"
		stem="${base%%.md}"
		global/build-manpage.sh "$doc"
		# TODO html
		# TODO pdf
	done
done

# fourth pass: compile sources and save the
# resulting object files to $out, tracking
# which is a runtime or function unit. exe's
# will not be compiled until a later pass

fn_objects=()
rt_objects=()
data_objects=( $out/internal.ident.o )
for mod in ${modules[@]}; do
	for fn in $(scan $mod '*.fn.c'); do
		base="$(basename $fn)"
		dest="$out/$mod.${base%%.c}.o"
		comp_co "$fn" "$dest"
		fn_objects+="$dest"
	done

	for rt in $(scan $mod '*.rt.c'); do
		base="$(basename $rt)"
		dest="$out/$mod.${base%%.c}.o"
		comp_co "$rt" "$dest"
		rt_objects+="$dest"
	done

	for fn in $(scan $mod "*.fn.$target.s"); do
		base="$(basename $fn)"
		dest="$out/$mod.${base%%.s}.o"
		comp_asm "$fn" "$dest"
		fn_objects+="$dest"
	done

	for rt in $(scan $mod "*.rt.$target.s"); do
		base="$(basename $rt)"
		dest="$out/$mod.${base%%.s}.o"
		comp_asm "$rt" "$dest"
		rt_objects+="$dest"
	done
done

echo fns: ${fn_objects[@]}
echo rts: ${rt_objects[@]}
