#
# Functions for detection and usage of then Android SDK
# Used when cross compiling for Android
#
# The Android device emulator (included in the SDK) can
# be "remote" controlled by the functions and macros
# implemented in this file.
# They are employing a pseudo object-oriented approach
# to handle an emulator instance, that is an emulator
# instance is described by a so called emulator instance
# object handle which has to be passed to all functions
# operating on it as their first argument.
# Android emulator object handles consist of the
# following properties, which need to be tracked to
# describe and Android emulator instance:
#  EMULATOR_STATE: the state of the emulator instance,
#    can be one of the following values:
#       * STARTING - the instance has been initialized
#                    and the emulator has been executed
#       * RUNNING  - the emulated device is online and
#                    can receive instructions
#       * STOPPED  - the emulated device has been
#                    shutdown or is currently being
#                    shutdown
#  EMULATOR_UUID: a generated ID to identify the emulator
#    instance. Meant to prevent accessing the wrong device
#    in case multiple Android devices are accessible.
#  EMULATOR_NAME: the name of the emulator instance.
#    all running emulator instances are named by the SDK
#    in a locally unique fashion. The name will only be
#    available in case the state is 'RUNNING', since
#    only the name of running devices can be retrieved
#    by UUID matching.
#    An emulator instance is accessed by the different
#    tools of the NDK (e. g. 'adb') by referring to the
#    instance with the value of EMULATOR_NAME.

INCLUDE(CMakeParseArguments)

#
# 'Unpacks' an Android emulator instance handle object to
# individual components.
# VAR - the emulator instance handle object to unpack
# EMULATOR_STATE - will contain the emulators state
# EMULATOR_UUID  - will contain the generated UUID that
#                  identifies the emulator instance
# EMULATOR_NAME  - will contain the instance name, required
#                  to access the emulator via 'adb' etc.
# All three outputs will be unset if the object in VAR
# is not a valid emulator instance handle.
# All additional arguments will be ignored.
#
MACRO(DCMTK_ANDROID_GET_OBJECT_PROPERTIES VAR)
    LIST(LENGTH ${VAR} ${VAR}_LENGTH)
    IF(${VAR}_LENGTH EQUAL 3)
        LIST(GET ${VAR} 0 EMULATOR_STATE)
        LIST(GET ${VAR} 1 EMULATOR_UUID)
        LIST(GET ${VAR} 2 EMULATOR_NAME)
    ELSE()
        UNSET(EMULATOR_STATE)
        UNSET(EMULATOR_UUID)
        UNSET(EMULATOR_NAME)
    ENDIF()
ENDMACRO(DCMTK_ANDROID_GET_OBJECT_PROPERTIES)

#
# Stores the properties of an emulator instance inside an
# emulator instance handle object.
# VAR - the name of the variable that shall contain the
#       resulting instance handle object.
# EMULATOR_STATE - the state to set
# EMULATOR_UUID  - the UUID to set
# EMULATOR_NAME  - the name to set
# All additional arguments will be ignored.
#
MACRO(DCMTK_ANDROID_SET_OBJECT_PROPERTIES VAR EMULATOR_STATE EMULATOR_UUID EMULATOR_NAME)
    SET(${VAR} "${EMULATOR_STATE}" "${EMULATOR_UUID}" "${EMULATOR_NAME}" CACHE INTERNAL "")
ENDMACRO(DCMTK_ANDROID_SET_OBJECT_PROPERTIES)

#
# Destroys an instance handle object.
# VAR - the name of the variable its emulator instance handle
#       shall be object destroyed
# All additional arguments will be ignored.
#
MACRO(DCMTK_ANDROID_DESTROY_OBJECT VAR)
    UNSET(${VAR} CACHE)
ENDMACRO(DCMTK_ANDROID_DESTROY_OBJECT)

