# -*- coding: utf-8 -*-
# Moovida - Home multimedia server
# Copyright (C) 2006-2009 Fluendo Embedded S.L. (www.fluendo.com).
# All rights reserved.
#
# This file is available under one of two license agreements.
#
# This file is licensed under the GPL version 3.
# See "LICENSE.GPL" in the root of this distribution including a special
# exception to use Moovida with Fluendo's plugins.
#
# The GPL part of Moovida is also available under a commercial licensing
# agreement from Fluendo.
# See "LICENSE.Moovida" in the root directory of this distribution package
# for details on that license.

"""
win32 specific helpers.
"""

import win32con, win32api, win32pdh, win32process, win32event, winerror, \
           pywintypes, pythoncom
from win32com.shell.shell import ShellExecuteEx
from win32com.shell import shell, shellcon
import win32com.client

import os.path, subprocess, tempfile, time, sys, ctypes

from elisa.core.utils import locale_helper
from elisa.core.default_config import CONFIG_DIR


def _get_reg_value(hkey, value_name):
    val = None
    try:
        val, type = win32api.RegQueryValueEx(hkey, value_name)
        if type != win32con.REG_SZ:
            raise(TypeError, "Only strings can be asked for %s" % value_name)
    except:
        raise(TypeError, "can't retrieve the registry value : %s" % value_name)

    return val

class RegKey(object):
    PATH = None
    """ Very simple class to get and set values in a given key
    """
    def __init__(self, domain, key_name=None,
                 permission=win32con.KEY_READ, **kwargs):
        super(RegKey, self).__init__(self, **kwargs)
        
        key_path = []
        if self.PATH is not None:
            key_path.append(self.PATH)
        if key_name is not None:
            key_path.append(key_name)
        self.full_key_name = str.join('\\', key_path)

        self._key, disp = win32api.RegCreateKeyEx(domain, self.full_key_name,
                                            samDesired=permission)

    def get_value(self, value_name=None):
        val, type = win32api.RegQueryValueEx(self._key, value_name)
        return val

    def set_value(self,  value_name, value):
        if type(value) == str:
           value_type = win32con.REG_SZ
        elif type(value) == int:
            value_type = win32con.REG_DWORD
        elif value is None:
            value_type = win32con.REG_NONE
        else:
            raise TypeError, "Don't know how to handle type %s for key %s" % \
                            (str(type(value)), self.full_key_name)

        win32api.RegSetValueEx(self._key, value_name, 0, value_type, value)

    def delete(self, value_name=None):
        win32api.RegDeleteValue(self._key, value_name)

    def delete_subkey(self, subkey_name):
        win32api.RegDeleteKey(self._key, subkey_name)

    def close(self):
        win32api.RegCloseKey(self._key)

class ElisaRegKey(RegKey):
    PATH = "Software\\Moovida"

def get_multimedia_dir():
    """
    return in a dict the multimedia directories in UTF8
    """
    multimedia_dir = {}
    system_encoding = locale_helper.system_encoding()

    key_name = "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders"
    hkey = win32api.RegOpenKey(win32con.HKEY_CURRENT_USER, key_name)

    key = "My Music"
    try:
        val = _get_reg_value(hkey, key)
        multimedia_dir[key] = val.decode(system_encoding)
    except:
        pass

    key = "My Pictures"
    try:
        val = _get_reg_value(hkey, key)
        multimedia_dir[key] = val.decode(system_encoding)
    except:
        pass

    key = "My Video"
    try:
        val = _get_reg_value(hkey, key)
        multimedia_dir[key] = val.decode(system_encoding)
    except:
        pass

    win32api.RegCloseKey(hkey)

    return multimedia_dir


