#!/bin/bash
# advbld: copyright Mike Arnautov 2014-2024, licensed              
# under GPL (version 3 or later) or the Modified BSD Licence, 
# whichever is asserted by the supplied LICENCE file.   
#
# Builds an A-code game from sources (A-code or derived C ones).
#
CC=cc     # Select the C compiler
CPP=c++   # Only required for the Qt5 build.
#
# Check if the C compiler is available
! which $CC &>/dev/null && echo "$CC not found." && exit 1 
#
# Set ADV, BIN, ADIR, KDIR and define some functions.
# Source the common library functions
. $(dirname `which $0`)/lib.sh
#
# Script version and architecture detection
version=3.3
case "$(uname -m)" in
  *64*) arch=64 ;;
  *) arch=32 ;;
esac
# Initialize script variables
name=               # Game name to build
plain=              # Plain text flag (no encryption)
mode=               # Build mode flags
stable=0            # Version stability preference (-1=unstable, 0=auto, 1=stable)
build=YES           # Whether to build executable
translate=YES       # Whether to translate A-code to C
xref=               # Cross-reference generation flag
debug=              # Debug mode flags
query=YES           # Interactive query mode
wiz=NO              # Wizard mode (debug commands)
flags=              # C compiler flags
vrb=1               # Verbosity level (0=quiet, 1=normal, 2=verbose)
type=B              # Build type (B=browser/console, C=console, J=JavaScript, etc.)
exetype='BROWSER/CONSOLE executable'  # Description of executable type
readline=           # Readline library configuration
#-----------------------------------------------------------------------------
#
usage()
{
   cat <<EOT
Usage: $0 [<game>] [<options>]

The <game> parameter is either a pathname of the main source file
(the .acd suffix is optional), or the name of the game to be built.
Default <game> is the name of the current directory (less the version number,
if any; so if the directory is e.g. adv770-2.20, adv770 will be built).
If a game name is specified, it is searched for in in the current directory,
then in the directory specified by the global variable ADVDIR, if this is set, 
and finally in directories one and two levels above this script.

C sources are created in the the C subdirectory of the directory containing
the located game source. 

Options specifying general build type. Except for -W they are mutually
exclusive, overriding any previously specified type.

  -B ........... default; build combined console/browser executable
  -C ........... build create a console-only executable
  -J ........... build a JavaScript version (.html)
  -L ........... build a test executable using the library mode
  -Q ........... build a Qt5 executable
  -T ........... build a single turn executable
  -W ........... include opt/debug.acd (if exists) for "wizard" in-game commands

Options affecting the behaviour of this script.

  -q ........... be quiet (opposite of -v)
  -v ........... be verbose (opposite of -q)
  -x ........... echo commands being executed
  -h ........... print this text

The rest of options are of use only to an A-code game developer. 

Versions of acdc, kernel and game are deemed "unstable" if the relevant
directories lack a version number. The script defaults to using unstable
versions, if present, and the latest (highest version number) versions
otherwise.

  -s ........... insist on using latest stable versions
  -u ........... insist on using unstable versions
  -a <version> . use the specified version of acdc
  -k <version> . use the specified version of the A-code kernel

Other build options.

  -g ........... create a gdb-instrumented executable
  -gg .......... As -g but with gcc macro capability (forces CC=gcc)
  -D <symbol> .. add a symbol to the C compilation/linking command
  --m32 ........ on a 64 bit machine, force building 32 bit executable
  -p ........... (plain) don't encrypt game data
  -d ........... acdc's -debug -- echos to stderr A-code lines being executed
  -c ........... translate A-code to C but do not buid executable
  -b ........... build from pre-existing C sources (don't translate A-code to C)
  -w ........... show acdc warnings (these are suppressed by default)
  -X ........... generate cross-reference files for the A-code game source

EOT
   exit 1
}
#-----------------------------------------------------------------------------
# Parse command line arguments
# Pre-process the command line to handle combined short options
set -- $(comline 'akD' $@)
warn=NO                 # No acdc warnings by default
# Process each command line argument
while [ $# -gt 0 ]; do
  case "$1" in 
    -B) type=B; exetype='HTTP/CONSOLE executable' ;;
    -C) type=C; exetype='CONSOLE-only executable' ;;
    -J) ! which emcc 2>/dev/null && \
        echo === Cannot build HTML/Javascript without emcc && exit 1
        type=J ; query=NO ;;
    -L) type=L; flags="$flags -DADVLIB"; query=NO;
        exetype='ADVLIB test executable' ;;
    -Q) type=Q; flags="$flags -DADVLIB -DQT"; query=NO
        exetype="Qt5 executable" ;;
    -T|-S) type=T; flags="$flags -DTURN"; query=NO; readline='-DNO_READLINE'
        exetype='SINGLE TURN executable' ;;
    -W) wiz=YES ;;
    -q) vrb=0 ;;
    -v) vrb=2 ;;
    -x) set -x ;;
    -h|--help) usage ;;
    -p) plain=-plain ;;
    -d) debug="$debug -d" ;;
    -b) translate=NO ;;
    -c) build=NO ;;
    -w) warn=YES ;;
    -X) xref=-x ;;
    -D) [ "$2" = 'NO_READLINE' ] && readline="$1$2" || flags="$flags $1$2"
        shift ;;
    -s) stable=1 ;;
    -u) stable=-1 ;;
    -a) shift; AVER="$1"; suptype="$suptype acdc-$1," ;;
    -k) shift; KVER="$1"; suptype="$suptype kernel-$1," ;;
    -g) [ -n "$dbg" ] && CC=gcc && dbg="-g -gdwarf-2 -g3" || dbg=-g ;;
    --m32) flags="$flags -m32"; arch=32; CPPFLAGS=-m32 ;;
    --fread) mode="$mode -file-read" ;;        # Deprecated
    --fmemory) mode="$mode -file-memory" ;;    # Deprecated
    --fpage) mode="$mode -file-page" ;;        # Deprecated
    --locates) flags="$flags -DLOC_STATS=2" ;; # Deprecated
    -*) echo Unknown keyword $1. Use -h to see usage details. && exit 1;;
    *) [ -n "$name" ] && echo Bad command line. && exit 1; name="$1" ;;
  esac
  shift