#
# Tries to detect any required shared objects within the NDK
# VAR - the name of the variable that will receive a list
#       of required shared object files (if any)
# Will set VAR to an empty list if no shared objects are
# required (e. g. when they are linked statically).
# All additional arguments will be ignored.
#
FUNCTION(DCMTK_ANDROID_FIND_RUNTIME_LIBRARIES VAR)
    SET(CMAKE_FIND_LIBRARY_SUFFIXES ".so")
    FOREACH(DIR ${ANDROID_STL_INCLUDE_DIRS})
        IF(CMAKE_VERSION VERSION_LESS 2.8.11)
            GET_FILENAME_COMPONENT(DIR ${DIR} PATH)
        ELSE()
            GET_FILENAME_COMPONENT(DIR ${DIR} DIRECTORY)
        ENDIF()
        LIST(APPEND ANDROID_STL_LIBRARY_DIRS ${DIR})
    ENDFOREACH()
    FIND_LIBRARY(ANDROID_STL_SHARED_OBJECT ${ANDROID_STL} PATHS ${ANDROID_STL_LIBRARY_DIRS} NO_DEFAULT_PATH NO_CMAKE_FIND_ROOT_PATH)
    IF(ANDROID_STL_SHARED_OBJECT)
        SET(${VAR} ${ANDROID_STL_SHARED_OBJECT} PARENT_SCOPE)
    ENDIF()
ENDFUNCTION(DCMTK_ANDROID_FIND_RUNTIME_LIBRARIES)

#
# Tries to detect the required executables and scripts automatically,
# which should be contained in the Android SDK. Emits an error that
# will halt the configuration process if that is not possible.
# Will also detect all available AVDs after tool detection was
# successful.
# All arguments will be ignored.
#
FUNCTION(DCMTK_SETUP_ANDROID_EMULATOR)
    IF(NOT ANDROID_TEMPORARY_FILES_LOCATION)
        SET(ANDROID_TEMPORARY_FILES_LOCATION "/cache" CACHE STRING "The path on the Android device that should be used for temporary files")
    ENDIF()
    IF(NOT ANDROID_SDK_ROOT)
        IF(CMAKE_HOST_SYSTEM MATCHES "Windows.*")
            FILE(TO_CMAKE_PATH "$ENV{PROGRAMFILES}" ANDROID_SDK_SEARCH_PATHS)
            SET(ANDROID_SDK_ROOT "${ANDROID_SDK_SEARCH_PATHS}/android-sdk" CACHE PATH "Location of the Android SDK")
        ELSE()
            SET(ANDROID_SDK_ROOT "/opt/android-sdk" CACHE PATH "Location of the Android SDK")
        ENDIF()
    ENDIF()
    FIND_PROGRAM(ANDROID_EMULATOR_PROGRAM emulator PATHS ${ANDROID_SDK_ROOT} PATH_SUFFIXES tools NO_DEFAULT_PATH NO_CMAKE_FIND_ROOT_PATH)
    IF(CMAKE_HOST_SYSTEM MATCHES "Windows.*")
        FIND_PROGRAM(ANDROID_ANDROID_PROGRAM android.bat PATHS ${ANDROID_SDK_ROOT} PATH_SUFFIXES tools NO_DEFAULT_PATH NO_CMAKE_FIND_ROOT_PATH)
    ELSE()
        FIND_PROGRAM(ANDROID_ANDROID_PROGRAM android PATHS ${ANDROID_SDK_ROOT} PATH_SUFFIXES tools NO_DEFAULT_PATH NO_CMAKE_FIND_ROOT_PATH)
    ENDIF()
    FIND_PROGRAM(ANDROID_ADB_PROGRAM adb PATHS ${ANDROID_SDK_ROOT} PATH_SUFFIXES platform-tools NO_DEFAULT_PATH NO_CMAKE_FIND_ROOT_PATH)
    IF(NOT ANDROID_EMULATOR_PROGRAM OR NOT ANDROID_ANDROID_PROGRAM OR NOT ANDROID_ADB_PROGRAM)
        MESSAGE(FATAL_ERROR
            "Failed to detect the Android SDK, please set ANDROID_SDK_ROOT to the location of your Android SDK"
            "or set the missing tools manually!"
        )
    ELSE()
        EXECUTE_PROCESS(COMMAND ${ANDROID_ANDROID_PROGRAM} list avd RESULT_VARIABLE RESULT OUTPUT_VARIABLE OUTPUT ERROR_QUIET)
        STRING(REGEX MATCHALL "Name:[ \t]*[^\r\n]*" ANDROID_AVAILABLE_AVDS ${OUTPUT})
        STRING(REGEX REPLACE "Name:[ \t]*([^\r\n;]*)" "\\1" ANDROID_AVAILABLE_AVDS "${ANDROID_AVAILABLE_AVDS}")
        SET(ANDROID_EMULATOR_AVD "${ANDROID_EMULATOR_AVD}" CACHE STRING "Android emulator Android Virtual Device (AVD) configuration" FORCE)
        SET_PROPERTY(CACHE ANDROID_EMULATOR_AVD PROPERTY STRINGS ${ANDROID_AVAILABLE_AVDS})
    ENDIF()
