#!/usr/bin/env bash

CONFIG="config.mk"
PREFIX="/usr/local"

DEBUG=
LIQSRCDIR=./lib
LIQCONFIGURE=(--quiet)
SSE=auto
OPENMP=
LIBPNG_DIR=.
LCMS2=auto
EXTRA_CFLAGS=
EXTRA_LDFLAGS=

# make gcc default compiler unless CC is already set
CC=${CC:-gcc}
SKIP_CC_CHECK=0

help() {
    printf "%4s %s\n" "" "$1"
}

for i in "$@"; do
    case $i in
    --help|-h)
        echo
        help "--prefix=<dir>                installation directory [$PREFIX]"
        help "--extra-cflags=<flags>        append to CFLAGS"
        help "--extra-ldflags=<flags>       append to LDFLAGS"
        echo
        help "--enable-debug"
        help "--enable-sse/--disable-sse    enable/disable SSE instructions"
        echo
        help "--with-libimagequant=<dir>    external libimagequant (lib/ default)"
        help "--with-openmp=static          compile with multicore support"
        help "--with-lcms2/--without-lcms2  compile with color profile support"
if [[ "$OSTYPE" =~ "darwin" ]]; then
        help "--with-cocoa/--without-cocoa  use Cocoa framework to read images"
fi
        help "--with-libpng=<dir>           search for libpng in directory"
        echo
        help "CC=<compiler>                 use given compiler command"
        help "CFLAGS=<flags>                pass options to the compiler"
        help "LDFLAGS=<flags>               pass options to the linker"
        echo
        exit 0
        ;;
    # Can be set before or after configure. Latter overrides former.
    CC=*)
        CC=${i#*=}
        SKIP_CC_CHECK=1
        LIQCONFIGURE+=("$i")
        ;;
    CFLAGS=*)
        CFLAGS=${i#*=}
        LIQCONFIGURE+=("$i")
        ;;
    LDFLAGS=*)
        LDFLAGS=${i#*=}
        LIQCONFIGURE+=("$i")
        ;;
    --enable-debug)
        DEBUG=1
        LIQCONFIGURE+=("$i")
        ;;
    --enable-sse)
        SSE=1
        LIQCONFIGURE+=("$i")
        ;;
    --disable-sse)
        SSE=0
        LIQCONFIGURE+=("$i")
        ;;
    --with-openmp)
        OPENMP=1
        LIQCONFIGURE+=("$i")
        ;;
    --with-openmp=static)
        OPENMP=static
        LIQCONFIGURE+=("$i")
        ;;
    --with-lcms2)
        LCMS2=1
        ;;
    --without-lcms2)
        LCMS2=0
        ;;
    --with-cocoa)
        LCMS2=auto
        ;;
    --without-cocoa)
        LCMS2=auto
        ;;
    --with-libpng=*)
        LIBPNG_DIR=${i#*=}
        ;;
    --with-libimagequant=*)
        LIQSRCDIR=${i#*=}
        ;;
    --with-libimagequant)
        LIQSRCDIR=""
        ;;
    --prefix=*)
        PREFIX=${i#*=}
        LIQCONFIGURE+=("$i")
        ;;
    # can be used multiple times or in quotes to set multiple flags
    --extra-cflags=*)
        EXTRA_CFLAGS="$EXTRA_CFLAGS ${i#*=}"
        LIQCONFIGURE+=("$i")
        ;;
    --extra-ldflags=*)
        EXTRA_LDFLAGS="$EXTRA_LDFLAGS ${i#*=}"
        LIQCONFIGURE+=("$i")
        ;;
    *)
        echo "warning: unknown switch ${i%%=*} (see $0 --help for the list)"
        ;;
    esac
done

# If someone runs sudo make install as very first command, and configure later,
# $CONFIG cannot be overwritten, and must be deleted before continuing.
if [[ -f "$CONFIG" && ! -w "$CONFIG" ]]; then
    echo "Cannot overwrite file $CONFIG! Please delete it."
    exit 1
fi

cflags() {
    CFLAGS="$CFLAGS $1"
}