done
#
[ "$type" = J ] && dbg= && debug=
[ $warn = NO ] && mode="$mode -no-warnings"
#
[ $vrb != 0 ] && echo && echo "[A-code adventure builder, version $version]"
#
#-----------------------------------------------------------------------------
# Find and check the acdc (A-code compiler) and kernel versions to be used
# Determine stability preference message
stb=''
[ $stable = -1 ] && stb=' unstable'
[ $stable = 1 ] && stb=' stable'
# Set acdc directory name based on version specification
[ -z "$AVER" ] && acdc=acdc || acdc=acdc-$AVER 
# Find the A-code compiler directory
ADIR=$(find_it $stable $acdc)
[ -z "$ADIR" ] && echo Unable to find$stb translator executable. && exit 1
# Build acdc if executable doesn't exist
[ ! -x "$ADIR/acdc" ] && echo Building acdc... && (cd $ADIR; ./build -cc $CC)
[ ! -x "$ADIR/acdc" ] && echo No acdc executable in $ADIR! && exit 1
#
#
# Set kernel directory name and find the A-code kernel
[ -z "$KVER" ] && kernel=kernel || kernel=kernel-$KVER
KDIR=$(find_it $stable $kernel)
[ -z "$KDIR" ] && echo Unable to find$stb A-code kernel! && exit 1
# Get kernel version information
IKVER=$(kversion $KDIR)
#-----------------------------------------------------------------------------
# Find the game source file to build
# Locate the game source file
GAME=$(find_it $stable $name)
if [ -z "$GAME" ]; then
  # If no name specified, use current directory name
  [ -z "$name" ] && name=$(basename $(pwd))
  echo Unable to find$stb A-code source file for $name. && exit 1
fi
#
# Extract game directory and source file information
GDIR=$(dirname $GAME)
srcfile=$(basename $GAME)
[ -z "$name" ] && name=$(basename $GDIR) 
name=${name%%.acd}    # Strip off the .acd suffix, if there
#-----------------------------------------------------------------------------
# Set various build mode flags and variables based on options
# Set console-only flag for C build type
[ "$type" = C ] && flags="$flags -DCONSOLE";
# Add quiet flag if not in verbose mode
[ "$vrb" != 2 ] && mode="$mode -q"
# Build supplementary type description for output
[ "$wiz" = YES ] && suptype=" wizard mode,"
[ -n "$dbg" ] && suptype="$suptype gdb,"
[ -n "$debug" ] && suptype="$suptype embedded A-code,"
[ -n "$plain" ] && suptype="$suptype plain text,"
suptype=${suptype:0:$(expr ${#suptype} - 1)}
#
[ "$stable" = 1 ] && vstate='(stable)'
#
[ "$type" != J ] && case $arch in
  *32*) exetype="$exetype (32 bit)" ;;
  *64*) exetype="$exetype (64 bit)" ;;
  *) ;;