ENDFUNCTION(DCMTK_SETUP_ANDROID_EMULATOR)

#
# Enumerates all currently available Android devices
# (started emulators and connected devices) and separates
# them into two different lists.
# ONLINE  - the name of the variable that will be set to a
#           list of all currently online devices' names
#           (devices that can receive instructions)
# OFFLINE - the name of the variable that will be set to a
#           list of all currently offline device's names
#           (devices that are connected but cannot receive
#           instructions)
# Will ignore all additional arguments.
#
FUNCTION(DCMTK_ANDROID_LIST_EMULATORS ONLINE OFFLINE)
    DCMTK_SETUP_ANDROID_EMULATOR()
    IF(ANDROID_ADB_PROGRAM)
        EXECUTE_PROCESS(
            COMMAND ${ANDROID_ADB_PROGRAM} devices
            RESULT_VARIABLE RESULT
            OUTPUT_VARIABLE DEVICES_RAW
            ERROR_QUIET
        )
        STRING(REPLACE "\n" ";" DEVICES "${DEVICES_RAW}")
        FOREACH(DEVICE ${DEVICES})
            STRING(REGEX REPLACE "(.+)\t(.+)" "\\1;\\2" DS "${DEVICE}")
            LIST(LENGTH DS DSL)
            IF(DSL EQUAL 2)
                LIST(GET DS 0 EMULATOR_NAME)
                LIST(GET DS 1 EMULATOR_STATE)
                IF(EMULATOR_STATE MATCHES "^device$")
                    LIST(APPEND ${ONLINE} ${EMULATOR_NAME})
                ELSE()
                    LIST(APPEND ${OFFLINE} ${EMULATOR_NAME})
                ENDIF()
            ENDIF()
        ENDFOREACH()
    ENDIF()
    SET(${ONLINE} ${${ONLINE}} PARENT_SCOPE)
    SET(${OFFLINE} ${${OFFLINE}} PARENT_SCOPE)
ENDFUNCTION(DCMTK_ANDROID_LIST_EMULATORS)

#
# Generate a random ID that is hopefully unique
# enough to be used as the instance UUID.
# VAR - the name of the variable that will receive
#       the generated UUID as a string value
# Will ignore all additional arguments.
#
IF(CMAKE_VERSION VERSION_LESS 2.8.11)
MACRO(DCMTK_ANDROID_EMULATOR_GENERATE_UUID VAR)
    STRING(RANDOM LENGTH 20 ALPHABET "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" ${VAR})
ENDMACRO(DCMTK_ANDROID_EMULATOR_GENERATE_UUID)
ELSE(CMAKE_VERSION VERSION_LESS 2.8.11)
FUNCTION(DCMTK_ANDROID_EMULATOR_GENERATE_UUID VAR)
    STRING(RANDOM LENGTH 20 RAND)
    STRING(TIMESTAMP TM)
    SET(${VAR} "${TM}${RAND}")
    STRING(MD5 ${VAR} ${${VAR}})
    SET(${VAR} ${${VAR}} PARENT_SCOPE)
ENDFUNCTION(DCMTK_ANDROID_EMULATOR_GENERATE_UUID)
ENDIF(CMAKE_VERSION VERSION_LESS 2.8.11)

#
# Tries to query the UUID property of an accessible Android device.
# EMULATOR_NAME - the device name, as returned by DCMTK_ANDROID_LIST_EMULATORS
# VAR           - the name of the variable that will be set to the device UUID
# Will unset the variable referred to by VAR if no device with the given name
# is accessible or the device is offline.
# Will ignore all additional arguments.
#
FUNCTION(DCMTK_ANDROID_GET_EMULATOR_UUID EMULATOR_NAME VAR)
    EXECUTE_PROCESS(
        COMMAND ${ANDROID_ADB_PROGRAM} -s ${EMULATOR_NAME} shell getprop
        RESULT_VARIABLE RESULT
        OUTPUT_VARIABLE OUTPUT
        ERROR_QUIET
    )
    DCMTK_UNSET_PARENT_SCOPE(${VAR})
    IF(NOT RESULT)
        STRING(REGEX REPLACE ".*\\[emu\\.uuid\\]: \\[([^]]+)\\].*" "\\1" ${VAR} "${OUTPUT}")
        IF(${VAR})
            SET(${VAR} ${${VAR}} PARENT_SCOPE)
        ENDIF()
    ENDIF()
