#!/bin/bash
# advmake: copyright Mike Arnautov 2014-2016, 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 C ones).
#
echo
echo '[A-code adventure builder, version 2.3]'
#
# Set ACDC, ADV, BIN and KERNEL.
#
BIN=$(dirname $(which $0))
[ "$BIN" = '.' ] && BIN=$(pwd)    # Needed for MacOS
ADV=$(dirname $BIN)
KERNEL="$ADV/kernel"
ACDC="$ADV/acdc"
#
# Sanitise PATH, allowing for emscripten.
#
cline="$@"    # Preserve the command line
RIFS="$IFS"
IFS=: 
set -- $PATH  # Split PATH
IFS="$RIFS"
PATH=''
while [ $# -gt 0 ]; do
  case "$1" in
    /bin|/usr*|*/ems*) 
      [ -n "$PATH" ] && PATH="$PATH:"
      PATH="$PATH$1"
    ;;
    *) ;;
  esac
  shift
done
PATH="$PATH:$BIN"
set -- $cline     # Restore command line
#
# Find the highest versioned file/dir of the specified stem.
#
latest()
{
  local mode='un'
  local stable=FALSE
  if [ $# -eq 3 -a "$3" = '-s' ]; then
    mode=
    stable=TRUE
  else
    if [ $# -ne 2 ]; then
      >&2 echo '=== Usage: latest <variable> <target> [-s]'
      exit
    fi
  fi
  local retvar="$1"
  local arg="$2"
  local dir="$ADV/$2"
  if [ $stable = TRUE -o ! -d "$dir" ]; then
    local list="$dir-*"
    if [ -z "$list" ]; then
      >&2 echo === No ${mode}stable $arg version found!
      exit
    fi
    local minor=0
    local major=0
    for item in "$dir"-*; do
      if [ -d $item ]; then
        local name=$(basename $item)
        name=${name/"$arg"-/};
        name=${name/./ }
        RIFS="$IFS"
        IFS=' '
        set -- $name
        IFS="$RIFS"
        [ $1 -gt $major -o $1 -eq $major -a $2 -gt $minor ] &&           major=$1 && minor=$2 && eval "$retvar='$1.$2'"
      fi
    done
  fi
}
#
# From here on we suppose that $ACDC and $KERNEL have been set as appropriate
# (e.g. by a call to latest()) for the stable or unstable mode.
#
# Get the actual version info of acdc or kernel as embedded in sources.
#
function version_info
{
  local type=version
  local string=
  local file=
  local retvar=$1
  shift
  while [ $# -gt 0 ]; do
    case "$1" in
      date|version) type=$1 ;;
      acdc) file="$ACDC/acdc.c"; string=ACDC_VERSION ;;
#      kernel) file="$KERNEL/advkern.c"; string=KERNEL_VERSION ;;
      kernel) file="$KERNEL/adv00.c"; string=KERNEL_VERSION ;;
      *) >&2 echo '=== Usage: {varname} version_info {acdc|kernel} [version|date]'
         exit ;; 
    esac
    shift
  done
  if [ -r "$file" ]; then
