#!/bin/bash
#
# Tvheadend configure script
#
# Copyright (c) 2012 Adam Sutton <dev@adamsutton.me.uk>
#

# ###########################################################################
# Setup
# ###########################################################################

ROOTDIR=$(cd "$(dirname "$0")"; pwd)
test -z "$PKG_CONFIG" && PKG_CONFIG=pkg-config

#
# Options
#

OPTIONS=(
  "pie:yes"
  "ccdebug:no"
  "cardclient:auto"
  "cwc:yes"
  "cccam:yes"
  "capmt:yes"
  "constcw:yes"
  "linuxdvb:yes"
  "satip_server:yes"
  "satip_client:yes"
  "hdhomerun_client:no"
  "hdhomerun_server:yes"
  "hdhomerun_static:yes"
  "iptv:yes"
  "tsfile:yes"
  "dvbscan:yes"
  "timeshift:yes"
  "trace:yes"
  "avahi:auto"
  "zlib:auto"
  "libav:auto"
  "ffmpeg_static:yes"
  "libx264:yes"
  "libx264_static:yes"
  "libx265:yes"
  "libx265_static:yes"
  "libvpx:yes"
  "libvpx_static:yes"
  "libtheora:yes"
  "libtheora_static:yes"
  "libvorbis:yes"
  "libvorbis_static:yes"
  "libfdkaac:no"
  "libfdkaac_static:yes"
  "libopus:yes"
  "libopus_static:yes"
  "nvenc:no"
  "vaapi:auto"
  "mmal:no"
  "omx:no"
  "inotify:auto"
  "epoll:auto"
  "pcre:auto"
  "pcre2:auto"
  "uriparser:auto"
  "ccache:auto"
  "tvhcsa:auto"
  "bundle:no"
  "pngquant:no"
  "kqueue:no"
  "dbus_1:auto"
  "android:no"
  "gtimer_check:no"
  "slow_memoryinfo:no"
  "libsystemd_daemon:no"
  "pcloud_cache:yes"
  "ddci:yes"
  "cclang_threadsan:no"
  "gperftools:no"
  "execinfo:auto"
)

#
# Begin
#

. "$ROOTDIR/support/configure.inc"

case "$ROOTDIR" in
  *\ * ) die "The source directory contains a space. It is not supported."
esac