ENDFUNCTION(DCMTK_ANDROID_GET_EMULATOR_UUID)

#
# Retrieves the name of the emulator instance referred to by
# the given UUID. Will wait until the device becomes online
# to query the name.
# VAR           - the name of the variable that will be set
#                 to the device name
# EMULATOR_UUID - the emulator UUID of the device to inquired
# Will wait until all available devices are online or the right
# one has been found.
# Will ignore all additional arguments.
#
FUNCTION(DCMTK_ANDROID_GET_EMULATOR_NAME VAR EMULATOR_UUID)
    DCMTK_ANDROID_LIST_EMULATORS(ONLINE_EMULATORS OFFLINE_EMULATORS)
    FOREACH(EMULATOR ${ONLINE_EMULATORS})
        DCMTK_ANDROID_GET_EMULATOR_UUID(${EMULATOR} UUID)
        IF(EMULATOR_UUID STREQUAL UUID)
            SET(${VAR} ${EMULATOR} PARENT_SCOPE)
            RETURN()
        ENDIF()
    ENDFOREACH()
    WHILE(OFFLINE_EMULATORS)
        LIST(GET OFFLINE_EMULATORS 0 EMULATOR)
        LIST(REMOVE_AT OFFLINE_EMULATORS 0)
        EXECUTE_PROCESS(
            COMMAND ${ANDROID_ADB_PROGRAM} -s ${EMULATOR} wait-for-device
            TIMEOUT 1
            RESULT_VARIABLE RESULT
            OUTPUT_QUIET
            ERROR_QUIET
        )
        IF(NOT RESULT)
            DCMTK_ANDROID_GET_EMULATOR_UUID(${EMULATOR} UUID)
            IF(UUID)
                IF(EMULATOR_UUID STREQUAL UUID)
                    SET(${VAR} ${EMULATOR} PARENT_SCOPE)
                    RETURN()
                ENDIF()
            ELSE()
                LIST(APPEND OFFLINE_EMULATORS ${EMULATOR})
            ENDIF()
        ELSE()
            LIST(APPEND OFFLINE_EMULATORS ${EMULATOR})
        ENDIF()
    ENDWHILE()
ENDFUNCTION()

#
# Sets up and starts a new emulator instance or reuses an
# existing one.
# VAR - the emulator instance handle object to reuse or
#       initialize
# Will test if VAR refers to a valid emulator instance
# handle and, if so, ensure the device is started.
# Will initialize a new emulator instance and start it,
# if VAR does not refer to a valid emulator instance.
# Will ignore all additional arguments.
#
FUNCTION(DCMTK_ANDROID_START_EMULATOR VAR)
    DCMTK_SETUP_ANDROID_EMULATOR()
    IF(NOT ANDROID_EMULATOR_AVD)
        MESSAGE(FATAL_ERROR "Please select which Android emulator Android Virtual Device (AVD) configuration to use!")
    ELSE()
        DCMTK_ANDROID_GET_OBJECT_PROPERTIES(${VAR})
        IF(NOT EMULATOR_STATE)
            DCMTK_ANDROID_EMULATOR_GENERATE_UUID(EMULATOR_UUID)
        ELSEIF(EMULATOR_STATE STREQUAL "RUNNING")
            DCMTK_ANDROID_GET_EMULATOR_UUID(${EMULATOR_NAME} UUID)
            # Do nothing if the running emulator instance is ok and can be reused.
            # Otherwise restart it.
            IF(UUID STREQUAL EMULATOR_UUID)
                MESSAGE(STATUS "Reusing already running Android device emulator...")
                RETURN()
            ENDIF()
        ELSEIF(EMULATOR_STATE STREQUAL "STARTING")
            # Is it really starting, or has somebody aborted it?
            MESSAGE(STATUS "Found previously started Android device emulator, checking if it's still present...")
            DCMTK_ANDROID_GET_EMULATOR_NAME(EMULATOR_NAME ${EMULATOR_UUID})
            IF(EMULATOR_NAME)
              MESSAGE(STATUS "Found previously started Android device emulator, checking if it's still present... yes")
              DCMTK_ANDROID_SET_OBJECT_PROPERTIES(${VAR} RUNNING ${EMULATOR_UUID} ${EMULATOR_NAME})
              RETURN()
            ENDIF()
            MESSAGE(STATUS "Found previously started Android device emulator, checking if it's still present... no")
        ENDIF()
        MESSAGE(STATUS "Starting the Android device emulator...")
        IF(CMAKE_HOST_SYSTEM MATCHES "Windows.*")
            SET(COMMAND sh -c "${ANDROID_EMULATOR_PROGRAM} -avd ${ANDROID_EMULATOR_AVD} -no-boot-anim -noaudio -prop emu.uuid=${EMULATOR_UUID} >/dev/null 2>&1 < /dev/null &")
        ELSE()
            SET(COMMAND sh -c "${ANDROID_EMULATOR_PROGRAM} -avd ${ANDROID_EMULATOR_AVD} -no-window -no-boot-anim -noaudio -prop emu.uuid=${EMULATOR_UUID} >/dev/null 2>&1 < /dev/null &")
        ENDIF()
        EXECUTE_PROCESS(
            COMMAND ${COMMAND}
            RESULT_VARIABLE RESULT
            OUTPUT_QUIET
            ERROR_QUIET
        )
        IF(NOT RESULT)
            DCMTK_ANDROID_SET_OBJECT_PROPERTIES(${VAR} STARTING ${EMULATOR_UUID} "")
        ELSE()
            DCMTK_ANDROID_DESTROY_OBJECT(${VAR})
            MESSAGE(FATAL_ERROR "Error starting Android emulator.")
        ENDIF()
    ENDIF()
