#!/bin/bash
###############################################################################
# BRLTTY - A background process providing access to the console screen (when in
#          text mode) for a blind person using a refreshable braille display.
#
# Copyright (C) 1995-2026 by The BRLTTY Developers.
#
# BRLTTY comes with ABSOLUTELY NO WARRANTY.
#
# This is free software, placed under the terms of the
# GNU Lesser General Public License, as published by the Free Software
# Foundation; either version 2.1 of the License, or (at your option) any
# later version. Please see the file LICENSE-LGPL for details.
#
# Web Page: http://brltty.app/
#
# This software is maintained by Dave Mielke <dave@mielke.cc>.
###############################################################################

set -e
umask 022
export MAKEFLAGS=

copyProperties() {
   local from="${1}"
   local to="${2}"
   shift 2

   for suffix in ${*}
   do
      setVariable "${to}${suffix}" "$(getVariable "${from}${suffix}")"
   done
}

installFile() {
   local source="${1}"
   local target="${2}"

   [ "${target%/}" = "${target}" ] || target="${target}${source##*/}"

   if [ -e "${source}" ]
   then
      local path="${installRoot}/${target}"
      mkdir -p "${path%/*}"
      cp "${source}" "${path}"
   else
      logWarning "file not installed: ${target}"
   fi
}

installFiles() {
   local target="${1}"
   shift 1
   local source

   for source
   do
      installFile "${source}" "${target}"
   done
}

installPackages() {
   while [ "${#}" -gt 0 ]
   do
      local package="${1}"
      shift 1

      local name="$(getVariable "${package}Name")"
      logTask "installing package files: ${name}"
      "installPackage_${package}"
   done
}

installPackage_libusb0() {
   if [ -n "${libusb0Root}" ]
   then
      cd "${libusb0Root}/bin"
      installFile "x86/libusb0.sys" "bin/libusb0.sys"
      installFile "x86/libusb0_x86.dll" "bin/libusb0.dll"
      installFile "amd64/libusb0.sys" "bin/libusb0_x64.sys"
      installFile "amd64/libusb0.dll" "bin/libusb0_x64.dll"
   else
      logWarning "${libusb0Name} not installable"
   fi
}

installPackage_libusb1() {
   if [ -n "${libusb1Root}" ]
   then
      cd "${libusb1Root}"
      installFile "MinGW32/dll/libusb-1.0.dll" "bin/"
   else
      logWarning "${libusb1Name} not installable"
   fi
}

installPackage_winusb() {
   if [ -n "${winusbRoot}" ]
   then
      cd "${winusbRoot}"
      installFile "x86/winusbcoinstaller2.dll" "bin/x86/"
      installFile "amd64/winusbcoinstaller2.dll" "bin/amd64/"
      installFile "x86/WdfCoInstaller01009.dll" "bin/x86/"
      installFile "amd64/WdfCoInstaller01009.dll" "bin/amd64/"
   else
      logWarning "${winusbName} not installable"
   fi
}

. "`dirname "${0}"`/../brltty-prologue.sh"
. "${programDirectory}/mingw32.sh"

defaultUsbPackage="libusb"
defaultTemporaryDirectory="${TMPDIR:-/tmp}/brltty-${programName}"

addProgramOption u string.package usbPackage "which USB package to use" "${defaultUsbPackage}"
addProgramOption s flag invokeShell "invoke interactive shell to inspect/modify result"
addProgramOption t string.directory temporaryDirectory "the temporary directory to use for the build" "${defaultTemporaryDirectory}"
addProgramOption k flag keepBuild "keep (do not remove) the temporary directory"
addWindowsPackageOption M msvc
addWindowsPackageOption A ahk
addWindowsPackageOption N nsis
addWindowsPackageOption U libusb0
addWindowsPackageOption X libusb1
addWindowsPackageOption W winusb
addWindowsPackageOption P python
addWindowsPackageOption I icu
addProgramOption C string.directory cygwinRoot "the root directory of a Cygwin installation"
addProgramParameter source sourceRoot "the top-level directory of the source tree"
optionalProgramParameters
addProgramParameter revision buildRevision "the revision of the build"
parseProgramArguments "${@}"

sourceRoot="$(cd "${sourceRoot}" && pwd -W)"
[ -f "${sourceRoot}/configure" ] || semanticError "not a source tree: ${sourceRoot}"

revisionIdentifier="$("${sourceRoot}/getrevid" "${sourceRoot}")"
logTask "revision identifier: ${revisionIdentifier}"

[ -n "${buildRevision}" ] || {
   buildRevision="${revisionIdentifier%-g*}-"
   buildRevision="${buildRevision#*-}"
   buildRevision="${buildRevision#*-}"

   if [ -n "${buildRevision}" ]
   then
      buildRevision="${buildRevision%-}"
   else
      buildRevision="0"
   fi
}