esac
[ $vrb != 0 ] && echo "Building $name $exetype $vstate"
name=$(basename $name)
name=`echo $name | sed s/-[0-9]*.[0-9]*//`
#
# Verify game directory exists and change to it
if [ ! -d "$GDIR" ]; then
   echo Not found: $GDIR
   exit 1
fi
cd "$GDIR"
#-----------------------------------------------------------------------------
# Point to the single-turn game version, if necessary
# Some games have special CGI versions for single-turn mode
srcdir=.
#if [ "$query" = NO ]; then
#  [ "$name" = adv550 ] && srcfile=cgi/cgi_adv550.acd && srcdir=cgi
#  [ "$name" = adv580 ] && srcfile=cgi/cgi_adv580.acd && srcdir=cgi
#  [ "$name" = adv660 ] && srcfile=cgi/cgi_adv660.acd && srcdir=cgi
#fi
#-----------------------------------------------------------------------------
# Configure readline library support for command line editing
# Check for readline and ncurses libraries if not disabled
if [ -z "$readline" ]; then
   findlibs $arch readline ncurses  # Sets $libs and $mislib
  [ -n "$mislib" ] && findlibs $arch readline ncursesw
  case "$libs" in
    *-lreadline*-lncurses*) readline="$libs" ;;
    *) readline=-DNO_READLINE
       LIBWARN='WARNING: command line editing not supported.'
    ;;
  esac