ENDFUNCTION(DCMTK_ANDROID_START_EMULATOR)

#
# Waits until the given emulator instance becomes online (RUNNING).
# VAR - the emulator instance handle object referring to the emulator
#       to wait for
# Will fail with a warning message if the given variable does not
# refer to a valid emulator instance handle object.
# Will set the object's state to "RUNNING" when successful.
# Will wait indefinitely if no such device exists or the device does
# not become online.
# Will ignore all additional arguments.
#
FUNCTION(DCMTK_ANDROID_WAIT_FOR_EMULATOR VAR)
    DCMTK_ANDROID_GET_OBJECT_PROPERTIES(${VAR})
    IF(NOT EMULATOR_STATE)
        MESSAGE(AUTHOR_WARNING "Error: ${VAR} is not a valid Android emulator instance handle.")
    ELSEIF(EMULATOR_STATE STREQUAL "RUNNING")
        # Emulator is running, no need to wait!
    ELSEIF(EMULATOR_STATE STREQUAL "STOPPED")
        MESSAGE(WARNING "The Android emulator has stopped, aborting...")
    ELSE()
        MESSAGE(STATUS "Waiting until the Android device emulator is ready to receive instructions...")
        WHILE(NOT EMULATOR_NAME)
            DCMTK_ANDROID_GET_EMULATOR_NAME(EMULATOR_NAME ${EMULATOR_UUID})
        ENDWHILE()
        DCMTK_ANDROID_SET_OBJECT_PROPERTIES(${VAR} RUNNING ${EMULATOR_UUID} ${EMULATOR_NAME})
    ENDIF()
ENDFUNCTION(DCMTK_ANDROID_WAIT_FOR_EMULATOR)

#
# Prepare a command that will shutdown the given Android emulator when
# in the local shell run.
# VAR           - the name of the variable that will be set to the
#                 generated command
# EMULATOR_NAME - the name of the emulated device that shall be shut
#                 down
# Will unset the variable referred to by VAR if no such command can
# be generated for the current host platform (Windows).
# Will ignore all additional arguments.
#
MACRO(DCMTK_ANDROID_STOP_EMULATOR_COMMAND VAR EMULATOR_NAME)
    IF(CMAKE_HOST_SYSTEM MATCHES "Windows.*")
        UNSET(${VAR})
    ELSE()
        SET(${VAR} ${ANDROID_ADB_PROGRAM} -s ${EMULATOR_NAME} emu kill)
    ENDIF()
ENDMACRO(DCMTK_ANDROID_STOP_EMULATOR_COMMAND)

#
# Prepare an emulator shutdown message or a warning message if automated
# shutdown is not possible.
# VAR - the name of the variable that will be set to the generated message
# Will ignore all additional arguments.
#
MACRO(DCMTK_ANDROID_EMULATOR_SHUTDOWN_MESSAGE VAR)
    IF(CMAKE_HOST_SYSTEM MATCHES "Windows.*")
        SET(${VAR} WARNING "The Android device emulator can't be terminated automatically under Windows, please shutdown \"${EMULATOR_NAME}\" manually!")
    ELSE()
        SET(${VAR} STATUS "Shutting down the Android device emulator...")
    ENDIF()