[ -n "${cygwinRoot}" ] || {
   directory=/cygwin
   [ -e "${directory}" ] && cygwinRoot="${directory}"
}

[ -n "${cygwinRoot}" ] && {
   if [ -f "${cygwinRoot}/cygwin.ico" ]
   then
      export PATH="${PATH}:${cygwinRoot}/bin"
      export nodosfilewarning=1
   else
      logWarning "not a Cygwin root directory: ${cygwinRoot}"
   fi
}

[ -n "${usbPackage}" ] || usbPackage="${defaultUsbPackage}"

case "${usbPackage}"
in
   winusb) usbWindowsPackages=();;
   libusb) usbWindowsPackages=("libusb0");;
   libusb-1.0) usbWindowsPackages=("libusb1" "winusb");;
   *) syntaxError "unrecognized USB package: ${usbPackage}"
esac

winusbFilter=""

libusb0Filter="\
    select "USB:", and install the ${libusb0Name} filter by doing one of the
    following:
    + run Install ${libusb0Name} Filter from the Start Menu
    + answer yes to the setup prompt for installing the ${libusb0Name} filter
    + install it by hand, from ${libusb0Download}"

libusb1Filter="\
    you will need to use ${libusb0Name} instead because
    ${libusb1Name} does not support this requirement."

set -- "${usbWindowsPackages[@]}"
copyProperties "${1}" usb Name Location Download Version Pattern Filter

[ -z "${msvcRoot}" ] || {
   if verifyWindowsPackage msvc
   then
      export PATH="${msvcRoot}:${PATH}"
   fi
}

getPythonLocation pythonRoot && {
   if verifyWindowsPackage python
   then
      export PATH="${pythonRoot}:${pythonRoot}/Scripts:${PATH}"
      export PYTHON="${pythonRoot}/python"
   fi
}

verifyWindowsPackages ahk nsis icu "${usbWindowsPackages[@]}" || :
verifyWindowsCommands lib python cython || :
verifyMingwPackages pthreads curses || :
verifyMingwCommands zip unix2dos linuxdoc doxygen groff || :

if [ -z "${temporaryDirectory}" ]
then
   temporaryDirectory="${defaultTemporaryDirectory}"
   rm -f -r "${temporaryDirectory}"
elif [ -e "${temporaryDirectory}" ]
then
   semanticError "directory already exists: ${temporaryDirectory}"
fi

mkdir -p "${temporaryDirectory}"
cd "${temporaryDirectory}"
temporaryDirectory="$(pwd -W)"

logTask "copying source tree"
resolveDirectory "${sourceRoot}" rsyncSource
newSourceRoot="${temporaryDirectory}/source"
mkdir "${newSourceRoot}"
resolveDirectory "${newSourceRoot}" rsyncTarget
rsyncOptions=(--recursive --perms --times)
rsyncOptions+=(--exclude "/Android/Gradle/??l")
rsync "${rsyncOptions[@]}" -- "${rsyncSource}/" "${rsyncTarget}/"
cd "${newSourceRoot}"
sourceRoot="${newSourceRoot}"

logTask "applying patches"
for patch in affinity
do
   patch -p0 <"Windows/${patch}.patch"
done

infFile="${usbPackage}.inf"
infPath="Windows/${infFile}"
Tools/updusbdevs -q "c:Programs/usb_devices.c" "inf:${infPath}"
Tools/updcsvs -q

buildRoot="${temporaryDirectory}/build"
mkdir -p "${buildRoot}"
cd "${buildRoot}"

logTask "configuring build"
"../source/cfg-windows" \
   --prefix=/ \
   --enable-relocatable-install \
   --with-usb-package="${usbPackage}" \
   --quiet

. ./brltty-config.sh
buildVersion="${BRLTTY_VERSION}-${buildRevision}"
buildName="${BRLTTY_TARNAME}-win32-${buildVersion}-${usbPackage}"

logTask "building programs"
make -s
make -s -C Drivers/BrlAPI/WindowEyes we-dynamic-library-windows

logTask "building documents"
make -s -C Documents

logTask "installing files"
installRoot="${temporaryDirectory}/install/${buildName}"
make -s install install-messages \
   JAVA_JAR_DIR=/lib JAVA_JNI_DIR=/lib \
   PYTHON_PREFIX="Python" \
   INSTALL_ROOT="${installRoot}"

logTask "updating files"
documentDirectory="doc"

readmeHeader="\
This build of ${BRLTTY_NAME} ${BRLTTY_VERSION} includes Windows-specific patches.

You should probably read doc/BRLTTY.txt and doc/Windows.txt for information
about BRLTTY.