lflags() {
    LDFLAGS="$LDFLAGS $1"
}

status() {
    printf "%10s: %s\n" "$1" "$2"
}

# Append to CFLAGS if compiler supports flag, with optional prerequisite.
# Fails on errors and warnings.
conditional_cflags() {
    if [ -z "$(echo | "$CC" -xc -S -o /dev/null $2 $1 - 2>&1)" ]; then
        cflags "$1"
    fi
}

# returns first matching file in directory
find_f() {
    echo $(find "$1" -not -path '*/.git/*' -not -type d -name "$2" -print -quit 2> /dev/null)
}

# returns first matching file in directory (no symlinks)
find_h() {
    echo $(find "$1" -not -path '*/.*/*' -type f -name "$2" -print -quit 2> /dev/null)
}

find_pkgconfig() {
    local LIBNAME=$1
    PKG_CONFIG=${PKG_CONFIG:-pkg-config}
    if $PKG_CONFIG --exists "$LIBNAME" &> /dev/null; then
        cflags "$($PKG_CONFIG --cflags "$LIBNAME")"
        lflags "$($PKG_CONFIG --libs "$LIBNAME")"
        LIBRARY_FOUND_VERSION=$($PKG_CONFIG --modversion "$LIBNAME")
        status "$LIBNAME" "shared ($LIBRARY_FOUND_VERSION)"
        return 0
    fi
    if ! type "$PKG_CONFIG" &> /dev/null; then
        echo "warning: pkg-config is not installed, can't search for libraries. Please install pkg-config."
    fi
    return 1
}

find_static() {
    local LIBNAME=$1
    local HEADERPATTERN=$2
    local STATICPATTERN=$3

    local HPATH=$(find_h . "$HEADERPATTERN")
    if [ -n "$HPATH" ]; then
        local APATH=$(find_f . "$STATICPATTERN")
        if [ -n "$APATH" ]; then
            LIBRARY_FOUND_HEADER=$HPATH
            cflags "-I${HPATH%/*}"
            lflags "${APATH}"
            status "$LIBNAME" "static ($APATH)"
            return 0
        fi
    fi
    return 1
}

find_dynamic() {
    local LIBNAME=$1
    local HEADERPATTERN=$2
    local DYNAMICPATTERN=$3
    local HEADERDIR=$4
    local LIBDIR=$5

    local HPATH=$(find_h "$HEADERDIR" "$HEADERPATTERN")
    if [ -n "$HPATH" ]; then
        local SOPATH=$(find_f "$LIBDIR" "$DYNAMICPATTERN")
        if [ -n "$SOPATH" ]; then
            LIBRARY_FOUND_HEADER=$HPATH
            cflags "-I${HPATH%/*}"
            lflags "-L${SOPATH%/*} -l$DYNAMICLIBNAME"
            status "$LIBNAME" "shared ... $SOPATH"
            return 0
        fi
    fi
    return 1
}

find_library() {
    local LIBNAME=$1
    local DYNAMICLIBNAME=$2
    local HEADERPATTERN=$3
    local STATICPATTERN=$4
    local DYNAMICPATTERN=$5

    # other functions will overwrite it
    LIBRARY_FOUND_HEADER=
    LIBRARY_FOUND_VERSION=
    LIBRARY_FOUND_BUILD=

    if [ "imagequant" = "$LIBNAME" -a -n "$LIQSRCDIR" -a -f "$LIQSRCDIR"/"$HEADERPATTERN" ]; then
        status "$LIBNAME" "build static"
        cflags "-I$LIQSRCDIR"
        lflags "$LIQSRCDIR/libimagequant.a"
        LIBRARY_FOUND_HEADER="$LIQSRCDIR"/"$HEADERPATTERN"
        LIBRARY_FOUND_BUILD="$LIQSRCDIR"
        return 0;
    fi

    # try static in current directory first
    if find_static "$LIBNAME" "$HEADERPATTERN" "$STATICPATTERN"; then
        return 0;
    fi

    # try shared
    if find_pkgconfig "$LIBNAME"; then
        return 0
    fi

    for i in "${DIRS[@]}"; do
        DIR=($i)
        if find_dynamic "$LIBNAME" "$HEADERPATTERN" "$DYNAMICPATTERN" "${DIR[0]}" "${DIR[1]}"; then
            return 0
        fi
    done
    return 1
}