ENDMACRO(DCMTK_ANDROID_EMULATOR_SHUTDOWN_MESSAGE)

#
# Stops the emulator and sets its state to STOPPED when successful.
# VAR - the name of the variable referring to the emulator instance
#       handle object
# Will emit a warning message if the emulator can't be stopped and
# won't modify its state in that case.
# Will ignore all additional arguments.
#
FUNCTION(DCMTK_ANDROID_STOP_EMULATOR VAR)
    DCMTK_ANDROID_GET_OBJECT_PROPERTIES(${VAR})
    IF(NOT EMULATOR_STATE)
        MESSAGE(AUTHOR_WARNING "Error: ${VAR} is not a valid Android emulator instance handle.")
    ELSEIF(EMULATOR_STATE STREQUAL "STARTING")
        # Can't tell it to stop if it is not done starting
        DCMTK_ANDROID_WAIT_FOR_EMULATOR(${VAR})
    ELSEIF(EMULATOR_STATE STREQUAL "STOPPED")
        # Emulator is already stopped, do nothing
        RETURN()
    ENDIF()
    DCMTK_ANDROID_EMULATOR_SHUTDOWN_MESSAGE(MESSAGE)
    MESSAGE(${MESSAGE})
    IF(NOT CMAKE_HOST_SYSTEM MATCHES "Windows.*")
        DCMTK_ANDROID_STOP_EMULATOR_COMMAND(COMMAND ${EMULATOR_NAME})
        EXECUTE_PROCESS(
          COMMAND ${COMMAND}
          RESULT_VARIABLE RESULT
          OUTPUT_QUIET
          ERROR_QUIET
        )
        IF(NOT RESULT)
            DCMTK_ANDROID_SET_OBJECT_PROPERTIES(${VAR} STOPPED ${EMULATOR_UUID} "")
        ELSE()
            MESSAGE(WARNING "Unable to stop the android device emulator, please shutdown \"${EMULATOR_NAME}\" manually!")
        ENDIF()
    ENDIF()
ENDFUNCTION(DCMTK_ANDROID_STOP_EMULATOR)

#
# Uploads local files to the given Android device using 'adb push'.
# VAR - the emulator instance handle object referring to the target
#       device
# Requires the device to be running.
# Additional arguments will be parsed and interpreted similar to
# CMake's FILE(COPY ...) command.
#
FUNCTION(DCMTK_ANDROID_PUSH VAR)
    DCMTK_ANDROID_GET_OBJECT_PROPERTIES(${VAR})
    IF(NOT EMULATOR_STATE)
        MESSAGE(AUTHOR_WARNING "Error: ${VAR} is not a valid Android emulator instance handle.")
    ELSEIF(EMULATOR_STATE STREQUAL "RUNNING")
        CMAKE_PARSE_ARGUMENTS(DCMTK_ANDROID_PUSH "" "" "DESTINATION" ${ARGN})
        FOREACH(LOCAL_FILE ${DCMTK_ANDROID_PUSH_UNPARSED_ARGUMENTS})
            EXECUTE_PROCESS(
                COMMAND ${ANDROID_ADB_PROGRAM} -s ${EMULATOR_NAME} push ${LOCAL_FILE} ${DCMTK_ANDROID_PUSH_DESTINATION}
                RESULT_VARIABLE RESULT
                OUTPUT_QUIET
                ERROR_QUIET
            )
        ENDFOREACH()
    ELSE()
        MESSAGE(AUTHOR_WARNING "Error: the Android emulator \"${VAR}\" is not ready to receive commands")
    ENDIF()
ENDFUNCTION(DCMTK_ANDROID_PUSH)

#
# Downloads remote files from the given Android device using 'adb pull'.
# VAR - the emulator instance handle object referring to the target
#       device
# Requires the device to be running.
# Additional arguments will be parsed and interpreted similar to
# CMake's FILE(COPY ...) command.
#
FUNCTION(DCMTK_ANDROID_PULL VAR)
    DCMTK_ANDROID_GET_OBJECT_PROPERTIES(${VAR})
    IF(NOT EMULATOR_STATE)
        MESSAGE(AUTHOR_WARNING "Error: ${VAR} is not a valid Android emulator instance handle.")
    ELSEIF(EMULATOR_STATE STREQUAL "RUNNING")
        CMAKE_PARSE_ARGUMENTS(DCMTK_ANDROID_PULL "" "" "DESTINATION" ${ARGN})
        FOREACH(REMOTE_FILE ${DCMTK_ANDROID_PULL_UNPARSED_ARGUMENTS})
            EXECUTE_PROCESS(
                COMMAND ${ANDROID_ADB_PROGRAM} -s ${EMULATOR_NAME} pull ${REMOTE_FILE} ${DCMTK_ANDROID_PULL_DESTINATION}
                RESULT_VARIABLE RESULT
                OUTPUT_QUIET
                ERROR_QUIET
            )
        ENDFOREACH()
    ELSE()
        MESSAGE(AUTHOR_WARNING "Error: the Android emulator \"${VAR}\" is not ready to receive commands")
    ENDIF()