fi
#
[ -n "$debug" ] && DEBUG='=-DDEBUG -g'
#-----------------------------------------------------------------------------
# Ensure the C build subdirectory exists and is clean
# Create or clean the C directory for generated sources
if [ -d C ]; then
  if [ $translate = YES ]; then
    rm -rf C/*
  fi
else
  mkdir C
fi
# Remove any previous build log
rm -f bld.log
#
[ $vrb != 0 ] && echo [A-code kernel, version $IKVER] 
#-----------------------------------------------------------------------------
# Translate A-code source to C, unless building from existing C sources
if [ "$translate" = YES ]; then
#
  # Add wizard mode debug commands if requested, otherwise ensure they're absent
  if [ $wiz = YES ]; then
    # Copy wizard mode debug commands if available
    [ -r opt/debug.acd ] && cp opt/debug.acd $srcdir/
  else
    # Remove any existing debug commands
    rm -f $srcdir/debug.acd
  fi
  # Show acdc version and translate A-code to C
  $ADIR/acdc -v
  ! $ADIR/acdc $srcfile -o ./C $xref $debug $plain $mode &>bld.log && \
    ( [ $vrb != 0 ] && cat bld.log ) && exit 1
  # Clean up temporary files
  rm -f $srcdir/debug.acd bld.log
#
  # Copy the A-code kernel files to the C directory
  cp $KDIR/adv0* C/
  [ "$type" = L ] && cp $KDIR/extras/libtest.c C/
fi
#-----------------------------------------------------------------------------
# Sort cross-reference files if Perl is available and cross-refs were generated
if [ -n "$xref" -a -n "$(which perl)" ]; then
  [ $srcdir != . ] && mv $srcdir/*.xrf ${name}.xrf
  $BIN/sortxrefs $name.xrf &>bld.log
  [ $vrb = 2 ] && cat bld.log
  rm bld.log
fi
#
[ "$build" != 'YES' ] && exit 0
#-----------------------------------------------------------------------------
# Check if UTF-8 support is needed and configure utf8proc library
# Check if UTF-8 support is enabled in the generated code
grep UTF8 C/adv1.h | grep -q 1
if [ $? = 0 ]; then
  ULIB="$ADV/lib$arch"
  if [ ! -r "$ULIB/libutf8proc.c" ]; then
    flags="$flags -I $ADV/utf8proc $ADV/utf8proc/utf8proc.c"
  else
    echo Unable to find the utf8proc library. && exit 1  
  fi
fi
#   if [ ! -r "$ULIB/libutf8proc.a" ]; then
#     [ -r "$ADV"/utf8proc/Makefile ] &&
#     (
#       echo Building utf8proc library...
#       cd "$ADV"/utf8proc/;
#       make clean >/dev/null
#       CFLAGS="-O2 -m$arch" LDFLAGS=-m$arch make libutf8proc.a >/dev/null
#       cp libutf8proc.a "$ULIB/"
#     )
#   fi
#   [ ! -r "$ULIB/libutf8proc.a" ] && \
#     echo Unable to find/build the utf8proc library. && exit 1  
# #  cp "$ADV/utf8proc/utf8proc.h $ULIB/
# #  cp "$ADV/utf8proc/libutf8proc.a $ULIB/
#   flags="$flags -I $ADV/utf8proc -L $ULIB -lutf8proc"
# fi
#-----------------------------------------------------------------------------
# Now compile and link the final executable
[ "$type" = C ] && [ "$readline" = '-DNO_READLINE' ] && \
  exetype="$exetype without command editing"
[ -n "$suptype" ] && [ $vrb != 0 ] && echo "Modifiers:$suptype."
#-----------------------------------------------------------------------------
# JavaScript/HTML build using Emscripten
if [ "$type" = J ]; then
   [ $vrb = 0 ] || echo Creating a JAVASCRIPT/HTML build
   # Change to C directory for emscripten compilation
   cd C
#   [ -r /opt/emsdk/emsdk_env.sh ] && . /opt/emsdk/emsdk_env.sh &>/dev/null
   ! emcc -Os -s ASM_JS=1 -s WASM=0 -s ENVIRONMENT=web $flags \
     -DJS adv*.c  -Wno-parentheses-equality -lidbfs.js \
       -s "EXPORTED_RUNTIME_METHODS=['cwrap']" \
         -o $name.js -s EXPORTED_FUNCTIONS="['_advturn']" &>bld.log && \
           cat bld.log && exit 1
   rm bld.log
   [ $vrb != 0 ] && echo Merging $name.js into $name.html...
# 
   # Merge the generated JavaScript with the HTML template
   RIFS="$IFS"
   IFS=
   while read line; do
     case "$line" in
       *\%JAVASCRIPT\%*) cat $name.js ;;
       *\%NAME\%*) echo "${line/\%NAME\%/$name}" ;;
       *) echo "$line";;
     esac
   done <$KDIR/extras/acode.html >$name.html
   IFS="$RIFS"
   rm $name.js && mv $name.html ../
   echo Created $name.html $vstate
   [ $vrb != 0 ] && echo
   exit
fi
#-----------------------------------------------------------------------------
# Qt5 GUI build using qmake and make
if [ $type = Q ]; then
  # Check whether we have the necessary Qt5 development tools
  # findlibs sets the libs and mislibs variables
  ! which $CPP &>/dev/null && echo Need $CPP for a QT build. && exit 1
  ! which qmake &>/dev/null && echo Need qmake for a QT build. && exit 1
  ! which make &>/dev/null && echo Need make for a QT build. && exit 1
  findlibs $arch Qt5Core Qt5Widgets Qt5Gui Qt5WebKit Qt5WebKitWidgets pthread
  [ "$mislib" != '' ] && echo Missing $arch bit libraries: $mislib && exit 1
  cd C
  cp $KDIR/extras/*.{h,cpp} ../extras/advinfo.h .
  cp $KDIR/extras/acode.pro $name.pro
  qmake
  ! make -j $(nproc) >/dev/null 2>bld.log && cat bld.log && exit 1
  rm -f bld.log
  strip $name
  mv $name $GDIR/$name.qt5
  exit 0
fi
#-----------------------------------------------------------------------------
# Standard C compilation (console, browser/console, library, or single-turn)
# Compile and link the C sources
[ $vrb != 0 ] && echo "Compiling and linking $exetype"
[ -n "$LIBWARN" ] && echo $LIBWARN  
! $CC ./C/*.c $flags $dbg $readline -o $name $CFLAGS &>bld.log \
  && cat bld.log && exit 1
rm -f bld.log
#
# Copy the game data file if one was created during compilation
[ -f C/$name.dat ] && cp C/$name.dat .
# Strip debug symbols unless debugging is enabled
[ -z "$dbg" ] && strip $name
echo "Created $name $vstate"
[ $vrb != 0 ] && echo
#-----------------------------------------------------------------------------
exit      # All done!
#
############################### End of script ################################