def should_install_recommended_plugins():
    """
    Read the key in the registry set by the installer to define whether to
    install the recommended plugins upon first startup, and return its value.

    Once read, the key in the registry is deleted because the recommended
    plugins should be installed only upon first run.

    @return: whether to install the recommended plugins
    @rtype:  C{bool}
    """
    key_path = 'Software\\Moovida'
    key_name = 'InstallRecommendedPlugins'

    try:
        hkey = win32api.RegOpenKeyEx(win32con.HKEY_CURRENT_USER, key_path, 0,
                             win32con.KEY_QUERY_VALUE | win32con.KEY_SET_VALUE)
    except pywintypes.error:
        return False

    try:
        value, vtype = win32api.RegQueryValueEx(hkey, key_name)
    except pywintypes.error:
        win32api.RegCloseKey(hkey)
        return False

    try:
        win32api.RegDeleteValue(hkey, key_name)
    except pywintypes.error:
        win32api.RegCloseKey(hkey)

    return bool(value)


def get_process_id(name):
    _wmi = win32com.client.GetObject(r"winmgmts:\\.\root\cimv2")
    query = "SELECT ProcessId FROM Win32_Process WHERE Name = '%s'" % name
    r = _wmi.ExecQuery(query)
    pid = []
    if len(r) > 0:
        for p in r:
            pid.append(p.ProcessId)
    return pid


def get_process_id_xp(name):
    try:
        object = "Process"
        items, instances = win32pdh.EnumObjectItems(None,None,object, win32pdh.PERF_DETAIL_WIZARD)
        val = None

        if name in instances :
            hq = win32pdh.OpenQuery()
            hcs = []
            item = "ID Process"
            path = win32pdh.MakeCounterPath( (None,object,name, None, 0, item) )
            hcs.append(win32pdh.AddCounter(hq, path))
            win32pdh.CollectQueryData(hq)

            time.sleep(0.01)

            win32pdh.CollectQueryData(hq)

            for hc in hcs:
                type, val = win32pdh.GetFormattedCounterValue(hc, win32pdh.PDH_FMT_LONG)
                win32pdh.RemoveCounter(hc)
            win32pdh.CloseQuery(hq)

            return [val]
    except:
        pass
    return []


def kill_process_pid(pid):
    handle = win32api.OpenProcess(win32con.PROCESS_TERMINATE, 0, pid) #get process handle
    win32api.TerminateProcess(handle,0) #kill by handle
    win32api.CloseHandle(handle) #close api


def kill_process_pids(pid_list):
    for pid in pid_list:
        try:
            kill_process_pid(pid)
        except:
            pass


def kill_process(name):
    pid_list = get_process_id(name)
    pid_list.extend(get_process_id_xp(name))
    kill_process_pids(pid_list)


def exit(delay=1, relaunch=False):
    """
    Exit Elisa using an external executable that makes sure no zombie processes
    are left over. If needed and after a given delay, remaining processes are
    killed.

    @param delay:    the delay in seconds before checking whether Elisa
                     correctly exited, and eventually kill all remaining
                     processes (default: C{1} second)
    @type delay:     C{int}
    @param relaunch: whether to relaunch elisa after it successfully exited
                     (default: C{False})
    @type relaunch:  C{bool}

    @raise AssertionError: when trying to exit from an uninstalled version of
                           Elisa
    @raise ValueError: if the delay passed is not strictly greater than zero

    @return: the subprocess object running the external executable
    @rtype:  C{subprocess.Popen}
    """
    import sys
    if not hasattr(sys, 'frozen'):
        raise AssertionError('Not running an installed version of Moovida.')

    if delay <= 0:
        raise ValueError('Delay must be an integer > 0.')

    exe_name = 'moovida_reboot.exe'
    exe_path = os.path.join(os.path.dirname(sys.executable), exe_name)
    args = [exe_path, str(int(delay)), str(int(relaunch))]
    return subprocess.Popen(args)


