#!/bin/bash
#
#   makeVsProj -- Make Visual Studio Projects 
#
#   Copyright (c) Embedthis LLC, 2003-2009. All Rights Reserved.
#
#   It is expected that makeVsProj is only invoked from the Embedthis build systems. It
#   expects the Makefile and and make.rules to have defined all the necessary compile 
#   and link flag environment variables. It can't really be run on the command
#   line as it will be missing BLD_LDFLAGS etc.
#

#
#   TODO - check appweb project release profile. Check linker and compiler settings
#   make vsproj target
#   What is the UniqueIdentifier="{4FC737F1-C7A5-4376-A066-2A32D752A2FF}" is this different per project? If so, must generate
#
#   - cflags includes -arch
#   AdditionalDep   is missing .lib

ARCHIVE=
CFLAGS=
DFLAGS="WIN32;_DEBUG;_WINDOWS"
DO=
ENTRY=
EXECUTABLE=
OBJECT_FILE_LIST=
GRAPHICAL=
IFLAGS=
LDFLAGS=
LIBRARY=
LIBRARY_PATHS=
LIBS=
MAKE_STATIC=
OMITSTDLIBS=0
RESOURCES=
RPATH_LIST=
SHARED_LIBRARY=
SONAME=0
SOURCES=
SYSLIBS=
VERBOSE=1
VERSION=1.0
VERSION_SONAME=0
VERBOSE=${VERBOSE:=0}

###############################################################################

parseArgs() {
    local argc

    argc=$#

    echo -en "\nmakeVsProj "
	i=1
    while [ $i -le $argc ] ; do
		echo -en "\"${!i}\" "
		i=$((i + 1))
	done
	echo

    _INDEX=1
    while [ $_INDEX -le $argc ] ; do

        getSwitch "$@"

        case "${_SWITCH}" in 
        --cflags)
            CFLAGS="$CFLAGS ${_ARG}"
			useArg
            ;;
        --dflags)
            DFLAGS="$DFLAGS ${_ARG}"
			useArg
            ;;
        --dry-run)
            DO=:
            ;;
        --entry)
            ENTRY="${_ARG}"
			useArg
            ;;
        --executable|--exe)
            EXECUTABLE="${_ARG}"
			useArg
            ;;
        --graphical)
            GRAPHICAL=1
            ;;
        --help)
            usage
            ;;
        --iflags)
            IFLAGS="$IFLAGS ${_ARG}"
			useArg
            ;;
        --ldflags)
            LDFLAGS="$LDFLAGS ${_ARG}"
			useArg
            ;;
        --library)
            dir="${_ARG%${_ARG##*/}}"
            # "${dir:=./}" != "/" && dir="${dir%?}"
            : ${dir:=./}
            base=${_ARG##*/}
            noExtension=${base%\.*}
            LIBRARY="${dir}${noExtension}"
			useArg
            ;;
        --libs)
            LIBS="$LIBS ${_ARG}"
			useArg
            ;;
        --omitstdlibs)
            OMITSTDLIBS=1
            ;;
        --objects)
    #TODO - remove
            OBJECTS="$OBJECTS ${_ARG}"
			useArg
            ;;
        --objectList)
            OBJECT_FILE_LIST="$OBJECT_FILE_LIST ${_ARG}"
			useArg
            ;;
        --project)
            PROJECT="${_ARG}"
			useArg
            ;;
        --quiet)
            ;;
        --resources)
    #TODO - not needed?
            RESOURCES="${_ARG}"
			useArg
            ;;
        --rpath)
            #   Change ' ' to @ temporarily
            RPATH_LIST="$RPATH_LIST ${_ARG// /@}"
			useArg
            ;;
        --search)
            LIBRARY_PATHS="$LIBRARY_PATHS ${_ARG}"
			useArg
            ;;
        --shared)
            MAKE_STATIC=0
            ;;
        --soname)
    #TODO - not needed?
            SONAME=1
            ;;
        --static)
    #TODO - used?
            MAKE_STATIC=1
            ;;
        --syslibs)
            SYSLIBS="$SYSLIBS ${_ARG}"
			useArg
            ;;
        --version)
            echo $VERSION
            exit 0
            ;;
        --versionSoname)
    #TODO - no needed
            SONAME=1
            VERSION_SONAME=1
            ;;
        --verbose)
            VERBOSE=1
            ;;
        *)  argc=0
            ;;
        esac
    done
}