#
# Can't just set IFS as a part of the set command because of the double
# quote involved -- it's a bug in bash!
#
    RIFS="$IFS"
    IFS=' ",'
    set -- $(grep "$string" "$file")
    IFS="$RIFS"
    [ "$type" = date ] && eval "$retvar='$4 $5 $6'" || eval "$retvar='$3'"
  else
    ( >&2 echo '===' No such file: $file )
    exit
  fi
}
#
#########################################################################
#
ADVDFLT=$(basename `pwd`)   # Default game to build
#
arch="$(uname -m)"
name=''
plain=''
mode=''
build=YES
translate=YES
debug=''
advlib=''
js=''
wiz=NO
style='12'
state=c
src=C
flags=''
quiet=YES
console=YES
exetype='CONSOLE-only executable'
readline=
#
usage()
{
   cat <<EOT
Usage: $0 [<game>] [<options>]

Default <game> is the name of the current directory.
Default build (with no options) is a console-only executable.
Options must be given separately (not Unix-style). Possible ones are:

  -B or -H ..... create combined console/browser (HTTP) executable
  -C ........... default; create a console-only executable (no HTTP)
  -A or -LIB ... create a test executable using the library mode
  -J or -HTML .. create a JavaScript version
  -T or -S ..... create a single turn (cloud) mode executable
  -W ........... include opt/debug.acd if it exists

  -s ........... use the latest *stable* versions of acdc and kernel
  -m32 ......... force building 32 bit executable, if possible
  -m64 ......... force building 64 bit executable, if possible
  -g ........... create a gdb-instrumented executable
  -D<symbol> ... add a symbol to the C compilation command
  -L<pathname> . add a library search pathname to the C compilation command

  -t ........... equivalent to -g -DDEBUG 
  -p ........... (plain) don't encrypt game data
  -d ........... acdc's -debug -- adds A-code lines as comments in C
  -c ........... translate A-code to C but do not buid executable
  -nc .......... don't translate A-code to C but do build executable
  -w ........... show acdc warnings, which aresuppressed by default
  -v ........... show progress info
  -u ........... create and use uglified C sources
  -dkm ......... if uglifying, dump keyword mappings list in $ETC

  -fr .......... use .dat file and read from it as necessary (deprecated)
  -fm .......... use .dat file and read it into memory on start (deprecated)
  -fp .......... use .dat file and page from it (deprecated)
  -l ........... if paging (-fp) enable locate request stats reporting

  -x ........... echo commands being executed
  -h ........... print this text

EOT
   exit 1
}
#
# Parse command line
#
warn=NO
while [ $# -gt 0 ]; do
  case "$1" in 
    -fr) mode="$mode -file-read"   ;;
    -fm) mode="$mode -file-memory" ;;
    -fp) mode="$mode -file-page"   ;;
    -w) warn=YES ;;
    -v*) quiet=NO ;;
    -p*) plain=-plain ;;
    -c) build=NO ;;
    -nc) translate=NO ;;
    -CL*|-CGI|-T|-S) flags="$flags -DTURN";
                exetype='SINGLE TURN executable' ;;
    -C|-CO*) console=YES; exetype='CONSOLE-only executable' ;;
    -B*|-HTTP) console=NO; exetype='HTTP/CONSOLE executable' ;;
    -A*|-LIB*) flags="$flags -DADVLIB"; advlib=YES; 
             console=NO; exetype='ADVLIB test executable' ;;
    -J*|-HTML) type emcc >/dev/null 2>&1; if [ $? -ne 0 ]; then
                >&2 echo === Cannot build HTML/Javascript without emcc
                echo
                exit 1;
              fi
              js=YES; console=NO; exetype='JavaScript version' ;;
    -D*|-L*) [ "$1" = '-DNO_READLINE' ] && readline=$1 || flags="$flags $1" ;;
    -g) gdb=YES flags="$flags -g" ;;
    -t*) src=C; flags="$flags -g -DDEBUG" ;;
    -u) src=U ;;
    -dkm) UFLAGS='-m' ;;
    -m32|-m64) flags="$flags $1"; arch=$1 ;;
    -l*) flags="$flags -DLOC_STATS=2" ;;
    -d*) debug=-debug ;;
    -W*) wiz=YES ;;
    -h*) usage ;;
    -s*) stable=-s ;;
    -x) set -x ;;
    -) ;;
    -*) echo Unknown keyword $1; usage ;;
    *) [ -z "$name" ] && name="$1" || usage ;;
  esac
  shift
done
[ $warn = NO ] && mode="$mode -no-warnings"
#
# Find and check the acdc and kernel versions to be used.
#
latest AVER acdc $stable
[ -n "$AVER" ] && ACDC="$ACDC-$AVER"
[ ! -x "$ACDC/acdc" ] && (cd $ACDC; make)
[ ! -x "$ACDC/acdc" ] &&  echo No acdc executable in $ACDC! && exit
#
latest KVER kernel $stable
[ -n "$KVER" ] && KERNEL="$KERNEL-$KVER"
#
err=FALSE
version_info IAVER acdc version
version_info IKVER kernel version
version_info KDATE kernel date
if [ -n "$AVER" -a "$AVER" != $IAVER ]; then
  >&2 echo $ACDC version mismatch: $AVER / $IAVER
  err=TRUE
fi
if [ -n "$KVER" -a "$KVER" != $IKVER ]; then
  >&2 echo $KERNEL version mismatch: $KVER / $IKVER
  err=TRUE
fi
[ $err = TRUE ] && exit 1;
#
# Set various build mode flags and variables.
#
[ "$console" = YES ] && flags="$flags -DCONSOLE";
[ "$quiet" = YES ] && mode="$mode -q"
[ "$wiz" = YES ] && suptype=" wizard mode,"
[ "$gdb" = YES -a -z "$js" ] && suptype="$suptype gdb,"
[ -n "$debug" -a -z "$js" ] && suptype="$suptype embedded A-code,"
[ -n "$plain" ] && suptype="$suptype plain text,"
suptype=${suptype:0:$(expr ${#suptype} - 1)}
#
[ "$stable" = '-s' ] && vstate=', stable'
#
# Try finding the game if it has been explicitly specified.
#
if [ -z "$name" ]; then
   name=$ADVDFLT
   ADVDIR=.
else
   ADVDIR="$ADV/$name"
fi
#
echo "Making $name (style $style$vstate)"
echo
#
if [ ! -d "$ADVDIR" ]; then
   echo Not found: $ADVDIR
   exit 1
fi
cd "$ADVDIR"
#
if [ "$js" = YES ]; then
  if [ "$name" = adv550 -o "$name" = adv660 ]; then
    oname=$name
    [ "$oname" = adv550 ] && name=cgi550 || name=cgi660
    echo === No JS mode in $oname. Using corresponding $name sources...
    echo
    cd ../$name
  fi
fi
#
if [ ! -r $name -a ! -r ${name}.acd ]; then
   echo Unable to find the A-code source file for $name.
   echo
   exit 1
fi
#
# If need readline, set things up appropriately.
#
if [ -z "$readline" ]; then
  readline="-L$ADV/lib -L$ADV/lib64 -lreadline -lncurses"
  case "$readline" in
    *NO_READLINE*) LIBWARN='WARNING: command line editing not supported.' ;;
    *) ;;
  esac
fi
[ -z "$js" ] && case "$arch" in
  *32*) exetype="$exetype (32 bit)" ;;
  *64*) exetype="$exetype (64 bit)" ;;
  *) ;;