# returns full png.h version string
pngh_string() {
    echo "$(grep -m1 "define PNG_LIBPNG_VER_STRING" "$1" | \
            grep -Eo '"[^"]+"' | grep -Eo '[^"]+')"
}

# returns major minor version numbers from png.h
pngh_majmin() {
    local MAJ=$(grep -m1 "define PNG_LIBPNG_VER_MAJOR" "$1" | grep -Eo "[0-9]+")
    local MIN=$(grep -m1 "define PNG_LIBPNG_VER_MINOR" "$1" | grep -Eo "[0-9]+")
    echo "${MAJ}${MIN}"
}

error() {
    status "$1" "error ... $2"
    echo
    exit 1
}

echo

# /tmp, because mingw has problems opening /dev/null and gives false negative
if ! echo "int main(){}" | "$CC" -xc -std=c99 -o pngquant-gcccheck - > /dev/null; then
    rm -f pngquant-gcccheck
    if [ "$SKIP_CC_CHECK" -eq 1 ]; then
        status "Compiler" "$CC failed to compile anything (make sure it's installed and supports C99)"
    else
        error "Compiler" "$CC failed to compile anything (make sure it's installed and supports C99)"
    fi
fi
rm -f pngquant-gcccheck

status "Compiler" "$CC"

# init flags
CFLAGS=${CFLAGS:--fno-math-errno -funroll-loops -fomit-frame-pointer -Wall}
cflags "-std=c99 -I."

# DEBUG
if [ -z "$DEBUG" ]; then
    cflags "-O3 -DNDEBUG"
    status "Debug" "no"
else
    cflags "-O1 -g -DDEBUG"
    status "Debug" "yes"
fi

# SSE
if [ "$SSE" = 'auto' ]; then
    SSE=0
    if type uname > /dev/null; then
        if [[ "$(uname -m)" =~ "amd64" || "$(uname -m)" =~ "x86_64" ||
              "$(grep -E -m1 "^flags" /proc/cpuinfo)" =~ "sse" ]]; then
            SSE=1
        fi
    fi
fi

if [ "$SSE" -eq 1 ]; then
    status "SSE" "yes"
    cflags "-DUSE_SSE=1"
    cflags "-msse"
    # Silence a later ICC warning due to -msse working slightly different.
    conditional_cflags "-wd10121"
    # Must be set explicitly for GCC on x86_32. Other compilers imply it.
    conditional_cflags "-mfpmath=sse" "-msse"
elif [ "$SSE" -eq 0 ]; then
    status "SSE" "no"
    cflags "-DUSE_SSE=0"
fi

# OpenMP
if [ -n "$OPENMP" ]; then
    if [ "static" = "$OPENMP" ]; then
        OPENMPFLAGS="-static-libgcc -Bstatic -fopenmp -Bdynamic"
    else
        OPENMPFLAGS="-fopenmp"
    fi
    if [[ "$("$CC" -xc -E $OPENMPFLAGS <(echo "#ifdef _OPENMP
           #include <omp.h>
           #endif") 2>&1)" =~ "omp_get_thread_num" ]]; then
        cflags "$OPENMPFLAGS"
        lflags "$OPENMPFLAGS"
        status "OpenMP" "yes"
    else
        error "OpenMP" "not supported by compiler (please install a compiler that supports OpenMP (e.g. gcc) and specify it with the CC= argument)"
    fi
else
    # silence warnings about omp pragmas
    cflags "-Wno-unknown-pragmas"
    conditional_cflags "-wd3180" # ICC
    status "OpenMP" "no"
fi

# Cocoa
if [[ "$OSTYPE" =~ "darwin" ]]; then
    if [ -z "${MACOSX_DEPLOYMENT_TARGET+isset}" ]; then
        cflags "-mmacosx-version-min=10.9"
        lflags "-mmacosx-version-min=10.9"
    fi
fi

# pairs of possible *.h and lib*.so locations
DIRS=()

if command -v >/dev/null libpng-config; then
    DIRS+=("$(libpng-config --prefix) $(libpng-config --libdir)")
fi

if [ -n $"LIQSRCDIR" ]; then
    DIRS+=("$LIQSRCDIR" "$LIQSRCDIR") # local libimagequant
fi

DIRS+=(
      "/usr/local/include /usr/local/lib"
      "/usr/include /usr/lib64"
      "/usr/include /usr/lib"
      "/opt/local/include /opt/local/lib" # macports
      )

if [[ "$OSTYPE" =~ "darwin" ]]; then
    SOLIBSUFFIX=dylib

    # Search Developer SDK paths, since Apple seems to have dropped the standard Unixy ones
    XCODE_CMD="xcode-select"
    XCODE_PATH=$($XCODE_CMD -p)
    DIRS+=("$XCODE_PATH/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include $XCODE_PATH/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/lib")
    DIRS+=("$XCODE_PATH/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.9.sdk/usr/include $XCODE_PATH/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.9.sdk/usr/lib")
elif [[ "$OSTYPE" =~ "msys" ]]; then
    SOLIBSUFFIX=dll
else
    SOLIBSUFFIX=so
fi


# libimagequant
if [ "./lib" = "$LIQSRCDIR" -a -d ".git" -a ! -f "lib/Cargo.toml" ]; then
    git submodule init && git submodule update;
fi


if find_library "imagequant" "imagequant" "libimagequant.h" "libimagequant.a" "libimagequant.$SOLIBSUFFIX*"; then
    if [ -n "$LIBRARY_FOUND_VERSION" ]; then
        VERSION=$LIBRARY_FOUND_VERSION
    elif [ -n "$LIBRARY_FOUND_HEADER" ]; then
        VERSION=$(grep LIQ_VERSION_STRING "$LIBRARY_FOUND_HEADER" | grep -Eo "2\.[0-9.]+")
    else
        VERSION=unknown
    fi

    if [ -n "$LIBRARY_FOUND_BUILD" ]; then
        STATICLIBDEPS="$LIBRARY_FOUND_BUILD/libimagequant.h $LIBRARY_FOUND_BUILD/libimagequant.a"
        LIQSRCDIR="$LIBRARY_FOUND_BUILD"
    fi
else
    if [ -n "$LIQSRCDIR" -a ! -f "$LIQSRCDIR"/libimagequant.h ]; then
        echo "If you're using git, do clone with --recursive, or download from https://github.com/ImageOptim/libimagequant"
        error "imagequant" "libimagequant.h not found in $LIQSRCDIR/"
    else
        error "imagequant" "not found. Get it from https://github.com/ImageOptim/libimagequant and build it in ./lib/"
    fi
fi

# libpng
# try if given flags are enough
HAS_LIBPNG=0
if echo "#include \"png.h\"
    int main(){
    return !png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
}" | "$CC" -xc -std=c99 -o /dev/null $CFLAGS $LDFLAGS - &> /dev/null; then
    status "libpng" "custom flags"
    HAS_LIBPNG=1
fi

if [ "$HAS_LIBPNG" -eq 0 ]; then
    # try static in the given directory
    PNGH=$(find_h "$LIBPNG_DIR" "png.h")
    if [ -n "$PNGH" ]; then
        PNGH_STRING=$(pngh_string "$PNGH")
        PNGH_MAJMIN=$(pngh_majmin "$PNGH")
        if [[ -n "$PNGH_STRING" && -n "$PNGH_MAJMIN" ]]; then
            LIBPNGA=$(find_f "$LIBPNG_DIR" "libpng${PNGH_MAJMIN}.a")
            if [ -z "$LIBPNGA" ]; then
                LIBPNGA=$(find_f "$LIBPNG_DIR" "libpng.a")
            fi
            if [ -n "$LIBPNGA" ]; then
                cflags "-I${PNGH%/*}"
                lflags "${LIBPNGA}"
                status "libpng" "static (${PNGH_STRING})"
                HAS_LIBPNG=1
            fi
        fi
    fi
fi
# try shared
if [ "$HAS_LIBPNG" -eq 0 ]; then
    if find_pkgconfig libpng; then
        HAS_LIBPNG=1
    else
        for i in "${DIRS[@]}"; do
            DIR=($i)
            PNGH=$(find_h "${DIR[0]}" "png.h")
            if [ -n "$PNGH" ]; then
                PNGH_STRING=$(pngh_string "$PNGH")
                PNGH_MAJMIN=$(pngh_majmin "$PNGH")
                if [[ -n "$PNGH_STRING" && -n "$PNGH_MAJMIN" ]]; then
                    LIBPNGSO=$(find_f "${DIR[1]}" "libpng${PNGH_MAJMIN}.$SOLIBSUFFIX*")
                    if [ -n "$LIBPNGSO" ]; then
                        cflags "-I${PNGH%/*}"
                        lflags "-L${LIBPNGSO%/*} -lpng${PNGH_MAJMIN}"
                        status "libpng" "shared (${PNGH_STRING})"
                        HAS_LIBPNG=1
                        break
                    fi
                fi
            fi
        done
    fi
fi
if [ "$HAS_LIBPNG" -eq 0 ]; then
    if [[ "$OSTYPE" =~ "darwin" ]]; then
        LIBPNG_CMD='`brew install libpng`'
    else
        LIBPNG_CMD='`apt-get install libpng16-dev` or `apt-get install libpng-dev` or `yum install libpng-devel`'
    fi
    error "libpng" "not found (try: $LIBPNG_CMD)"
fi

# zlib
if ! find_library "zlib" "z" "zlib.h" "libz.a" "libz.$SOLIBSUFFIX*"; then
    if ! find_library "zlib" "z" "zlib.h" "zlib.a" "zlib.$SOLIBSUFFIX*"; then
        error "zlib" "not found (please install zlib-devel package)"
    fi
fi

# lcms2
if [ "$LCMS2" != 0 ]; then
    if find_library "lcms2" "lcms2" "lcms2.h" "liblcms2.a" "liblcms2.$SOLIBSUFFIX*"; then
        cflags "-DUSE_LCMS=1"
    else
        if [ "$LCMS2" = 'auto' ]; then
            status "lcms2" "no"
        else
            error "lcms2" "not found (please install libcms2-devel package)"
        fi
    fi
else
    status "lcms2" "no"
fi

echo

# As of GCC 4.5, 387 fp math is significantly slower in C99 mode without this.
# Note: CPUs without SSE2 use 387 for doubles, even when SSE fp math is set.
conditional_cflags "-fexcess-precision=fast"

# Intel C++ Compiler

# ICC does usually only produce fast(er) code when it can optimize to the full
# capabilites of the (Intel) CPU. This is equivalent to -march=native for GCC.
conditional_cflags "-xHOST"

# Disable unsafe fp optimizations and enforce fp precision as set in the source.
conditional_cflags "-fp-model source"

# Silence a gold linker warning about string misalignment.
conditional_cflags "-falign-stack=maintain-16-byte"

lflags "-lm" # Ubuntu requires this library last, issue #38

if [ -n "$EXTRA_CFLAGS" ]; then
    cflags "$EXTRA_CFLAGS"
fi

if [ -n "$EXTRA_LDFLAGS" ]; then
    lflags "$EXTRA_LDFLAGS"
fi

# Overwrite previous configuration.
echo "
# auto-generated by configure
PREFIX = $PREFIX
VERSION = $VERSION
CC = $CC
CFLAGS = $CFLAGS
LDFLAGS = $LDFLAGS
SOLIBSUFFIX = $SOLIBSUFFIX
STATICLIBDEPS = $STATICLIBDEPS
LIQSRCDIR = $LIQSRCDIR
LIQCONFIGUREFLAGS = $(printf "'%s' " "${LIQCONFIGURE[@]}")
" > "$CONFIG"

