#! /bin/bash

#set -x

# Default qmake setup (for GUI compilation). This requires Qt4 or Qt5 (Qt5 is
# preferred). We try to locate the qmake executable in some common locations
# here. If this doesn't work out then you can also set the QMAKE environment
# variable explicitly, or use one of the -qt4 and -qt5 options below.
[ -z "$QMAKE" ] && QMAKE=$(which qmake-qt5 || which /opt/local/libexec/qt5/bin/qmake || which qmake-qt4 || which /opt/local/libexec/qt4/bin/qmake || echo qmake)

# Where the Faust includes live. We assume that this is under the prefix of
# whatever Faust binary 'which' locates. You can also specify this explicitly
# by setting the FAUSTINC environment variable accordingly.
[ -z "$FAUSTINC" ] && FAUSTINC=$(which faust 2>/dev/null | sed -e 's?/bin/faust?/include/faust?')

# Where our own Faust library files are. This may be under a different prefix
# or not installed anywhere. We try 'ls' on lv2ui.cpp in some common locations
# here, and fall back to the current directory if the file isn't found, so
# that you can run the script from the faust-lv2 source directory. You can
# also specify this explicitly by setting the FAUSTLIB environment variable
# accordingly.
[ -z "$FAUSTLIB" ] && FAUSTLIB=$(dirname "$((ls -f /usr/share/faust/lv2ui.cpp /usr/local/share/faust/lv2ui.cpp /opt/local/share/faust/lv2ui.cpp "$PWD/lv2ui.cpp" 2>/dev/null)|tail -1)")
[ -z "$FAUSTLIB" ] && FAUSTLIB="$PWD"

# defaults (these can be changed with the options listed below)
URI_PREFIX=http://faustlv2.bitbucket.org
FAUST_META=1
FAUST_MIDICC=1
FAUST_MTS=1
FAUST_UI=0
VOICE_CTRLS=1
NVOICES=-1

KEEP="no"
STYLE=""

PROCARCH="-fPIC"
dllext=".so"
CXXFLAGS="-O3 -march=native -mfpmath=sse -msse -msse2 -msse3 -ffast-math -ftree-vectorize"

# Darwin specifics
if [[ $(uname) == Darwin ]]; then
    dllext=".dylib"
fi