esac
[ -n "$debug" ] && DEBUG='=-DDEBUG -g'
#
#
# Add wizard mode if requested, otherwise ensure it is absent.
#
if [ $wiz = YES ]; then
   [ -r opt/debug.acd ] && cp opt/debug.acd .
else
   rm -f debug.acd
fi
# Create any missing directories.
#
if [ -d C ]; then
  if [ $translate = YES ]; then
    rm -rf C/*
  fi
else
  mkdir C
fi
rm -f bld.log
#
echo [A-code kernel, version $IKVER $KDATE] 
#
# Translate A-code source to C, unless told not to.
#
if [ "$translate" == YES ]; then
  $ACDC/acdc $name -x $debug $plain $mode || exit
  rm -f debug.acd
#
# Copy the kernel.
#
  cp $KERNEL/adv0* C/
  [ "$advlib" = YES ] && cp $KERNEL/tools/libtest.c C/
fi
#
# Need a cross-reference sort?
#
if [ $quiet = NO ]; then
  [ -f $name.xrf ] && $ACDC/tools/sortref $name
else
  [ -f $name.xrf ] && $ACDC/tools/sortref $name >bld.log 2>&1
fi
#
# Define functions for reporting results.
#
done_it()
{
   if [ -f C/$name.dat ]; then
      cp C/$name.dat .
      [ $src = U ] && cp C/$name.dat U/
   fi
   rm -f error.log
   [ "$gdb" = YES ] || strip $name
   echo Created $name.
   echo
}
failed_it()
{
   cat error.log
   rm -f error.log
   echo
   exit 1
}
#
#=================================================================
#
[ "$build" != 'YES' ] && exit 0
#
if [ $src = 'U' ]; then
  [ -d U ] && rm -rf U/* || mkdir U;
  echo "Uglifying $name C => U"
  [ ! -x $ACDC/tools/uglify ] && (cd $ACDC/tools; make)
  [ ! -x $ACDC/tools/uglify ] &&     echo No uglify executable in $ACDC/tools! && exit 1
  if [ $quiet = NO ]; then
    $ACDC/tools/uglify $UFLAGS C U
  else
    $ACDC/tools/uglify $UFLAGS C U >>bld.log 2>&1
  fi
else
  rm -rf U
fi
[ "$console" = 'YES' -a "$readline" = '-DNO_READLINE' ] &&   exetype="$exetype without command editing"
echo "Compiling and linking $exetype."
[ -n "$suptype" ] && echo "Modifiers:$suptype."
# 
# The JS mode is special.
#
if [ "$js" = YES ]; then
   cd $src
   . /opt/emsdk_portable/emsdk_set_env.sh
   emcc -O1 -s ASM_JS=1 -DJS adv*.c  -Wno-parentheses-equality       -o $name.js -s EXPORTED_FUNCTIONS="['_advturn']" >.emcc.log 2>&1
   if [ $? -ne 0 ]; then
     >&2 cat .emcc.log
     rm .emcc.log
     exit 1
   fi
   rm -f ._advmake_.log
#   echo '*** IGNORE ABOVE WARNING (IF PRESENT) TO USE -O2 ***'
   [ $quiet = NO ] && echo Merging $name.js into $name.html...
# 
# Now merge $KERNEL/acode.html with $name.js
#
   RIFS="$IFS"
   IFS=
   while read line; do
     case "$line" in
       *%JAVASCRIPT%*) cat $name.js ;;
       *%NAME%*) echo "${line/%NAME%/$name}" ;;
       *) echo "$line";;
     esac
   done <$KERNEL/acode.html >$name.html
   IFS="$RIFS"
   rm $name.js && mv $name.html ../
   [ -n "$oname" ] &&      cp -p ../$name.html ../../$oname/$oname.html && name=$oname
   echo Created $name.html.
   echo
   exit
fi
#
# Just a C build of some sort.
#
[ -n "$LIBWARN" ] && echo $LIBWARN  
if cc ./$src/*.c $flags $readline -I ./$src -I $ADV    -o $name $CFLAGS >error.log 2>&1; then
     done_it
else
   failed_it
fi
exit
#
####################################################################