Here are some notes on how to get started:

- BRLTTY only gives access to text consoles. For the rest of the Windows
  environment, you need to also install and run NVDA.
- Either use the BRLTTY configurator (brlttycnf.exe) or manually uncomment the
  appropriate lines in etc/brltty.conf.
- For Python support (e.g. for NVDA), install Brlapi-${BRLAPI_RELEASE}-<PythonVersion>-win32.whl using pip.
- For sighted users, use the xw braille driver to get a virtual braille box.
"

readmeFooter="\

If you are having problems, please run debug-brltty.bat and send us the
debug.log and brltty.conf files.

Documentation can be found in the doc/ subdirectory.

==============================
Technical notes on this build:

- Source Revision: ${revisionIdentifier}
- BRLTTY Version: ${BRLTTY_VERSION}
- BrlAPI Version: ${BRLAPI_RELEASE}
- Some MinGW-specific path and file name changes have been made.
- To make life easier for Windows users, the BrlAPI server was modified to:
  * not use authentication by default (BRLAPI_DEFAUTH set to none)
  * only use local sockets (:0+127.0.0.1:0 changed to :0)
- *${usbPattern}* files come from ${usbName} ${usbVersion}, which can be found at:
  ${usbDownload}
- Python bindings are provided by: Brlapi-${BRLAPI_RELEASE}-<PythonVersion>-win32.whl
- C bindings are provided in: include/, and lib/
  A .lib file is provided for linking in (for example) Visual C. Then you can
  just ship bin/brlapi${BRLAPI_RELEASE%.*}.dll alongside your .exe application.
"

cd "${buildRoot}"
installFile "Documents/brltty.conf" "etc/"
installFile "Drivers/BrlAPI/WindowEyes/webrloem109.dll" "WindowEyes/"

installFiles "Debug/" config.h config.mk config.log
installFiles "Debug/" Programs/*.exe Programs/*.dll

set -- Bindings/Python/dist/Brlapi-${BRLAPI_RELEASE}-*.whl
if [ "${#}" -gt 0 ]
then
   for file
   do
      installFile "${file}" "Python/"
   done
else
   logWarning "Python bindings installer not found"
fi

cd "${sourceRoot}"
installFile "LICENSE-LGPL" "LICENSE-LGPL.txt"
installFile "README" "${documentDirectory}/BRLTTY.txt"
installFile "Drivers/Braille/XWindow/UBraille.ttf" "Fonts/"
installFile "Drivers/BrlAPI/WindowEyes/README" "${documentDirectory}/WindowEyes.txt"
installFile "${infPath}" "bin/brltty-${infFile}"
installFiles "etc/" Documents/*.csv

for document in ChangeLog HISTORY TODO
do
   installFile "Documents/${document}" "${documentDirectory}/${document}.txt"
done

for document in Windows BrailleDots TextTables ContractionTables AttributesTables KeyTables
do
   installFile "Documents/README.${document}" "${documentDirectory}/${document}.txt"
done

for root in "${sourceRoot}" "${buildRoot}"
do
   cd "${root}/Documents"

   for manual in Manual-BRLTTY Manual-BrlAPI BrlAPIref
   do
      [ -d "${manual}" ] || continue

      for file in $(find "${manual}" -type f -print)
      do
         name="${file##*/}"
         extension="${name##*.}"

         case "${extension}"
         in
            txt | html | htm | doc | pdf)
               installFile "${file}" "${documentDirectory}/${file}"
               ;;

            *);;
         esac
      done
   done
done

cd "${sourceRoot}/Drivers"
for document in $(find Braille Speech -type f -name "README*" -print)
do
   installFile "${document}" "${documentDirectory}/Drivers/${document}.txt"
done

cd "${programDirectory}"
installFiles "bin/" *.dll *.cat
installFiles "/" *.bat

logTask "USB package: ${usbPackage}"
installPackages "${usbWindowsPackages[@]}"

cd /mingw/bin
for file in libgcc_s_dw2-1.dll libiconv-2.dll libpdcurses*.dll libintl-8.dll
do
   installFile "${file}" "bin/"
done

cd "${installRoot}"
rm -f "bin/brltty-config"
rm -f "etc/brlapi.key"

echo "${revisionIdentifier}" >"REVISION.txt"

for source in $(find "share/man" -type f -name "*.[1-9]" -print)
do
   [ -z "${groffPath}" ] || {
      target="${source%.*}.txt"

      "${groffPath}" -T ascii -mandoc 2>/dev/null <"${source}" |
      sed -e 's/'$'\033''\[[0-9]*m//g' >"${target}"

      [ -s "${target}" ] || rm -f "${target}"
   }

   rm -f "${source}"
done