# ###########################################################################
# Parse arguments
# ###########################################################################
opt=
val=
for opt do
  val=${opt#*=}
  opt=${opt%%=*}
  opt=${opt#*--}
  case "$opt" in
    help)
      show_help
      ;;
    *dir|prefix)
      eval "$opt=$val"
      ;;
    gzip)
      eval "$(toupper ${opt}CMD)=\"$val\""
      ;;
    cc|cflags|ldflags|arch|cpu|platform|python|bzip2|nowerror)
      eval "$(toupper $opt)=\"$val\""
      ;;
    enable-*)
      opt=${opt#*-}
      enable $opt 1
      ;;
    disable-*)
      opt=${opt#*-}
      disable $opt 1
      ;;
  esac
done

# ###########################################################################
# Checks
# ###########################################################################

echo "Checking support/features"

#
# Compiler
#

# Use ccache
if enabled_or_auto ccache; then
  which ccache &> /dev/null
  if [ $? -eq 0 ]; then
    echo "$CC" | grep -q ccache || CC="ccache $CC"
    enable ccache
  elif enabled ccache; then
    die "ccache not found, try --disable-ccache"
  fi
fi

# Valiate compiler
check_cc || die 'No C compiler found'
# We only want libunwind on non-linux plaforms where we have had bad
# stack traces from existing implementation.
if [ ${PLATFORM} = "freebsd" ]; then
    check_cc_header libunwind
    # If we don't have libunwind then fallback to execinfo.
    if ! enabled libunwind
    then
        if enabled_or_auto execinfo; then
            if check_cc_header execinfo; then
                enable execinfo
            else
                die "execinfo.h not found (use --disable-execinfo)"
           fi
        fi
    fi

else
    if enabled_or_auto execinfo; then
        if check_cc_header execinfo; then
            enable execinfo
        else
            die "execinfo.h not found (use --disable-execinfo)"
        fi
    fi
fi
check_cc_option mmx
check_cc_option sse2
check_cc_optionW unused-result
# Some options from https://wiki.debian.org/Hardening
check_cc_optionf stack-protector
check_cc_optionf stack-protector-strong
# Useful for multi-threaded programs
check_cc_optionf stack-check
check_cc_optionf PIE

if check_cc '
#if !defined(__clang__)
#error this is not clang
#endif
'; then
  COMPILER=clang
else
  COMPILER=gcc
fi

check_cc_snippet strlcat '#include <string.h>
#define TEST test
int test() {
  char dst[10];
  strlcat("test", dst, sizeof(dst));
  return 0;
}'

check_cc_snippet strlcpy '#include <string.h>
#define TEST test
int test() {
  char dst[10];
  strlcpy("test", dst, sizeof(dst));
  return 0;
}'

check_cc_snippet fdatasync '#include <unistd.h>
#define TEST test
int test() {
  fdatasync(0);
  return 0;
}'

check_cc_snippet getloadavg '#include <stdlib.h>
#define TEST test
int test() { return getloadavg(NULL,0); }'

check_cc_snippet atomic32 '#include <stdint.h>
int test(int *ptr){
return __sync_fetch_and_add(ptr, 1);
}'

check_cc_snippet atomic64 '#include <stdint.h>
uint64_t test(uint64_t *ptr){
return __sync_fetch_and_add(ptr, 1);
}'

check_cc_snippet atomic_time_t '#include <stdint.h>
#include <time.h>
uint64_t test(time_t *ptr){
return __sync_fetch_and_add(ptr, 1);
}'

check_cc_snippet atomic_ptr '#include <stdint.h>
#include <time.h>
void *test(void * volatile *ptr){
return __sync_fetch_and_add(ptr, (void *)1);
}'

check_cc_snippet bitops64 '#include <stdint.h>
#define TEST test
int test(void){
  int l = sizeof(long);
  return l == 8 ? 0 : 1;
}'

check_cc_snippet lockowner '
#include <sys/syscall.h>
#include <unistd.h>
#include <pthread.h>
#define TEST test
int ok = 1;
void *lockowner ( void *p)
{
  pthread_mutex_t lock;
  pthread_mutex_init(&lock, NULL);
  pthread_mutex_lock(&lock);
  if (lock.__data.__owner == syscall(SYS_gettid))
    ok = 0;
  return NULL;
}
int test ( void )
{
  pthread_t tid;
  pthread_create(&tid, NULL, lockowner, NULL);
  pthread_join(tid, NULL);
  return ok;
}' -lpthread

check_cc_snippet qsort_r '
#define _GNU_SOURCE
#include <stdlib.h>
#define TEST test
int test(void)
{
  qsort_r(NULL, 0, 0, NULL, NULL);
  return 0;
}
'

check_cc_snippet time_ld '
#define _FILE_OFFSET_BITS 64
#define _TIME_BITS 64
#include <stdio.h>
#include <time.h>
#define TEST test
int test(void)
{
  printf("%ld", (time_t)1);
  return 0;
}
' '-Wformat -Werror=format'

check_cc_snippet time_lld '
#define _FILE_OFFSET_BITS 64
#define _TIME_BITS 64
#include <stdio.h>
#include <time.h>
#define TEST test
int test(void)
{
  printf("%lld", (time_t)1);
  return 0;
}
' '-Wformat -Werror=format'

if enabled time_lld; then
  printf "    ^ using time_t format 'lld'\n"
elif enabled time_ld; then
  printf "    ^ using time_t format 'ld'\n"
else
  die 'Unable to determine size of "time_t"'
fi

check_cc_snippet stime '
#include <time.h>
#define TEST test
int test(void)
{
  time_t t = 1;
  stime(&t);
  return 0;
}
'

check_cc_snippet gmtoff '
#include <time.h>
#define TEST test
int test(void)
{
  struct tm x;
  x.tm_gmtoff = 0;
  return 0;
}
' -DHAS_GMTOFF

check_cc_snippet recvmmsg '
#define _GNU_SOURCE
#include <stdlib.h>
#include <sys/socket.h>
#define TEST test
int test(void)
{
  recvmmsg(0, NULL, 0, 0, NULL);
  return 0;
}
'

check_cc_snippet sendmmsg '
#define _GNU_SOURCE
#include <stdlib.h>
#include <sys/socket.h>
#define TEST test
int test(void)
{
  sendmmsg(0, NULL, 0, 0);
  return 0;
}
'

# a check for the external (gnu)iconv library
# for build-in libc iconv routines this check should fail
# note that iconv routines are mandatory
check_cc_snippet gnu_libiconv '
#include <gnu-libiconv/iconv.h>
#define TEST test
int test(void)
{
  iconv_t ic = iconv_open("ASCII", "ASCII");
  return 0;
}
' -liconv

check_cc_snippet libiconv '
#include <iconv.h>
#define TEST test
int test(void)
{
  iconv_t ic = iconv_open("ASCII", "ASCII");
  return 0;
}
' -liconv

if enabled gnu_libiconv; then
  printf "    ^ using gnu libiconv library\n"
elif enabled libiconv; then
  printf "    ^ using external iconv library\n"
else
  printf "    ^ using build-in glibc iconv routines\n"
fi

check_cc_snippet ifnames '
#include <net/if.h>
#define TEST test
int test(void)
{
  struct if_nameindex *ifnames = if_nameindex();
  if_freenameindex(ifnames);
  return 0;
}
'

check_cc_snippet cclang_threadsan '
#define TEST test
int test(void){
#if __has_feature(thread_sanitizer)
  return 0;
#endif
}'

#
# Python
#
check_py || echo 'WARN: no python binary found'
check_py_import gzip

#
# Binaries
#
check_bin ${PKG_CONFIG} bin_pkg_config || die 'no pkg-config binaries found'
check_bin ${XGETTEXT} || die 'no gettext binaries found'
check_bin ${MSGMERGE} || die 'no gettext binaries found'
check_bin ${GZIPCMD} || die 'no gzip binary found'
check_bin ${BZIP2} || echo 'WARN: no bzip2 binary found'

#
# SSL
#
if [ ${PLATFORM} = "freebsd" ]; then
  # FreeBSD has libssl in base
  enable ssl
elif check_pkg openssl || check_pkg libssl; then
  enable ssl
elif check_cc_header 'openssl/ssl' ssl; then
  enable ssl
else
  die "SSL development support not found"
fi

#
# OS X
#
if [ ${PLATFORM} = "darwin" ]; then
  disable linuxdvb
  disable avahi
  enable bonjour
fi

#
# DVB API
#
check_cc_header 'linux/dvb/version' linuxdvbapi
if enabled_or_auto linuxdvb; then
  if enabled linuxdvbapi; then
    enable linuxdvb
    enable linuxdvb_ca
  elif enabled linuxdvb; then
    die "Linux DVB API not found (use --disable-linuxdvb)"
  fi
fi

if enabled linuxdvb; then
  if enabled_or_auto ddci; then
    enable ddci
  fi
else
  disable ddci
fi

#
# HDHomeRun - libhdhomerun
#
if enabled hdhomerun_static; then

  enable hdhomerun_client

else

  if enabled_or_auto hdhomerun_client; then
    if check_cc_header 'libhdhomerun/hdhomerun' libhdhomerun; then
      enable  hdhomerun_client
      LDFLAGS="$LDFLAGS -lhdhomerun"
    fi
  fi

fi

if enabled libunwind; then
    LDFLAGS="$LDFLAGS -lunwind"
fi
#
# Gzip
#
if enabled_or_auto zlib; then
  if check_pkg zlib; then
    enable zlib
  elif enabled zlib; then
    die "Zlib development support not found (use --disable-zlib)"
  fi
fi

#
# SAT>IP server
#
if enabled_or_auto satip_server; then
  enable upnp
fi

#
# SAT>IP client
#
if enabled_or_auto satip_client; then
  enable upnp
fi

#
# PCRE2
#
if enabled_or_auto pcre2; then
  if check_pkg libpcre2-8; then
    enable pcre2
  elif enabled pcre2; then
    die "pcre2 development support not found (use --disable-pcre2)"
  fi
fi

#
# PCRE
#
if enabled pcre2; then
  disable pcre
else
  if enabled_or_auto pcre; then
    if check_pkg libpcre; then
      enable pcre
    elif enabled pcre; then
      die "pcre development support not found (use --disable-pcre)"
    fi
  fi
fi

#
# uriparser
#
if enabled_or_auto uriparser; then
  if check_pkg liburiparser; then
    enable uriparser
  elif enabled uriparser; then
    die "liburiparser development support not found (use --disable-uriparser)"
  fi
fi

#
# Bundling
#
if enabled bundle; then
  if enabled zlib && ! enabled py_gzip; then
    die "Python gzip module not found (use --disable-zlib or --disable-bundle)"
  fi
fi

#
# Avahi
#
if enabled_or_auto avahi; then
  if check_pkg avahi-client; then
    enable avahi
  elif enabled avahi; then
    die "Avahi development support not found (use --disable-avahi)"
  fi
fi

#
# ffmpeg
#
if enabled ffmpeg_static; then

  enable libav
  has_libav=true

  # libx264
  if enabled libx264; then
    if disabled libx264_static; then
      check_pkg x264 ">=0.142" || die "x264 package not found"
    fi
  else
    disable libx264_static
  fi

  # libx265
  if enabled libx265; then
    if disabled libx265_static; then
      check_pkg x265 ">=1.5" || die "x265 package not found"
    else
      check_bin cmake || die "cmake not found"
      check_cc_lib stdc++ stdcpp || die "libstdc++ not found"
    fi
  else
    disable libx265_static
  fi

  # libvpx
  if enabled libvpx; then
    if disabled libvpx_static; then
      check_pkg vpx ">=1.3.0" || die "vpx package not found"
    fi
  else
    disable libvpx_static
  fi

  # libtheora
  if enabled libtheora; then
    if disabled libtheora_static; then
      check_pkg theoraenc ">=1.1.1" || die "theoraenc package not found"
      check_pkg theoradec ">=1.1.1" || die "theoradec package not found"
      check_pkg theora    ">=1.1.1" || die "theora package not found"
    else
      enable libogg_static
    fi
  else
    disable libtheora_static
  fi

  # libvorbis
  if enabled libvorbis; then
    if disabled libvorbis_static; then
      check_pkg vorbisfile ">=1.3.4" || die "vorbisfile package not found"
      check_pkg vorbisenc  ">=1.3.4" || die "vorbisenc package not found"
      check_pkg vorbis     ">=1.3.4" || die "vorbis package not found"
    else
      enable libogg_static
    fi
  else
    disable libvorbis_static
  fi

  # libfdk-aac
  if enabled libfdkaac; then
    if disabled libfdkaac_static; then
      check_pkg fdk-aac ">=0.1.3" || die "fdk-aac package not found"
    fi
  else
    disable libfdkaac_static
  fi

  # libopus
  if enabled libopus; then
    if disabled libopus_static; then
      check_pkg opus ">=1.1" || die "opus package not found"
    fi
  else
    disable libopus_static
  fi

  # nvenc
  # latest ffmpeg has own code
  if enabled_or_auto nvenc; then
    #check_cc_header nvEncodeAPI || \
    #  die "NVENC library (https://developer.nvidia.com/nvidia-video-codec-sdk) not found"
    enable nvenc
    enable hwaccels
  else
    disable nvenc
  fi

  # omx
  if enabled omx; then
    OLDCFLAGS=$CFLAGS
    if [ -d "/opt/vc/include" ]; then
      CFLAGS="-I/opt/vc/include $CFLAGS"
    fi
    check_cc_header "bcm_host" omx_rpi
    if enabled omx_rpi; then
      CFLAGS="-I/opt/vc/include/IL $OLDCFLAGS"
      enable mmal
    else
      CFLAGS=$OLDCFLAGS
    fi
    check_cc_header OMX_Core omx || die "OpenMAX IL not found"
  fi

else

  if enabled_or_auto libav; then
    has_libav=true
    has_resample=false

    check_pkg libavfilter   ">=6.47.100"  || has_libav=false
    check_pkg libswresample ">=2.1.100"   && has_resample=true
    check_pkg libswscale    ">=4.1.100"   || has_libav=false
    check_pkg libavformat   ">=57.41.100" || has_libav=false
    check_pkg libavcodec    ">=57.48.101" || has_libav=false
    check_pkg libavutil     ">=55.28.100" || has_libav=false

    if $has_libav -a $has_resample; then
      enable libav
    else
      echo "WARNING: none or old ffmpeg libraries were detected"
      echo "         * use --disable-libav or --enable-ffmpeg_static"
      if enabled libav; then
        die "ffmpeg development support not found"
      fi
    fi

  fi

fi

#
# vaapi
#
if enabled_or_auto vaapi; then
  if enabled libav; then
    if check_pkg libva ">=0.38.0"; then
      check_pkg libva-drm ">=0.38.0" || die "libva-drm not found"
      enable vaapi
      enable hwaccels
    elif enabled vaapi; then
      die "vaapi (Video Acceleration (VA) API for Linux) not found"
    fi
  fi
fi

#
# Inotify
#
if enabled_or_auto inotify; then
  if check_cc_header "sys/inotify" inotify_h; then
    enable inotify
    check_cc_snippet inotify_init1 '
    #include <sys/inotify.h>
    int test(void)
    {
      inotify_init1(IN_CLOEXEC);
      return 0;
    }
    '
  elif enabled inotify; then
    die "Inotify support not found (use --disable-inotify)"
  fi
fi

#
# common card client
#
if enabled cwc || enabled cccam; then
  enable cardclient
fi

#
# libdvbcsa, tvhcsa
#
if enabled_or_auto tvhcsa; then
  if enabled cwc || enabled cccam || enabled capmt || enabled constcw; then
    enable tvhcsa
    enable dvbcsa
    if enabled dvbcsa; then
      (check_cc_header "dvbcsa/dvbcsa" dvbcsa_h &&\
       check_cc_lib    dvbcsa dvbcsa_l) ||\
      die "Failed to find dvbcsa library"
      LDFLAGS="$LDFLAGS -ldvbcsa"
    fi
  else
    disable tvhcsa
  fi
fi

#
# DVB scan
#
if enabled dvbscan; then
  printf "${TAB}" "fetching dvb-scan files ..."
  "${ROOTDIR}/support/getmuxlist"
  if [ $? -ne 0 ]; then
    echo "fail"
    check_bin git || echo "FATAL: git binary not found (install the git package)"
    die "Failed to fetch dvb-scan data (use --disable-dvbscan)"
  fi
  echo "ok"
fi

#
# epoll
#
if [ ${PLATFORM} = "linux" ]; then
  enable epoll
  check_cc_snippet epoll_create1 '
  #include <sys/epoll.h>
  int test(void)
  {
    epoll_create1(EPOLL_CLOEXEC);
    return 0;
  }
  '
fi

#
# kqueue
#
if [ ${PLATFORM} = "freebsd" ] || [ ${PLATFORM} = "darwin" ]; then
  enable kqueue
fi

#
# MPEGTS support
#
disable mpegts
disable mpegts_dvb
if enabled linuxdvb || enabled iptv || enabled tsfile || enabled satip_client || \
   enabled hdhomerun_client || enabled satip_server;
then
  enable mpegts
fi
if enabled linuxdvb || enabled satip_client || enabled hdhomerun_client || enabled satip_server; then
  enable mpegts_dvb
fi

#
# DBus
#
if enabled_or_auto dbus_1; then
  if check_pkg dbus-1; then
    enable dbus_1
  elif enabled dbus-1; then
    die "DBus-1 development support not found (use --disable-dbus-1)"
  fi
fi

#
# systemd
#
if enabled_or_auto libsystemd_daemon; then
  if check_pkg libsystemd-daemon || check_pkg libsystemd; then
    enable libsystemd_daemon
  elif enabled libsystemd_daemon; then
    die "libsystemd-daemon development support not found (use --disable-systemd_daemon)"
  fi
fi

# ###########################################################################
# Write config
# ###########################################################################

# Write config
write_config
cat >> "${CONFIG_H}" <<EOF
#define TVHEADEND_DATADIR "$(eval echo ${datadir})/tvheadend"
EOF

# Output config
print_config
echo "Final Binary:"
echo "  $BUILDDIR/tvheadend"
echo ""
echo "Tvheadend Data Directory:"
echo "  $(eval echo ${datadir}/tvheadend)"
echo ""
