# GUI Application automation and testing library
# Copyright (C) 2006-2018 Mark Mc Mahon and Contributors
# https://github.com/pywinauto/pywinauto/graphs/contributors
# http://pywinauto.readthedocs.io/en/latest/credits.html
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice, this
#   list of conditions and the following disclaimer.
#
# * Redistributions in binary form must reproduce the above copyright notice,
#   this list of conditions and the following disclaimer in the documentation
#   and/or other materials provided with the distribution.
#
# * Neither the name of pywinauto nor the names of its
#   contributors may be used to endorse or promote products derived from
#   this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

"""Functions to retrieve properties from a window handle

These are implemented in a procedural way so as to to be
useful to other modules with the least conceptual overhead
"""

import warnings
import win32process
import win32api
import win32con
import win32gui

from ctypes import wintypes
from ctypes import WINFUNCTYPE
from ctypes import c_int
from ctypes import byref
from ctypes import sizeof
from ctypes import create_unicode_buffer
from . import win32functions
from . import win32defines
from . import win32structures
from .actionlogger import ActionLogger


#=========================================================================
def text(handle):
    """Return the text of the window"""
    class_name = classname(handle)
    if class_name == 'IME':
        return 'Default IME'
    if class_name == 'MSCTFIME UI':
        return 'M'
    if class_name is None:
        return ''
    #length = win32functions.SendMessage(handle, win32defines.WM_GETTEXTLENGTH, 0, 0)

    # XXX: there are some very rare cases when WM_GETTEXTLENGTH hangs!
    # WM_GETTEXTLENGTH may hang even for notepad.exe main window!
    c_length = win32structures.DWORD_PTR(0)
    result = win32functions.SendMessageTimeout(
        handle,
        win32defines.WM_GETTEXTLENGTH,
        0,
        0,
        win32defines.SMTO_ABORTIFHUNG,
        500,
        byref(c_length)
    )
    if result == 0:
        ActionLogger().log('WARNING! Cannot retrieve text length for handle = ' + str(handle))
        return ''
    else:
        length = c_length.value

    textval = ''
    # In some rare cases, the length returned by WM_GETTEXTLENGTH is <0.
    # Guard against this by checking it is >0 (==0 is not of interest):
    if length > 0:
        length += 1

        buffer_ = create_unicode_buffer(length)

        ret = win32functions.SendMessage(
            handle, win32defines.WM_GETTEXT, length, byref(buffer_))

        if ret:
            textval = buffer_.value

    return textval


#=========================================================================
def classname(handle):
    """Return the class name of the window"""
    if handle is None:
        return None
    class_name = create_unicode_buffer(u"", 257)
    win32functions.GetClassName(handle, class_name, 256)
    return class_name.value


#=========================================================================
def parent(handle):
    """Return the handle of the parent of the window"""
    return win32functions.GetParent(handle)


#=========================================================================
def style(handle):
    """Return the style of the window"""
    return win32functions.GetWindowLong(handle, win32defines.GWL_STYLE)


#=========================================================================
def exstyle(handle):
    """Return the extended style of the window"""
    return win32functions.GetWindowLong(handle, win32defines.GWL_EXSTYLE)


#=========================================================================
def controlid(handle):
    """Return the ID of the control"""
    return win32functions.GetWindowLong(handle, win32defines.GWL_ID)


#=========================================================================
def userdata(handle):
    """Return the value of any user data associated with the window"""
    return win32functions.GetWindowLong(handle, win32defines.GWL_USERDATA)


#=========================================================================
def contexthelpid(handle):
    """Return the context help id of the window"""
    return win32functions.GetWindowContextHelpId(handle)


#=========================================================================
def iswindow(handle):
    """Return True if the handle is a window"""
    return False if handle is None else bool(win32functions.IsWindow(handle))


#=========================================================================
def isvisible(handle):
    """Return True if the window is visible"""
    return False if handle is None else bool(win32functions.IsWindowVisible(handle))


#=========================================================================
def isunicode(handle):
    """Return True if the window is a Unicode window"""
    return False if handle is None else bool(win32functions.IsWindowUnicode(handle))


#=========================================================================
def isenabled(handle):
    """Return True if the window is enabled"""
    return False if handle is None else bool(win32functions.IsWindowEnabled(handle))


#=========================================================================
def is64bitprocess(process_id):
    """Return True if the specified process is a 64-bit process on x64

    Return False if it is only a 32-bit process running under Wow64.
    Always return False for x86.
    """
    from .sysinfo import is_x64_OS
    is32 = True
    if is_x64_OS():
        phndl = win32api.OpenProcess(win32con.MAXIMUM_ALLOWED, 0, process_id)
        if phndl:
            is32 = win32process.IsWow64Process(phndl)
            #print("is64bitprocess, is32: %d, procid: %d" % (is32, process_id))

    return (not is32)


#=========================================================================
def is64bitbinary(filename):
    """Check if the file is 64-bit binary"""
    import win32file
    try:
        binary_type = win32file.GetBinaryType(filename)
        return binary_type != win32file.SCS_32BIT_BINARY
    except Exception as exc:
        warnings.warn('Cannot get binary type for file "{}". Error: {}'
                      .format(filename, exc), RuntimeWarning, stacklevel=2)
        return None


#=========================================================================
def clientrect(handle):
    """Return the client rectangle of the control"""
    client_rect = win32structures.RECT()
    win32functions.GetClientRect(handle, byref(client_rect))
    return client_rect