cat >"README.txt" <<END-OF-README
${readmeHeader}
- To register BRLTTY as a service so that it will get started automatically at
  boot, run enable-brlapi.bat. To unregister it, run disable-brlapi.bat.
- If your braille device uses a USB connection:
  * If you either cannot, or would prefer not to, install its
    manufacturer's driver, then you can select "USB:", and install
    the ${usbName} driver by doing one of the following:
    + right-clicking on bin/brltty-${usbPackage}.inf and selecting install
    + answering yes to the setup prompt for installing the ${usbName} driver
  * If you have installed its manufacturer's driver, and if that driver defines
    a virtual COM port, then select that virtual COM port.
  * If you need to install its manufacturer's driver, but
    that driver does not define a virtual COM port, then 
${usbFilter}
- If your braille device uses a serial connection, or if it is connected via a
  serial to USB converter, then select the correct COM port. Make sure to
  select the correct braille driver as well, because serial autodetection may
  brick some devices.
- If your braille device uses a Bluetooth connection, you can either use the
  Windows Bluetooth facilities to create a virtual COM port which you can then
  select, or manually configure the braille-device line in brltty.conf.
${readmeFooter}
END-OF-README

if [ -n "${libPath}" ]
then
   cd "${installRoot}/lib"
   "${libPath}" //nologo /def:brlapi.def /name:brlapi-${BRLAPI_VERSION}.dll /out:brlapi.lib /machine:x86
else
   logWarning "import library not creatable"
fi

cd "${installRoot}/.."
cat >"README.txt" <<END-OF-README
${readmeHeader}
${readmeFooter}
END-OF-README

# for the installer but before text file conversion
nsisScript="${usbPackage}.nsi"
nsisStrings="nsistrings.txt"

for file in "${nsisScript}" "brltty.nsi" "${nsisStrings}"
do
   path="${programDirectory}/${file}"

   if [ -f "${path}" ]
   then
      cp "${path}" .
   else
      logWarning "installer source file not found: ${file}"
      nsisRoot=""
   fi
done

if [ -n "${ahkRoot}" ]
then
   logTask "creating configurator"
   cd "${installRoot}"
   cp "${programDirectory}/brlttycnf.ahk" .
   "${ahkRoot}/Compiler/Ahk2Exe.exe" //in brlttycnf.ahk //out brlttycnf.exe
   rm brlttycnf.ahk
else
   logWarning "configurator not creatable"
fi

if [ -n "${unix2dosPath}" ]
then
   logTask "converting text files"
   cd "${installRoot}/.."

   find . -print |
      while read path
      do
         handle="${path#.}"
         [ -n "${handle}" ] || continue
         handle="${handle#/}"

         name="${path##*/}"
         extension="${name##*.}"

         if [ -f "${path}" ]
         then
            if [ "${extension}" != "${name}" ]
            then
               case "${extension}"
               in
                  bash | bat | cat | conf | csv | h | htm | html | inf | log | lua | mk | nsi | pc | policy | sh | tcl | txt | xml | [tcak]t[bi])
                     "${unix2dosPath}" -q -o "${path}"
                     ;;

                  a | def | dll | doc | egg-info | exe | exp | lib | mo | pdf | pyd | sys | ttf | whl);;
                  *) logWarning "unexpected file extension: ${handle}";;
               esac
            fi
         elif [ ! -d "${path}" ]
         then
            logWarning "unsupported special file: ${handle}"
         fi
      done
else
   logWarning "text files not convertible"
fi

if "${invokeShell}"
then
   logTask "invoking shell"
   cd "${installRoot}"
   "${SHELL:-/bin/sh}" || :
fi

if [ -n "${zipPath}" ]
then
   logTask "creating archive"
   archiveFile="${initialDirectory}/${buildName}.zip"
   rm -f "${archiveFile}"
   cd "${installRoot}/.."
   "${zipPath}" -q -A -r "${archiveFile}" "${buildName}"
else
   logWarning "archive not creatable"
fi

if [ -n "${nsisRoot}" ]
then
   logTask "creating installer"
   cd "${installRoot}/.."
   mv "${nsisStrings}" "${buildName}"
   installerFile="${buildName}.exe"

   "${nsisRoot}/makensis" -V2 \
      -DVERSION="${buildVersion}" \
      -DDISTDIR="${buildName}" \
      -DOUTFILE="${installerFile}" \
      "${nsisScript}"

   rm -f "${initialDirectory}/${installerFile}"
   cp "${installerFile}" "${initialDirectory}/${installerFile}"
else
   logWarning "installer not creatable"
fi

"${keepBuild}" || {
   logTask "cleaning up"
   cd "${initialDirectory}"
   rm -f -r "${temporaryDirectory}"
}

logTask "done"
exit 0