# TODO - cull args not supported or needed
# TODO --sources ""

usage() {
    cat <<!EOF
makeVsProj: usage: makeVsProj [options] objects ....
    Options:
# Delete
    --cflags                Compiler flags
    --debug                 Not implemented
# Delete
    --dry-run               Trace commands but do not execute
    --entry name            Shared library entry point
    --exe name              Name of executable to build
    --graphical             Create a windowed program instead of a console.
    --help                  Print usage information
    --iflags flags          Include search path
    --ldflags               Linker flags
    --library name          Name of library to link
    --libs libraries        Extra libraries to link with. Use shared or static 
                            intelligently depending on BLD_FEATURE_STATIC.
# Delete
    --omitstdlibs           Don't use standard libraries (UNIX only)
    --objects "objects..."  String containing objects to link
    --project               Project file name
    --quiet                 Run quietly without tracing actions to stdout
    --resources file        Resource file (menus and icons for Windows)
    --rpath path            Specify the executable run-time library search path
    --search "paths"        Paths to search for the libraries
    --shared                Only make a shared library
# delete
    --soname                Create a shared library with soname (no version)
    --static                Only make a static library
    --syslibs libraries     Extra system libraries to link with
    --version               Print the makeVsProj version
    --versionSoname         Create a versioned shared library with soname
    --verbose               Verbose operation. Traces internal information.

    Environment variables used:
      BLD_TOP               Top of the source tree
      BLD_FEATURE_STATIC    Build static libraries where relevant.

    Configuration files used:
        buildConfig.sh
!EOF
    exit 255
}


#
#   Search for library in the specified search locations.
#
searchLib()
{
    local syslib path libName extraSysLib prefix name suffix suffixes file

    name="$1"

    [ "$VERBOSE" -gt 0 ] && echo -e "\nmakeVsProj: searchLib for library $name" >&2

    suffixes="${SUFFIXES}"
    if [ "${name%.*}" != "${name}" ] ; then
        suffixes=".${name#*.}"
        name="${name%.*}"
    fi

    for path in ${LIBRARY_PATHS} "${BLD_LIB_DIR}" "${BLD_LIB_PREFIX}" "$syslib" "$extraSysLib"
    do
        for suffix in `echo $suffixes`
        do
            for prefix in lib ""
            do
                [ "$path" = "" ] && continue

                file=`shopt -s extglob ; eval ls -1 "${path}/${prefix}${name}?(.*)${suffix}*" 2>/dev/null | head -1`
				
                libName="${file##*/}"

                [ "$VERBOSE" -gt 0 ] && echo "makeVsProj: TESTING ${file}" >&2

                if [ -f "${file}" -o -L "${file}" ] ; then
                    [ "$VERBOSE" -gt 0 ] && echo -e "makeVsProj: FOUND ${file}\n" >&2
                    echo "${file}"
                    return
                fi
            done
        done
    done
    echo ""
}


#
#   Find a library. Look in the library paths with a "lib" prefix first.
#
findLib() {
    local path libName static suffixes

    libName="$1"

    [ "$VERBOSE" -gt 0 ] && echo -e "\nmakeVsProj: findLib: search for $libName using suffixes \"$SUFFIXES\"" >&2
    newName=`searchLib ${libName}`

    if [ "$newName" != "" ] ; then
        echo "$newName"
        return
    fi
    echo -e "\nWarning: makeVsProj could not find library \"$libName\". Build may not complete." >&2

    if [ "$newName" != "" -a "$newName" != "${newName%$BLD_ARCH}" ] ; then
        #
        #   If static library, use actual path. Fixed MAC OSX which insists on dynamic lib priority
        #
        echo "$newName"
        return
    fi

    libName="${libName%$BLD_ARCH}"
    libName="${libName%$BLD_SHLIB}"
    echo "${libName}.lib"
}


#
#   For some reason, link commands can complete and the executable is not yet visible on windows.
#
waitFor() {
    local timeout file

    file=$1
    timeout=$2

    while [ ! -x $file -a $timeout -gt 0 ] ; do
        [ $VERBOSE -gt 0 ] && echo "File $file not yet present, waiting ..."
        sleep 1
        timeout=$(($timeout - 1))
    done
}