# dispatch command arguments
for ((i=1;i<$#+1;i++)); do
    p=${!i}
    if [ $p = "-help" ] || [ $p = "-h" ]; then
	cat <<EOF
faust2lv2 [options ...] <file.dsp>

Options:
-dyn-manifest: use dynamic manifests (requires LV2 host support)
-gui: build the plugin GUI (requires LV2 UI host support)
-httpd: activate HTTP control (add -qrcode to activate QR code generation)
-keep: retain the build directory
-nometa: ignore metadata (author information etc.) from the Faust source
-nomidicc: plugin doesn't process MIDI control data
-notuning: disable the tuning control (instruments only)
-novoicectrls: no extra polyphony/tuning controls on GUI (instruments only)
-nvoices N: number of synth voices (instruments only; arg must be an integer)
-osc: activate OSC control
-qt4, -qt5: select the GUI toolkit (requires Qt4/5; implies -gui)
-style S: select the stylesheet (arg must be Default, Blue, Grey or Salmon)
-uri-prefix URI: URI prefix of plugin (arg must be a valid URI)

Environment variables:
FAUSTINC: specify the location of the Faust include directory
  Default: $FAUSTINC
FAUSTLIB: specify the location of the Faust LV2 library files
  Default: $FAUSTLIB
QMAKE: specify the location of the qmake binary of the desired Qt version
  Default: $QMAKE
EOF
	exit 0
    elif [ $p = "-omp" ]; then
	: ignore
    elif [ $p = "-icc" ]; then
	CXX=icpc
	CXXFLAGS="-O3 -xHost -ftz -fno-alias -fp-model fast=2"
    elif [ $p = "-osc" ]; then
	OSCDEFS="DEFINES += OSCCTRL"
	OSCLIBS="-lOSCFaust"
    elif [ $p = "-httpd" ]; then
	HTTPDEFS="DEFINES += HTTPCTRL"
	HTTPLIBS="-lHTTPDFaust -lmicrohttpd -lqrencode"
    elif [ $p = "-qrcode" ]; then # requires -httpd
	QRDEFS="DEFINES += QRCODECTRL"
    elif [ $p = "-nometa" ]; then
	FAUST_META=0
    elif [ $p = "-nomidicc" ]; then
	FAUST_MIDICC=0
    elif [ $p = "-notuning" ]; then
	FAUST_MTS=0
    elif [ $p = "-novoicectrls" ]; then
	VOICE_CTRLS=0
    elif [ $p = "-gui" ]; then
	FAUST_UI=1
	plugin_gui=yes
    elif [ $p = "-qt4" ]; then
	FAUST_UI=1
	plugin_gui=yes
	QMAKE=$(which qmake-qt4 || which /opt/local/libexec/qt4/bin/qmake || echo qmake-qt4)
    elif [ $p = "-qt5" ]; then
	FAUST_UI=1
	plugin_gui=yes
	QMAKE=$(which qmake-qt5 || which /opt/local/libexec/qt5/bin/qmake || echo qmake-qt5)
    elif [ $p = "-dyn-manifest" ]; then
	dyn_manifest=yes
    elif [ $p = "-uri-prefix" ]; then
	(( i++ ))
	URI_PREFIX=${!i}
    elif [ $p = "-nvoices" ]; then
	(( i++ ))
	NVOICES=${!i}
    elif [ $p = "-arch32" ]; then
	PROCARCH="-m32 -L/usr/lib32"
    elif [ $p = "-arch64" ]; then
	PROCARCH="-m64 -fPIC"
    elif [ $p = "-osx" ]; then
	CXXFLAGS="-O3 -march=native -mfpmath=sse -msse -msse2 -msse3 -ffast-math -ftree-vectorize -I/opt/local/include"
	dllext=".dylib"
    elif [ $p = "-keep" ]; then
	KEEP="yes"
    elif [ $p = "-style" ]; then
	(( i++ ))
	STYLE=${!i}
    elif [ ${p:0:1} = "-" ]; then
	OPTIONS="$OPTIONS $p"
    elif [[ -f "$p" ]]; then
	FILES="$FILES $p"
    else
	OPTIONS="$OPTIONS $p"
    fi
done

FILES=( $FILES )
if [ ${#FILES[@]} = 0 ]; then
    echo "$0: no filename specified" >&2
    exit 1
elif [ ${#FILES[@]} -gt 1 ]; then
    echo "$0: multiple filenames specified" >&2
    exit 1
fi

# Check to see whether the required include and library files are where we
# expect them, and bail out with an error message otherwise.
if [ ! -f "$FAUSTINC/gui/faustqt.h" ]; then echo "$0: faust include files not found" >&2; exit 1; fi
if [ ! -f "$FAUSTLIB/lv2ui.cpp" ]; then echo "$0: faust-lv2 library files not found" >&2; exit 1; fi

# Determine the Qt version so that we can edit the manifests accordingly.
QTVERSION=$($QMAKE -v 2>/dev/null | tail -1 | sed 's/.*Qt version \([0-9]\).*/\1/')

arch=lv2.cpp
archui=lv2ui.cpp
dspname=${FILES[0]}
SRCDIR=$(dirname "$dspname")
ABSDIR=$(cd $SRCDIR && pwd)
CURDIR=$(pwd)

clsname=`basename "$dspname" .dsp`
cppname="$clsname.cpp"
soname="$clsname$dllext"
uicppname="${clsname}ui.cpp"
uisoname="${clsname}ui$dllext"
lv2name="$clsname.lv2"
tmpdir=`mktemp -d /tmp/faust2lv2.XXXXXX`
uitmpdir=$tmpdir/${clsname}ui.src

RESOURCES=
STYLE_CXXFLAGS=
if [ -n "$STYLE" ]; then
    RESOURCES="RESOURCES+=$FAUSTINC/gui/Styles/$STYLE.qrc"
    STYLE_CXXFLAGS="QMAKE_CXXFLAGS+=-DSTYLE=\"$STYLE\""
fi

CXX=g++
CPPFLAGS="-DPLUGIN_URI=\"$URI_PREFIX/$clsname\" -DFAUST_META=$FAUST_META -DFAUST_MIDICC=$FAUST_MIDICC -DFAUST_MTS=$FAUST_MTS -DFAUST_UI=$FAUST_UI -DVOICE_CTRLS=$VOICE_CTRLS"
if [ $NVOICES -ge 0 ]; then
CPPFLAGS="$CPPFLAGS -DNVOICES=$NVOICES"
fi

# Create the temp directory and the bundle directory inside it.
mkdir -p $tmpdir/$lv2name
#trap "echo $0: compile error, intermediate files left in $tmpdir >&2" EXIT
# Compile the Faust module.
faust -i -a "$FAUSTLIB/$arch" -cn "$clsname" $OPTIONS "$dspname" -o "$tmpdir/$cppname" || exit 1
$CXX -shared $CXXFLAGS -DDLLEXT="\"$dllext\"" $FAUSTTOOLSFLAGS $PROCARCH $CPPFLAGS "$tmpdir/$cppname" -o "$tmpdir/$lv2name/$soname" || exit 1
if [ -n "$plugin_gui" ]; then
# Compile the UI module.
mkdir -p $uitmpdir
faust -i -a  "$FAUSTLIB/$archui" -cn "$clsname" $OPTIONS "$dspname" -o "$uitmpdir/$uicppname" || exit 1
(
    cd $uitmpdir
    $QMAKE -project -t lib -o ${clsname}ui.pro "CONFIG += gui plugin no_plugin_name_prefix warn_off" "QT += widgets printsupport network" "INCLUDEPATH+=$ABSDIR" "INCLUDEPATH+=$CURDIR" "INCLUDEPATH+=$FAUSTLIB" "INCLUDEPATH+=$FAUSTINC" "QMAKE_CXXFLAGS=$(echo $CPPFLAGS|sed -e 's/"/\\\\\\"/g')" $STYLE_CXXFLAGS "LIBS+=$ARCHLIB $OSCLIBS $HTTPLIBS" "HEADERS+=$FAUSTLIB/lv2qtgui.h $FAUSTINC/gui/faustqt.h" $RESOURCES "$OSCDEFS" "$HTTPDEFS" "$QRDEFS"
    $QMAKE *.pro
    make || exit 1
    mv $uisoname "$tmpdir/$lv2name"
) > /dev/null || exit 1
fi
# Generate the manifest. There are four different variations of the manifest,
# depending on whether dynamic manifests and the plugin gui is enabled or not.
if [ -n "$dyn_manifest" ]; then
# Use a dynamic manifest.
if [ -n "$plugin_gui" ]; then
sed -e"s?@name@?$clsname?g" -e"s?@uri@?$URI_PREFIX/$clsname?g" -e"s?@dllext@?$dllext?g" -e "s?ui:Qt5UI?ui:Qt${QTVERSION}UI?g" > "$tmpdir/$lv2name/manifest.ttl" <<EOF

########## @uri@ ##########

@prefix doap: <http://usefulinc.com/ns/doap#> .
@prefix foaf: <http://xmlns.com/foaf/0.1/> .
@prefix lv2:  <http://lv2plug.in/ns/lv2core#> .
@prefix ui:   <http://lv2plug.in/ns/extensions/ui#> .
@prefix dman: <http://lv2plug.in/ns/ext/dynmanifest#> .

<@uri@/manifest>
    lv2:binary <@name@@dllext@> ;
    a dman:DynManifest .

<@uri@ui>
    a ui:Qt5UI ;
    ui:binary <@name@ui@dllext@> .

# Here's how you can declare the category of the plugin. (For instruments,
# the lv2:InstrumentPlugin type will be added automatically.) See
# http://lv2plug.in/ns/lv2core/ for a list of known plugin classes.

# <@uri@> a lv2:FilterPlugin .

# You might also want to set the license and author information below.
# NOTE: This isn't normally necessary if you declared the corresponding
# information as metadata in the Faust source of the plugin. The standard
# author, license and description fields in the Faust source are automagically
# included in the generated LV2 manifest.

# <@uri@>
#     doap:license <http://opensource.org/licenses/isc> ;
#     doap:maintainer [
#         foaf:name "Your Name Here" ;
#         foaf:homepage <http://somewhere.org/> ;
#         foaf:mbox <mailto:your@mail.here> ;
#     ] .
EOF
else
sed -e"s?@name@?$clsname?g" -e"s?@uri@?$URI_PREFIX/$clsname?g" -e"s?@dllext@?$dllext?g" > "$tmpdir/$lv2name/manifest.ttl" <<EOF

########## @uri@ ##########

@prefix doap: <http://usefulinc.com/ns/doap#> .
@prefix foaf: <http://xmlns.com/foaf/0.1/> .
@prefix lv2:  <http://lv2plug.in/ns/lv2core#> .
@prefix dman: <http://lv2plug.in/ns/ext/dynmanifest#> .

<@uri@/manifest>
    lv2:binary <@name@@dllext@> ;
    a dman:DynManifest .

# Here's how you can declare the category of the plugin. (For instruments,
# the lv2:InstrumentPlugin type will be added automatically.) See
# http://lv2plug.in/ns/lv2core/ for a list of known plugin classes.

# <@uri@> a lv2:FilterPlugin .

# You might also want to set the license and author information below.
# NOTE: This isn't normally necessary if you declared the corresponding
# information as metadata in the Faust source of the plugin. The standard
# author, license and description fields in the Faust source are automagically
# included in the generated LV2 manifest.

# <@uri@>
#     doap:license <http://opensource.org/licenses/isc> ;
#     doap:maintainer [
#         foaf:name "Your Name Here" ;
#         foaf:homepage <http://somewhere.org/> ;
#         foaf:mbox <mailto:your@mail.here> ;
#     ] .
EOF
fi
else
# Use a static manifest.
if [ -n "$plugin_gui" ]; then
sed -e"s?@name@?$clsname?g" -e"s?@uri@?$URI_PREFIX/$clsname?g" -e"s?@dllext@?$dllext?g" -e "s?ui:Qt5UI?ui:Qt${QTVERSION}UI?g" > "$tmpdir/$lv2name/manifest.ttl" <<EOF

########## @uri@ ##########

@prefix doap: <http://usefulinc.com/ns/doap#> .
@prefix foaf: <http://xmlns.com/foaf/0.1/> .
@prefix lv2:  <http://lv2plug.in/ns/lv2core#> .
@prefix ui:   <http://lv2plug.in/ns/extensions/ui#> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .

<@uri@>
    a lv2:Plugin ;
    lv2:binary <@name@@dllext@> ;
    rdfs:seeAlso <@name@.ttl> .

<@uri@ui>
    a ui:Qt5UI ;
    ui:binary <@name@ui@dllext@> .

# Here's how you can declare the category of the plugin. (For instruments,
# the lv2:InstrumentPlugin type will be added automatically.) See
# http://lv2plug.in/ns/lv2core/ for a list of known plugin classes.

# <@uri@> a lv2:FilterPlugin .

# You might also want to set the license and author information below.
# NOTE: This isn't normally necessary if you declared the corresponding
# information as metadata in the Faust source of the plugin. The standard
# author, license and description fields in the Faust source are automagically
# included in the generated LV2 manifest.

# <@uri@>
#     doap:license <http://opensource.org/licenses/isc> ;
#     doap:maintainer [
#         foaf:name "Your Name Here" ;
#         foaf:homepage <http://somewhere.org/> ;
#         foaf:mbox <mailto:your@mail.here> ;
#     ] .
EOF
else
sed -e"s?@name@?$clsname?g" -e"s?@uri@?$URI_PREFIX/$clsname?g" -e"s?@dllext@?$dllext?g" > "$tmpdir/$lv2name/manifest.ttl" <<EOF

########## @uri@ ##########

@prefix doap: <http://usefulinc.com/ns/doap#> .
@prefix foaf: <http://xmlns.com/foaf/0.1/> .
@prefix lv2:  <http://lv2plug.in/ns/lv2core#> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .

<@uri@>
    a lv2:Plugin ;
    lv2:binary <@name@@dllext@> ;
    rdfs:seeAlso <@name@.ttl> .

# Here's how you can declare the category of the plugin. (For instruments,
# the lv2:InstrumentPlugin type will be added automatically.) See
# http://lv2plug.in/ns/lv2core/ for a list of known plugin classes.

# <@uri@> a lv2:FilterPlugin .

# You might also want to set the license and author information below.
# NOTE: This isn't normally necessary if you declared the corresponding
# information as metadata in the Faust source of the plugin. The standard
# author, license and description fields in the Faust source are automagically
# included in the generated LV2 manifest.

# <@uri@>
#     doap:license <http://opensource.org/licenses/isc> ;
#     doap:maintainer [
#         foaf:name "Your Name Here" ;
#         foaf:homepage <http://somewhere.org/> ;
#         foaf:mbox <mailto:your@mail.here> ;
#     ] .
EOF
fi
# This compiles the plugin to an executable which is run to generate the
# plugin-specific part of the manifest.
$CXX $CXXFLAGS -DDLLEXT="\"$dllext\"" $FAUSTTOOLSFLAGS $CPPFLAGS "$tmpdir/$cppname" -o "$tmpdir/$clsname" || exit 1
"$tmpdir/$clsname" > "$tmpdir/$lv2name/$clsname.ttl"
rm -f "$tmpdir/$clsname"
fi
#trap - EXIT

# copy down the bundle
rm -rf "$SRCDIR/$lv2name"
cp -r "$tmpdir/$lv2name" "$SRCDIR"
if [[ $KEEP == yes ]]; then
    # keep the build directory
    rm -rf "$SRCDIR/$clsname"
    mv $tmpdir "$SRCDIR/$clsname"
else
    # Clean up.
    rm -rf $tmpdir
fi
# Print the name of the generated bundle zip file.
echo "$SRCDIR/$lv2name;"