ENDFUNCTION(DCMTK_ANDROID_PULL)

#
# Executes a shell command on the given Android device using 'adb shell'.
# VAR - the emulator instance handle object referring to the target
#       device
# Requires the device to be running.
# Additional arguments will be parsed and interpreted similar to
# CMake's EXECUTE_PROCESS(...) command.
#
FUNCTION(DCMTK_ANDROID_SHELL VAR)
    DCMTK_ANDROID_GET_OBJECT_PROPERTIES(${VAR})
    IF(NOT EMULATOR_STATE)
        MESSAGE(AUTHOR_WARNING "Error: ${VAR} is not a valid Android emulator instance handle.")
    ELSEIF(EMULATOR_STATE STREQUAL "RUNNING")
        CMAKE_PARSE_ARGUMENTS(DCMTK_ANDROID_SHELL
            "OUTPUT_QUIET;ERROR_QUIET"
            "RESULT_VARIABLE;OUTPUT_VARIABLE;ERROR_VARIABLE;WORKING_DIRECTORY"
            "COMMAND"
            ${ARGN}
        )

        # Prepare commandline
        SET(COMMAND ${ANDROID_ADB_PROGRAM} -s ${EMULATOR_NAME} shell)
        IF(DCMTK_ANDROID_SHELL_WORKING_DIRECTORY)
            LIST(APPEND COMMAND "cd" "${DCMTK_ANDROID_SHELL_WORKING_DIRECTORY}" "&&")
        ENDIF()
        LIST(APPEND COMMAND "${DCMTK_ANDROID_SHELL_COMMAND}")

        # Inspect output variable parameters
        IF(DCMTK_ANDROID_SHELL_OUTPUT_VARIABLE)
            SET(PUSH_OUTPUT_VAR TRUE)
            IF(DCMTK_ANDROID_SHELL_ERROR_VARIABLE)
                IF(DCMTK_ANDROID_SHELL_ERROR_VARIABLE STREQUAL DCMTK_ANDROID_SHELL_OUTPUT_VARIABLE)
                    SET(STREAMS_MERGED TRUE)
                ELSE()
                    SET(PUSH_ERROR_VAR TRUE)
                ENDIF()
            ELSE()
                SET(DCMTK_ANDROID_SHELL_ERROR_VARIABLE DCMTK_ANDROID_SHELL_ERROR_MESSAGE_BUFFER)
            ENDIF()
        ELSE()
            SET(DCMTK_ANDROID_SHELL_OUTPUT_VARIABLE DCMTK_ANDROID_SHELL_OUTPUT_MESSAGE_BUFFER)
            IF(DCMTK_ANDROID_SHELL_ERROR_VARIABLE)
                SET(PUSH_ERROR_VAR TRUE)
            ELSE()
                SET(STREAMS_MERGED TRUE)
            ENDIF()
        ENDIF()

        # Prefix to prevent collision of output capturing files
        IF(CMAKE_VERSION VERSION_LESS 2.8.7)
            STRING(RANDOM LENGTH 20 ALPHABET "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" PREFIX)
        ELSE()
            STRING(MD5 PREFIX "${DCMTK_ANDROID_SHELL_COMMAND}")
        ENDIF()

        # Prepare output redirection (buffering)
        IF(STREAMS_MERGED)
            LIST(APPEND COMMAND > ${ANDROID_TEMPORARY_FILES_LOCATION}/${PREFIX}_output 2>&1)
        ELSE()
            LIST(APPEND COMMAND > ${ANDROID_TEMPORARY_FILES_LOCATION}/${PREFIX}_output 2> ${ANDROID_TEMPORARY_FILES_LOCATION}/${PREFIX}_error)
        ENDIF()

        # Prepare capturing the result
        LIST(APPEND COMMAND "\;" echo -n $? > ${ANDROID_TEMPORARY_FILES_LOCATION}/${PREFIX}_result)

        # Run the command
        EXECUTE_PROCESS(
          COMMAND ${COMMAND}
          RESULT_VARIABLE ADB_RESULT
          OUTPUT_VARIABLE ADB_OUTPUT
          ERROR_VARIABLE ADB_OUTPUT
        )

        # Deal with it
        IF(ADB_RESULT)
            MESSAGE(WARNING "Error sending command to the Android emulator, adb reported the error: ${ADB_OUTPUT}")
            IF(DCMTK_ANDROID_SHELL_RESULT_VARIABLE)
                SET(${DCMTK_ANDROID_SHELL_RESULT_VARIABLE} ${ADB_RESULT} PARENT_SCOPE)
            ENDIF()
        ELSE(ADB_RESULT)
            # Receive results
            DCMTK_ANDROID_PULL(${VAR} ${ANDROID_TEMPORARY_FILES_LOCATION}/${PREFIX}_output ${ANDROID_TEMPORARY_FILES_LOCATION}/${PREFIX}_error ${ANDROID_TEMPORARY_FILES_LOCATION}/${PREFIX}_result
                DESTINATION ${CMAKE_CURRENT_BINARY_DIR}${CMAKE_FILES_DIRECTORY}
            )
            # Cleanup temp files
            EXECUTE_PROCESS(
                COMMAND ${ANDROID_ADB_PROGRAM} -s ${EMULATOR_NAME} shell rm ${ANDROID_TEMPORARY_FILES_LOCATION}/${PREFIX}_output "\;" rm ${ANDROID_TEMPORARY_FILES_LOCATION}/${PREFIX}_error "\;" rm ${ANDROID_TEMPORARY_FILES_LOCATION}/${PREFIX}_result
                RESULT_VARIABLE RESULT_QUIET
                OUTPUT_QUIET
                ERROR_QUIET
            )
            # Analyze results
            IF(DCMTK_ANDROID_SHELL_RESULT_VARIABLE)
                FILE(READ ${CMAKE_CURRENT_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/${PREFIX}_result ${DCMTK_ANDROID_SHELL_RESULT_VARIABLE})
                SET(${DCMTK_ANDROID_SHELL_RESULT_VARIABLE} ${${DCMTK_ANDROID_SHELL_RESULT_VARIABLE}} PARENT_SCOPE)
            ENDIF()
            IF(NOT DCMTK_ANDROID_SHELL_OUTPUT_QUIET OR (STREAMS_MERGED AND NOT DCMTK_ANDROID_SHELL_ERROR_QUIET))
                FILE(READ ${CMAKE_CURRENT_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/${PREFIX}_output ${DCMTK_ANDROID_SHELL_OUTPUT_VARIABLE})
                IF(PUSH_OUTPUT_VAR)
                    SET(${DCMTK_ANDROID_SHELL_OUTPUT_VARIABLE} ${${DCMTK_ANDROID_SHELL_OUTPUT_VARIABLE}} PARENT_SCOPE)
                ELSE()
                    MESSAGE("${${DCMTK_ANDROID_SHELL_OUTPUT_VARIABLE}}")
                ENDIF()
            ENDIF()
            IF(NOT DCMTK_ANDROID_SHELL_ERROR_QUIET AND NOT STREAMS_MERGED)
                FILE(READ ${CMAKE_CURRENT_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/${PREFIX}_error ${DCMTK_ANDROID_SHELL_ERROR_VARIABLE})
                IF(PUSH_ERROR_VAR)
                    SET(${DCMTK_ANDROID_SHELL_ERROR_VARIABLE} ${${DCMTK_ANDROID_SHELL_ERROR_VARIABLE}} PARENT_SCOPE)
                ELSE()
                    MESSAGE("${${DCMTK_ANDROID_SHELL_ERROR_VARIABLE}}")
                ENDIF()
            ENDIF()
            # Cleanup temp files
            FILE(REMOVE ${CMAKE_CURRENT_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/${PREFIX}_result)
            FILE(REMOVE ${CMAKE_CURRENT_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/${PREFIX}_output)
            FILE(REMOVE ${CMAKE_CURRENT_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/${PREFIX}_error)
        ENDIF(ADB_RESULT)
    ELSE()
        MESSAGE(AUTHOR_WARNING "Error: the Android emulator \"${VAR}\" is not ready to receive commands")
    ENDIF()
ENDFUNCTION(DCMTK_ANDROID_SHELL)