basename() {
    local name="${1##*/}"
    echo "${name%$2}"
}


#
#   Find the top level directory
#
findTop() {
    local top level

    top=$BLD_TOP
    if [ "$top" = "" ] ; then
        top=.
        level=0
        while [ $level -lt 30 ] ; do
            if [ -d $top/build -a -d $top/build/bin -a -d $top/bin ] ; then
                break
            fi
            top=$top/..
            level=$((level + 1))
        done
        top=${top#./}

        if [ $level -ge 30 ] ; then
            echo "Can't find top level directory with build and bin directories" >&2
            exit 255
        fi
    fi
    echo $top
}


output() {
    echo "$*" >>$PROJECT
}


#
#   Emit the project header
#
#   TODO - try to cleanup the formatting?
outputHeader() {
    local guid

    guid=`uuidgen`

    cat >>$PROJECT <<!EOF_HEADER
<?xml version="1.0" encoding="UTF-8"?>
<VisualStudioProject
	ProjectType="Visual C++"
	Version="9.00"
	Name="${PROJECT%.*}"
	ProjectGUID="${guid}"
    RootNamespace=""
	Keyword="Win32Proj"
	TargetFrameworkVersion="0"
	>
	<Platforms>
		<Platform
			Name="Win32"
		/>
	</Platforms>
	<ToolFiles>
	</ToolFiles>
	<Configurations>
!EOF_HEADER
}


#
#   Emit a block per configuration
#
outputConfiguration() {
    local name

    name=$1

    cat >>$PROJECT <<!EOF_CONFIGURATION_A
		<Configuration
			Name="${name}|Win32"
			OutputDirectory="\$(SolutionDir)\$(ConfigurationName)"
			IntermediateDirectory="\$(ConfigurationName)"
			ConfigurationType="1"
			CharacterSet="0"
			>
			<Tool
				Name="VCPreBuildEventTool"
			/>
			<Tool
				Name="VCCustomBuildTool"
			/>
			<Tool
				Name="VCXMLDataGeneratorTool"
			/>
			<Tool
				Name="VCWebServiceProxyGeneratorTool"
			/>
			<Tool
				Name="VCMIDLTool"
			/>
			<Tool
				Name="VCManagedResourceCompilerTool"
			/>
			<Tool
				Name="VCResourceCompilerTool"
			/>
			<Tool
				Name="VCPreLinkEventTool"
			/>
			<Tool
				Name="VCALinkTool"
			/>
			<Tool
				Name="VCManifestTool"
			/>
			<Tool
				Name="VCXDCMakeTool"
			/>
			<Tool
				Name="VCBscMakeTool"
			/>
			<Tool
				Name="VCFxCopTool"
			/>
			<Tool
				Name="VCAppVerifierTool"
			/>
			<Tool
				Name="VCPostBuildEventTool"
			/>
			<Tool
				Name="VCCLCompilerTool"
				UsePrecompiledHeader="0"
				WarningLevel="3"
                ExceptionHandling="0"
                CompileAs="0"
                StringPooling="true"
                MinimalRebuild="true"
!EOF_CONFIGURATION_A

    if [ "$name" = Debug ] ; then
        output "				AdditionalIncludeDirectories=\"${IFLAGS}\""
        output '				Optimization="0"'
        output "				PreprocessorDefinitions=\"${DFLAGS}\""
        output '                BasicRuntimeChecks="3"'
        output '                RuntimeLibrary="3"'
        output '				DebugInformationFormat="4"'
    else 
        output "				AdditionalIncludeDirectories=\"${IFLAGS}\""
        output '				Optimization="2"'
        output "				PreprocessorDefinitions=\"${DFLAGS}\""
        output '                RuntimeLibrary="2"'
        output '				DebugInformationFormat="3"'
        output '                EnableIntrinsicFunctions="true"'
        output '                EnableFunctionLevelLinking="true"'
        output '                FavorSizeOrSpeed="1"'
        output '                InlineFunctionExpansion="2"'
    fi

    cat >>$PROJECT <<!EOF_CONFIGURATION_B
			/>
			<Tool
				Name="VCLinkerTool"
				TargetMachine="1"
                AdditionalLibraryDirectories="${LINK_LIBRARY_PATHS}"
                AdditionalDependencies="${LIB_LIST}"
!EOF_CONFIGURATION_B
    if [ "$EXECUTABLE" -a "$GRAPHICAL" ] ; then
        output '				SubSystem="2"'
    else
        output '				SubSystem="1"'
    fi

    if [ "$name" = Debug ] ; then
        output '				LinkIncremental="2"'
        output '				GenerateDebugInformation="true"'
    else
        output '				LinkIncremental="1"'
        output '				GenerateDebugInformation="false"'
        output '                OptimizeReferences="2"'
        output '                EnableCOMDATAFolding="2"'
    fi
    cat >>$PROJECT <<!EOF_CONFIGURATION_C
			/>
		</Configuration>
!EOF_CONFIGURATION_C
}


outputEndConfigurations() {
    cat >>$PROJECT <<!EOF_END_CONFIGURATIONS
	</Configurations>
	<References>
	</References>
	<Files>
!EOF_END_CONFIGURATIONS
}


outputHeaders() {
    local f uuid

    [ "$HEADERS" = "" ] && return

    uuid=`uuidgen`

    cat >>$PROJECT <<!EOF_HEADERS_A
		<Filter
			Name="Header Files"
			Filter="h;hpp;hxx;hm;inl;inc;xsd"
			UniqueIdentifier="{${uuid}}"
			>
!EOF_HEADERS_A

    for f in $HEADERS ; do
        f=${f//\//\\}
        output "            <File"
        output "                RelativePath=\"${f}\""
        output "                >"
        output "            </File>"
    done

    output "        </Filter>"
}


outputResources() {
    local f uuid

    [ "$RESOURCES" = "" ] && return

    uuid=`uuidgen`
    cat >>$PROJECT <<!EOF_RESOURCES_A
		<Filter
			Name="Resource Files"
			Filter="rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx"
			UniqueIdentifier="{${uuid}}"
			>
!EOF_RESOURCES_A

    for f in $RESOURCES ; do
        f=${f//\//\\}
        output "            <File"
        output "                RelativePath=\"${f}\""
        output "                >"
        output "            </File>"
    done

    output "        </Filter>"
}


outputSources() {
    local f uuid

    uuid=`uuidgen`

    [ "$SOURCES" = "" ] && return

    cat >>$PROJECT <<!EOF_SOURCES_A
		<Filter
			Name="Source Files"
			Filter="cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx"
			UniqueIdentifier="{${uuid}}"
			>
!EOF_SOURCES_A

    for f in $SOURCES ; do
        f=${f//\//\\}
        output "            <File"
        output "                RelativePath=\"${f}\""
        output "                >"
        #
        #   Temporary while sqlite has so many warnings
        #
        if [ "${f##*\\}" = "sqlite3.c" -o "${f##*\\}" = "sqlite.c" -o "${f##*\\}" = "esql.c" ] ; then
            output '            <FileConfiguration Name="Release|Win32">'
            output '                <Tool Name="VCCLCompilerTool" WarningLevel="1" />'
            output '            </FileConfiguration>'
            output '            <FileConfiguration Name="Debug|Win32">'
            output '                <Tool Name="VCCLCompilerTool" WarningLevel="1" />'
            output '            </FileConfiguration>'
        fi
        output "            </File>"
    done

    output "        </Filter>"
}


#
#   Emit the final trailer for the project file
#
outputTrailer() {

    cat >>$PROJECT <<!EOF_TRAILER
	</Files>
	<Globals>
	</Globals>
</VisualStudioProject>
!EOF_TRAILER
}



#
#   Get next command line argument. Uses globals _INDEX, _SWITCH, _ARG.
#
getSwitch()
{
    local i sw arg

    : =${_INDEX:=1}

    _ARG=
    _EQUALS_ARG=
    _SWITCH=

    if [ "${!_INDEX##--}" = "${!_INDEX}" ] ; then
		#
		#	Not a switch
		#
		return
	fi

	#
	#	Extract the value when the format is: --switch=value
	#
    _SWITCH=${!_INDEX%%=*}
    _EQUALS_ARG=${!_INDEX##*=}
    _INDEX=$((_INDEX + 1))
    if [ "$_EQUALS_ARG" != "$_SWITCH" ] ; then
		_ARG="$EQUALS_ARG"
	else
		_EQUALS_ARG=
		_ARG=${!_INDEX}
	fi
}


useArg() {
	_INDEX=$((_INDEX + 1))
}

###############################################################################
#
#   Main
#

BLD_TOP=`findTop`

. ${BLD_TOP}/build/buildConfig.sh

parseArgs "$@"

argc=$#
shift $((_INDEX - 1))

OS=${BLD_OS}

if [ "$PROJECT" = "" ] ; then
    echo "makeVsProj: must specify a project name with --project"
    exit 2
fi

#
#   Add the flags from user make files, build/make/make.*, and from configure
#
CFLAGS="$CFLAGS ${MAKE_CFLAGS} ${_CFLAGS} ${BLD_CFLAGS}"
DFLAGS="$DFLAGS ${MAKE_DFLAGS} ${_DFLAGS} ${BLD_DFLAGS}"
IFLAGS="$IFLAGS ${MAKE_IFLAGS} ${_IFLAGS} ${BLD_IFLAGS}"
LDFLAGS="$LDFLAGS ${MAKE_LDFLAGS} ${_LDFLAGS} ${BLD_LDFLAGS}"

IFLAGS=`echo $IFLAGS | sed 's/-I//g; s/ /;/g'`

if [ "$MAKE_STATIC" = "" ] ; then
    MAKE_STATIC=$BLD_FEATURE_STATIC
fi

#
#   Prioritize library suffix search order
#
SUFFIXES=
if [ $MAKE_STATIC = 0 ] ; then
    SUFFIXES="$SUFFIXES ${BLD_SHLIB} ${BLD_ARCH}"
else
    SUFFIXES="$SUFFIXES ${BLD_ARCH} ${BLD_SHLIB}"
fi

if [ "${OBJECT_FILE_LIST}" ]
then
    for f in ${OBJECT_FILE_LIST}
    do
        OBJECTS="$OBJECTS `cat ${f}`"
    done
fi

#
#   Add remaining args as objects. And fixup objects to add object extension and object directory
#
for f in $*
do
    case ${f##*.} in
    c|cpp)
        SOURCES="${SOURCES} ${f}"
        ;;
    h)
        HEADERS="${HEADERS} ${f}"
        ;;
    rc)
        RESOURCES="${RESOURCES} ${f}"
        ;;
    ## TODO - add other file types and categories
    esac
done

#
#   Remove redundant libraries
#
LIBRARY_PATHS="`echo $_LDPATH | sed 's/-L//'` $LIBRARY_PATHS"
LIBRARY_PATHS=`echo ${LIBRARY_PATHS} | tr ' ' '\n' | uniq | tr '\n' ' '`
LIBS=`echo ${LIBS} | tr ' ' '\n' | uniq | tr '\n' ' '`

#
#   Prepare for action
#
paths=
for p in ${LIBRARY_PATHS} ; do
    if [ "${p}" != "" ] ; then
        paths="${paths};${p}"
    fi
done
LINK_LIBRARY_PATHS=${paths#;*}

LIB_LIST=

for l in ${LIBS} ; do
    if [ "${l}" != "" ] ; then
#        libName=`findLib $l` 
        LIB_LIST="${LIB_LIST} ${l}"
    fi
done

for l in ${SYSLIBS} ; do
    if [ "${l}" != "" ] ; then
        LIB_LIST="${LIB_LIST} ${l}"
    fi
done
LIB_LIST=${LIB_LIST# *}

if [ "$LIBRARY" != "" ] ; then
    if [ $MAKE_STATIC = 0 -a "$ENTRY" = "" ]
    then
        ENTRY="_DllMainCRTStartup@12"
    fi
fi

if [ "$EXECUTABLE" ] ; then
    if [ "$GRAPHICAL" ] ; then
        ENTRY=WinMainCRTStartup
        SUBSYSTEM="WINDOWS"
    else 
        ENTRY=mainCRTStartup
        SUBSYSTEM="CONSOLE"
    fi
fi

if [ "$OMITSTDLIBS" = "0" ] ; then
    if [ "$BLD_FEATURE_STATIC" = "1" ] ; then
        STND_LIBS=$_STATIC_LIBS
    else
        STND_LIBS=$_SHARED_LIBS
    fi
fi


rm -f $PROJECT

outputHeader
for config in Release Debug ; do
    outputConfiguration $config
done
outputEndConfigurations
outputHeaders
outputSources
outputResources
outputTrailer
unix2dos -D $PROJECT 2>/dev/null

exit 0