class ShortcutResolver(object):

    """
    Helper class that resolves windows shortcuts.
    """

    def __init__(self):
        # Initialize the stuff needed for using COM Objects dealing with
        # shortcuts.
        pythoncom.CoInitialize()
        self.shell_link = \
            pythoncom.CoCreateInstance(shell.CLSID_ShellLink, None,
                                       pythoncom.CLSCTX_INPROC_SERVER,
                                       shell.IID_IShellLink)
        self.query_interface = \
            self.shell_link.QueryInterface(pythoncom.IID_IPersistFile)

    def resolve(self, shortcut):
        """
        Resolve a windows shortcut.
        Do not raise an error if the target of the shortcut does not exist.

        @param shortcut: the full filename of the shortcut (including its
                         C{.lnk} extension)
        @type shortcut:  C{str}

        @return: the full path to the shortcut's target
        @rtype:  C{unicode}

        @raise C{pywintypes.com_error}: if the given filename is not a shortcut
        """
        self.query_interface.Load(shortcut)
        # Remark: SLR_NO_UI is used to not display a message box when link
        # cannot be resolved. The second parameter type is DWORD, high order
        # word set to timeout value (ms) for resolving the link.
        self.shell_link.Resolve(0, (500 << 16) | shell.SLR_NO_UI)
        linked_to_file = self.shell_link.GetPath(shell.SLGP_UNCPRIORITY)[0]
        return linked_to_file.decode(locale_helper.system_encoding())


def run_with_privilege(program, params):
    # FIXME: insecure: an attacker could raise his privilege level by tampering
    # with that file
    # FIXME: an option that allows not to go through the .bat file (ie not
    # copy pieces of environment) would be great.
    filename = tempfile.mktemp(suffix='.bat', prefix='elisa_update', 
                               dir=CONFIG_DIR)
    file = open(filename, 'w')
    print >> file, "@echo off"
    cwd = os.getcwd()
    print >> file, "set PATH=%s" % os.environ.get('PATH', '')
    print >> file, "set PYTHONPATH=%s" % str.join(os.path.pathsep, sys.path)
    print >> file, os.path.splitdrive(cwd)[0]
    print >> file, "cd \"%s\"" % cwd
    print >> file, program, str.join(' ', ["\"%s\"" % arg for arg in params])
    real_program = os.environ.get('COMSPEC', 'cmd.exe')
    real_params = '/C "\"%s\""' %  file.name

    file.close()
    rc = ShellExecuteEx(fMask=shellcon.SEE_MASK_NOCLOSEPROCESS,
                   lpVerb='runas',
                   lpFile=real_program,
                   lpParameters=real_params)
    hproc = rc['hProcess']
    win32event.WaitForSingleObject(hproc, win32event.INFINITE)
    ret = win32process.GetExitCodeProcess(hproc)

    os.remove(filename)

    return ret

def register_com_dll(filename, register=True):
    """
    Register a COM dll.
    @param filename:    the full path to the dll to register
    @type  filename:    C{str}
    @param register:    whether we want to register (C{True}) or unregister
                        (C{False}) the dll
    @type  register:    C{bool}

    @raise pywintypes.error:    if the Dll[Un]RegisterServer() function of the
                                dll returned an error
    @raise WindowsError:        if the library cannot be loaded
    """
    #note: this function might block

    # save the dll directory if it is set
    try:
        old_dll_dir = win32api.GetDllDirectory()
    # we do a catchall because windows is just too unpredictable.
    except:
        old_dll_dir = None

    dll_dir = os.path.dirname(filename)
    win32api.SetDllDirectory(dll_dir)

    pythoncom.OleInitialize()
    lib = ctypes.windll.LoadLibrary(filename)
    if register:
        ret = lib.DllRegisterServer()
    else:
        ret = lib.DllUnregisterServer()

    win32api.SetDllDirectory(old_dll_dir)

    # FIXME: I think we should OleUninitialize but pythoncom doesn't provide
    # a binding for it :/

    if winerror.FAILED(ret):
        if register:
            funcname = "DllRegisterServer"
        else:
            funcname = "DllUnRegisterServer"
        raise pywintypes.error(winerror.HRESULT_CODE(ret), funcname, win32api.FormatMessage(ret)[:-2])

def unregister_com_dll(filename):
    register_com_dll(filename, register=False)