#=========================================================================
def rectangle(handle):
    """Return the rectangle of the window"""
    rect = win32structures.RECT()
    win32functions.GetWindowRect(handle, byref(rect))
    return rect


#=========================================================================
def font(handle):
    """Return the font as a LOGFONTW of the window"""
    # get the font handle
    if handle is None:
        handle = 0  # make sure we don't pass window handle down as None
    font_handle = win32functions.SendMessage(
        handle, win32defines.WM_GETFONT, 0, 0)

    # if the fondUsed is 0 then the control is using the
    # system font (well probably not - even though that is what the docs say)
    # instead we switch to the default GUI font - which is more likely correct.
    if not font_handle:

        # So just get the default system font
        font_handle = win32functions.GetStockObject(win32defines.DEFAULT_GUI_FONT)

        # if we still don't have a font!
        # ----- ie, we're on an antiquated OS, like NT 3.51
        if not font_handle:

            # ----- On Asian platforms, ANSI font won't show.
            if win32functions.GetSystemMetrics(win32defines.SM_DBCSENABLED):
                # ----- was...(SYSTEM_FONT)
                font_handle = win32functions.GetStockObject(
                    win32defines.SYSTEM_FONT)
            else:
                # ----- was...(SYSTEM_FONT)
                font_handle = win32functions.GetStockObject(
                    win32defines.ANSI_VAR_FONT)

    # Get the Logfont structure of the font of the control
    fontval = win32structures.LOGFONTW()
    ret = win32functions.GetObject(
        font_handle, sizeof(fontval), byref(fontval))

    # The function could not get the font - this is probably
    # because the control does not have associated Font/Text
    # So we should make sure the elements of the font are zeroed.
    if not ret:
        fontval = win32structures.LOGFONTW()

    # if it is a main window
    if is_toplevel_window(handle):

        if "MS Shell Dlg" in fontval.lfFaceName or \
           fontval.lfFaceName == "System":
            # these are not usually the fonts actaully used in for
            # title bars so we need to get the default title bar font

            # get the title font based on the system metrics rather
            # than the font of the control itself
            ncms = win32structures.NONCLIENTMETRICSW()
            ncms.cbSize = sizeof(ncms)
            win32functions.SystemParametersInfo(
                win32defines.SPI_GETNONCLIENTMETRICS,
                sizeof(ncms),
                byref(ncms),
                0)

            # with either of the following 2 flags set the font of the
            # dialog isthe small one (but there is normally no difference!
            if has_style(handle, win32defines.WS_EX_TOOLWINDOW) or \
               has_style(handle, win32defines.WS_EX_PALETTEWINDOW):

                fontval = ncms.lfSmCaptionFont
            else:
                fontval = ncms.lfCaptionFont

    return fontval


#=========================================================================
def processid(handle):
    """Return the ID of process that controls this window"""
    pid = wintypes.DWORD()
    win32functions.GetWindowThreadProcessId(handle, byref(pid))
    return pid.value


#=========================================================================
def has_enough_privileges(process_id):
    """Check if target process has enough rights to query GUI actions"""
    try:
        access_level = win32con.PROCESS_QUERY_INFORMATION | win32con.PROCESS_VM_READ
        process_handle = win32api.OpenProcess(access_level, 0, process_id)
        if process_handle:
            win32api.CloseHandle(process_handle)
            return True
        return False
    except win32gui.error:
        return False


#=========================================================================
def children(handle):
    """Return a list of handles to the children of this window"""
    # this will be filled in the callback function
    child_windows = []

    # callback function for EnumChildWindows
    def enum_child_proc(hwnd, lparam):
        """Called for each child - adds child hwnd to list"""
        # append it to our list
        child_windows.append(hwnd)

        # return true to keep going
        return True

    # define the child proc type
    enum_child_proc_t = WINFUNCTYPE(
        c_int,                   # return type
        wintypes.HWND,           # the window handle
        wintypes.LPARAM)         # extra information

    # update the proc to the correct type
    proc = enum_child_proc_t(enum_child_proc)

    # loop over all the children (callback called for each)
    win32functions.EnumChildWindows(handle, proc, 0)

    return child_windows


#=========================================================================
def has_style(handle, tocheck):
    """Return True if the control has style tocheck"""
    hwnd_style = style(handle)
    return tocheck & hwnd_style == tocheck


#=========================================================================
def has_exstyle(handle, tocheck):
    """Return True if the control has extended style tocheck"""
    hwnd_exstyle = exstyle(handle)
    return tocheck & hwnd_exstyle == tocheck


#=========================================================================
def is_toplevel_window(handle):
    """Return whether the window is a top level window or not"""
    # only request the style once - this is an optimization over calling
    # (handle, style) for each style I wan to check!
    style_ = style(handle)

    if (style_ & win32defines.WS_OVERLAPPED == win32defines.WS_OVERLAPPED or
        style_ & win32defines.WS_CAPTION == win32defines.WS_CAPTION) and \
       not (style_ & win32defines.WS_CHILD == win32defines.WS_CHILD):
        return True
    else:
        return False


#=========================================================================
def dumpwindow(handle):
    """Dump a window to a set of properties"""
    props = {}

    for func in (text,
                 classname,
                 rectangle,
                 clientrect,
                 style,
                 exstyle,
                 contexthelpid,
                 controlid,
                 userdata,
                 font,
                 parent,
                 processid,
                 isenabled,
                 isunicode,
                 isvisible,
                 children,
                 ):

        props[func.__name__] = func(handle)

    return props
