# -*- coding: utf-8 -*-
from __future__ import unicode_literals

import sys
from winsys._compat import unittest
import uuid

import winerror
import win32api
import win32con
import win32security
import pywintypes

from winsys.tests import utils as testutils
from winsys import registry, utils

GUID = str(uuid.uuid1())
TEST_KEY = r"HKEY_CURRENT_USER\Software\winsys"
TEST_KEY1 = r"HKEY_CURRENT_USER\Software\winsys1"
TEST_KEY2 = r"HKEY_CURRENT_USER\Software\winsys1\winsys2"

#
# Utility functions
#
def remove_key(root, key):
    hkey = win32api.RegOpenKeyEx(root, key, 0, win32con.KEY_ALL_ACCESS)
    for name, reserved, klass, last_written in win32api.RegEnumKeyEx(hkey):
        remove_key(hkey, name)
    win32api.RegDeleteKey(root, key)

def remove_access(path=r"software\winsys"):
    hKey = win32api.RegOpenKeyEx(
        win32con.HKEY_CURRENT_USER, path, 0,
        win32con.READ_CONTROL|win32con.WRITE_DAC
    )
    dacl = win32security.ACL()
    win32security.SetSecurityInfo(
        hKey, win32security.SE_REGISTRY_KEY,
        win32security.DACL_SECURITY_INFORMATION | win32security.PROTECTED_DACL_SECURITY_INFORMATION,
        None, None, dacl, None
    )

def restore_access(path=r"software\winsys"):
    hKey = win32api.RegOpenKeyEx(
        win32con.HKEY_CURRENT_USER, path,
        0,
        win32con.READ_CONTROL|win32con.WRITE_DAC
    )
    win32security.SetSecurityInfo(
        hKey, win32security.SE_REGISTRY_KEY,
        win32security.DACL_SECURITY_INFORMATION | win32security.UNPROTECTED_DACL_SECURITY_INFORMATION,
        None, None, None, None
    )

def keys_are_equal(key0, key1):
    return \
        list((utils.relative_to(key.moniker, key0), list(values)) for key, subkeys, values in registry.walk(key0)) == \
        list((utils.relative_to(key.moniker, key1), list(values)) for key, subkeys, values in registry.walk(key1))

def key0_subset_of_key1(key0, key1):
    s0 = set((utils.relative_to(key.moniker, key0), frozenset(values)) for key, subkeys, values in registry.walk(key0))
    s1 = set((utils.relative_to(key.moniker, key1), frozenset(values)) for key, subkeys, values in registry.walk(key1))
    return s0 < s1

@unittest.skipUnless(testutils.i_am_admin(), "These tests must be run as Administrator")
class TestRegistry(unittest.TestCase):

    #
    # Fixtures
    #
    def setUp(self):
        hwinsys = win32api.RegCreateKey(win32con.HKEY_CURRENT_USER, r"Software\winsys")
        hKey = win32api.RegOpenKeyEx(win32con.HKEY_CURRENT_USER, r"Software\winsys", 0, win32con.KEY_WRITE)
        win32api.RegSetValueEx(hKey, "winsys1", None, win32con.REG_SZ, GUID)
        win32api.RegSetValueEx(hKey, "winsys1", "value", win32con.REG_SZ, GUID)
        win32api.RegSetValueEx(hKey, "winsys2", None, win32con.REG_SZ, GUID)
        hSubkey = win32api.RegCreateKey(hKey, "winsys2")
        win32api.RegSetValueEx(hSubkey, "winsys2", None, win32con.REG_SZ, GUID)
        hKey = win32api.RegOpenKeyEx(win32con.HKEY_CURRENT_USER, r"Software\winsys", 0, win32con.KEY_WRITE)
        hSubkey = win32api.RegCreateKey(hKey, "win:sys3")
        win32api.RegSetValueEx(hSubkey, "winsys3", None, win32con.REG_SZ, GUID)
        self.setup_set_value()

    def tearDown(self):
        hKey = win32api.RegOpenKeyEx(win32con.HKEY_CURRENT_USER, r"Software\winsys", 0, win32con.READ_CONTROL|win32con.WRITE_DAC)
        dacl = win32security.ACL()
        sid, _, _ = win32security.LookupAccountName(None, win32api.GetUserName())
        dacl.AddAccessAllowedAce(win32security.ACL_REVISION_DS, win32con.KEY_ALL_ACCESS, sid)
        win32security.SetSecurityInfo(
            hKey, win32security.SE_REGISTRY_KEY,
            win32security.DACL_SECURITY_INFORMATION | win32security.UNPROTECTED_DACL_SECURITY_INFORMATION,
            None, None, dacl, None
        )
        remove_key(win32con.HKEY_CURRENT_USER, r"Software\winsys")

    #
    # Fixtures
    #
    #~ def setup_key_with_colon():
        #~ hKey = win32api.RegOpenKeyEx(win32con.HKEY_CURRENT_USER, r"Software\winsys", 0, win32con.KEY_WRITE)
        #~ hSubkey = win32api.RegCreateKey(hKey, "win:sys3")
        #~ win32api.RegSetValueEx(hSubkey, "winsys3", None, win32con.REG_SZ, GUID)

    #~ def teardown_key_with_colon():
        #~ hKey = win32api.RegOpenKeyEx(win32con.HKEY_CURRENT_USER, r"Software\winsys", 0, win32con.KEY_WRITE)
        #~ win32api.RegDeleteKey(hKey, "win:sys3")

    #
    # TESTS
    #

    #
    # test disabled until I can figure out a way to make it fail!
    #
    #~ def test_moniker_ill_formed():
        #~ assert_raises(registry.x_moniker_ill_formed, registry._parse_moniker, r"IN\VA:LID\MONI\KER")

    def test_moniker_computer_only(self):
        with self.assertRaises(registry.x_moniker_no_root):
            registry._parse_moniker(r"\\computer")

    def test_moniker_invalid_root(self):
        with self.assertRaises(registry.x_moniker_no_root):
            registry._parse_moniker(r"<nonsense>")

    def test_moniker_slash_and_root(self):
        self.assertEqual(registry._parse_moniker(r"\HKLM"),(None, win32con.HKEY_LOCAL_MACHINE, "", None))

    def test_moniker_root_only(self):
        self.assertEqual(registry._parse_moniker("HKLM"),(None, win32con.HKEY_LOCAL_MACHINE, "", None))

    def test_moniker_computer_and_root(self):
        self.assertEqual(registry._parse_moniker(r"\\COMPUTER\HKLM"),("COMPUTER", win32con.HKEY_LOCAL_MACHINE, "", None))

    def test_moniker_root_and_body(self):
        self.assertEqual(registry._parse_moniker(r"HKLM\Software\Microsoft"),(None, win32con.HKEY_LOCAL_MACHINE, r"Software\Microsoft", None))

    def test_moniker_computer_root_and_body(self):
        self.assertEqual(registry._parse_moniker(r"\\COMPUTER\HKLM\Software\Microsoft"),("COMPUTER", win32con.HKEY_LOCAL_MACHINE, r"Software\Microsoft", None))

    def test_moniker_body_only(self):
        with self.assertRaises(registry.x_moniker_no_root):
            registry._parse_moniker(r"Software\Microsoft")

    def test_moniker_default_value(self):
        self.assertEqual(registry._parse_moniker(r"HKLM\Software\Microsoft:"),(None, win32con.HKEY_LOCAL_MACHINE, r"Software\Microsoft", ""))

    def test_moniker_value(self):
        self.assertEqual(registry._parse_moniker(r"HKLM\Software\Microsoft:value"),(None, win32con.HKEY_LOCAL_MACHINE, r"Software\Microsoft", "value"))

    def test_moniker_create(self):
        parts = "COMPUTER", win32con.HKEY_LOCAL_MACHINE, "PATH", "VALUE"
        self.assertEqual(registry._parse_moniker(registry.create_moniker(*parts)), parts)

    def test_moniker_create_named_root(self):
        parts = "COMPUTER", "HKLM", "PATH", "VALUE"
        result = "COMPUTER", win32con.HKEY_LOCAL_MACHINE, "PATH", "VALUE"
        self.assertEqual(registry._parse_moniker(registry.create_moniker(*parts)), result)

    def test_moniker_create(self):
        parts = "COMPUTER", win32con.HKEY_LOCAL_MACHINE, "PATH", None
        self.assertEqual(registry._parse_moniker(registry.create_moniker(*parts)), parts)

    def test_registry_None(self):
        self.assertIs(registry.registry(None), None)

    def test_registry_Key(self):
        key = registry.registry("HKLM")
        self.assertIs(registry.registry(key), key)

    def test_registry_key_no_value(self):
        self.assertEqual(registry.registry(TEST_KEY + r"\win:sys3", accept_value=False).winsys3, GUID)

    def test_registry_value(self):
        self.assertEqual(registry.registry(TEST_KEY + r":winsys1"), GUID)

    def test_registry_string(self):
        self.assertEqual(registry.registry(TEST_KEY).winsys1, GUID)

    def test_registry_other(self):
        with self.assertRaises(registry.x_registry):
            hKey = win32api.RegOpenKey(win32con.HKEY_LOCAL_MACHINE, "Software")
            registry.registry(hKey)

    def test_values(self):
        values = registry.values(TEST_KEY)
        self.assertEqual(next(values),('winsys1', GUID))
        self.assertEqual(next(values),('winsys2', GUID))

    def test_values_access_denied(self):
        with self.assertRaises(registry.exc.x_access_denied):
            key = registry.registry(TEST_KEY, win32con.KEY_ENUMERATE_SUB_KEYS)
            next(registry.values(key))

    def test_values_ignore_access_denied(self):
        key = registry.registry(TEST_KEY, win32con.KEY_ENUMERATE_SUB_KEYS)
        values = registry.values(key, ignore_access_errors=True)
        self.assertEqual(list(values), [])

    def test_keys(self):
        keys = registry.keys(TEST_KEY)
        self.assertEqual(next(keys), registry.registry(TEST_KEY) + r"win:sys3")

    def test_keys_access_denied(self):
        with self.assertRaises(registry.exc.x_access_denied):
            key = registry.registry(TEST_KEY, win32con.KEY_NOTIFY)
            keys = registry.keys(key, ignore_access_errors=False)
            next(keys)

    def test_keys_ignore_access_denied(self):
        key = registry.registry(TEST_KEY, win32con.KEY_NOTIFY)
        keys = registry.keys(key, ignore_access_errors=True)
        self.assertEqual(list(keys), [])

    def test_copy_does_not_exist(self):
        key0 = TEST_KEY
        key1 = TEST_KEY1
        registry.copy(key0, key1)
        try:
            self.assertTrue(keys_are_equal(key0, key1))
        finally:
            registry.delete(key1)

    def test_copy_exists_empty(self):
        key0 = registry.registry(TEST_KEY)
        key1 = registry.registry(TEST_KEY1)
        self.assertFalse(key1)
        key1.create()
        self.assertTrue(key1)
        registry.copy(key0, key1)
        try:
            self.assertTrue(keys_are_equal(key0, key1))
        finally:
            key1.delete()

    def test_copy_exists_not_empty_keys(self):
        key0 = registry.registry(TEST_KEY)
        key1 = registry.registry(TEST_KEY1)
        self.assertFalse(key1)
        key1.create()
        self.assertTrue(key1)
        try:
            key1.create("winsys4")
            registry.copy(key0, key1)
            self.assertTrue(key0_subset_of_key1(key0, key1))
        finally:
            key1.delete()

    def test_copy_exists_not_empty_values(self):
        key0 = registry.registry(TEST_KEY)
        key1 = registry.registry(TEST_KEY1, access="F")
        self.assertFalse(key1)
        key1.create()
        self.assertTrue(key1)
        try:
            key1.winsys4 = GUID
            registry.copy(key0, key1)
            self.assertEqual(set(set(key1.flat()) - set(key0.flat())),
                set([("winsys4", GUID), key1, key1 + "win:sys3", key1 + "winsys2"])
            )
        finally:
            key1.delete()

    def test_create_does_not_exist(self):
        key1 = registry.registry(TEST_KEY1)
        self.assertFalse(key1)
        registry.create(key1)
        try:
            self.assertTrue(key1)
        finally:
            key1.delete()

    def test_create_does_not_exist_deep(self):
        key1 = registry.registry(TEST_KEY1)
        key2 = registry.registry(TEST_KEY2)
        self.assertFalse(key1)
        self.assertFalse(key2)
        registry.create(key2)
        try:
            self.assertTrue(key1)
            self.assertTrue(key2)
        finally:
            key1.delete()

    def test_create_does_exist(self):
        key = registry.registry(TEST_KEY)
        self.assertTrue(key)
        registry.create(key)
        self.assertTrue(key)

    def test_walk(self):
        walker = registry.walk(TEST_KEY)
        key, subkeys, values = next(walker)
        self.assertEqual(key, registry.registry(TEST_KEY))
        self.assertEqual(list(values), [("winsys1", GUID),("winsys2", GUID)])
        key, subkeys, values = next(walker)
        self.assertEqual(key, registry.registry(TEST_KEY) + "win:sys3")
        key, subkeys, values = next(walker)
        self.assertEqual(key, registry.registry(TEST_KEY) + "winsys2")
        self.assertEqual(list(values), [("winsys2", GUID)])

    def test_walk_access_denied(self):
        with self.assertRaises(registry.exc.x_access_denied):
            key = registry.registry(TEST_KEY, access=registry.REGISTRY_ACCESS.KEY_NOTIFY)
            walker = registry.walk(key)
            key, keys, values = next(walker)
            list(keys)

    def test_walk_ignore_access_denied(self):
        key = registry.registry(TEST_KEY, access=registry.REGISTRY_ACCESS.KEY_NOTIFY)
        walker = registry.walk(key, ignore_access_errors=True)
        key, keys, values = next(walker)
        list(keys) == [key + "winsys2"]

    def test_flat(self):
        key = registry.registry(TEST_KEY)
        self.assertEqual(
            list(registry.flat(key)),
            [
                key,
             ("winsys1", GUID),
             ("winsys2", GUID),
                key + "win:sys3",
             ("winsys3", GUID),
                key + "winsys2",
             ("winsys2", GUID)
            ]
        )

    def test_flat_access_denied(self):
        with self.assertRaises(registry.exc.x_access_denied):
            key = registry.registry(TEST_KEY, access=registry.REGISTRY_ACCESS.KEY_NOTIFY)
            list(registry.flat(key))

    def test_flat_ignore_access_denied(self):
        remove_access(r"software\winsys\winsys2")
        try:
            key = registry.registry(TEST_KEY)
            self.assertEqual(
                list(registry.flat(key, ignore_access_errors=True)),
                [
                key,
             ("winsys1", GUID),
             ("winsys2", GUID),
                key + "win:sys3",
             ("winsys3", GUID),
            ])
        finally:
            restore_access(r"software\winsys\winsys2")

    def test_parent(self):
        self.assertEqual(registry.parent(TEST_KEY + r"\winsys2"), registry.registry(TEST_KEY))

    def test_identical_functions(self):
        functions = "values keys delete create walk flat copy parent".split()
        for function in functions:
            self.assertIs(getattr(registry, function).__code__, getattr(registry.Registry, function).__code__)

    def test_Registry_init(self):
        key = registry.Registry(TEST_KEY, access=win32con.KEY_ALL_ACCESS)
        self.assertEqual(key.moniker, TEST_KEY)
        self.assertEqual(key.name, "winsys")
        self.assertEqual(key.access, win32con.KEY_ALL_ACCESS)
        self.assertEqual(key.id, registry._parse_moniker(TEST_KEY.lower()))

    def test_Registry_init_access(self):
        for k, v in registry.Registry.ACCESS.items():
            self.assertEqual(registry.registry(TEST_KEY, k).access, v)

    def test_Registry_access(self):
        access = registry.Registry._access
        self.assertIs(access(None), None)
        self.assertEqual(access(1), 1)
        for k, v in registry.Registry.ACCESS.items():
            self.assertEqual(registry.registry(TEST_KEY, k).access, v)

    def test_Registry_eq(self):
        self.assertEqual(registry.registry(TEST_KEY.upper(), access="R"), registry.registry(TEST_KEY.lower(), access="R"))

    def test_Registry_neq(self):
        self.assertNotEqual(
            registry.registry(TEST_KEY.upper(), access="R"),
            registry.registry(TEST_KEY.lower(), access="W")
        )

    def test_Registry_add(self):
        self.assertEqual(registry.registry(TEST_KEY) + "test", registry.registry(TEST_KEY + registry.sep + "test"))

    def test_Registry_pyobject(self):
        self.assertIsInstance(registry.registry(TEST_KEY).pyobject(), pywintypes.HANDLEType)

    def test_Registry_pyobject_not_exists(self):
        with self.assertRaises(registry.exc.x_not_found):
            self.assertFalse(registry.registry(TEST_KEY + "xxx"))
            registry.registry(TEST_KEY + "xxx").pyobject()

    def test_Registry_as_string(self):
        key = registry.registry(TEST_KEY)
        self.assertEqual(key.as_string(), key.moniker)

    def test_Registry_security(self):
        security_information = win32security.OWNER_SECURITY_INFORMATION | win32security.DACL_SECURITY_INFORMATION
        key = registry.registry(TEST_KEY)
        security = key.security(security_information)
        sd = win32security.GetSecurityInfo(
            win32api.RegOpenKey(win32con.HKEY_CURRENT_USER, r"software\winsys"),
            win32security.SE_REGISTRY_KEY,
            security_information
        )
        self.assertEqual(
            security.as_string(),
            win32security.ConvertSecurityDescriptorToStringSecurityDescriptor(
                sd,
                win32security.SDDL_REVISION_1,
                security_information
            )
        )

    def test_Registry_nonzero_exists(self):
        win32api.RegCreateKey(win32con.HKEY_CURRENT_USER, r"Software\winsys1")
        try:
            self.assertTrue(registry.registry(TEST_KEY1))
        finally:
            remove_key(win32con.HKEY_CURRENT_USER, r"Software\winsys1")

    def test_Registry_nonzero_not_exists(self):
        try:
            win32api.RegOpenKey(win32con.HKEY_CURRENT_USER, r"Software\winsys1")
        except win32api.error as error:
            errno, errctx, errmsg = error.args
            if errno != winerror.ERROR_FILE_NOT_FOUND:
                raise
        else:
            raise RuntimeError("Key exists but should not")

        self.assertFalse(registry.registry(TEST_KEY1))

    def test_Registry_dumped(self):
        #
        # Just test it doesn't fall over
        #
        dump = registry.registry("HKLM").dumped()

    def test_Registry_get_value(self):
        self.assertEqual(
            registry.registry(TEST_KEY).get_value("winsys1"),
            win32api.RegQueryValueEx(win32api.RegOpenKey(win32con.HKEY_CURRENT_USER, r"software\winsys"), "winsys1")[0]
        )

    def test_Registry_get_key(self):
        self.assertEqual(
            registry.registry(TEST_KEY).get_key("winsys1"),
            registry.registry(TEST_KEY + r"\winsys1")
        )

    def test_Registry_getattr_value(self):
        value, type = win32api.RegQueryValueEx(win32api.RegOpenKey(win32con.HKEY_CURRENT_USER, r"software\winsys"), "winsys1")
        self.assertEqual(registry.registry(TEST_KEY).winsys1, value)

    def test_Registry_getattr_value_shadows_key(self):
        value, type = win32api.RegQueryValueEx(win32api.RegOpenKey(win32con.HKEY_CURRENT_USER, r"software\winsys"), "winsys2")
        self.assertEqual(registry.registry(TEST_KEY).winsys2, value)

    def test_Registry_getattr_key(self):
        win32api.RegCreateKey(win32con.HKEY_CURRENT_USER, r"software\winsys\winsys3")
        try:
            self.assertEqual(registry.registry(TEST_KEY).winsys3, registry.registry(TEST_KEY).get_key("winsys3"))
        finally:
            win32api.RegDeleteKey(win32con.HKEY_CURRENT_USER, r"Software\winsys\winsys3")

    def setup_set_value(self):
        try:
            win32api.RegDeleteValue(
                win32api.RegOpenKeyEx(
                    win32con.HKEY_CURRENT_USER,
                    r"Software\winsys",
                    0,
                    win32con.KEY_ALL_ACCESS
                ),
                "winsys4"
            )
        except win32api.error as error:
            errno, errctx, errmsg = error.args
            if errno == 2:
                pass
            else:
                raise

    #~ @with_setup(setup_set_value)
    def test_Registry_set_value_type(self):
        registry.registry(TEST_KEY, access="F").set_value("winsys4", b"abc", win32con.REG_BINARY)
        self.assertEqual(
            win32api.RegQueryValueEx(win32api.RegOpenKey(win32con.HKEY_CURRENT_USER, r"software\winsys"), "winsys4"),
         (b"abc", win32con.REG_BINARY)
        )

    #~ @with_setup(setup_set_value)
    def test_Registry_set_value_int(self):
        registry.registry(TEST_KEY, access="F").set_value("winsys4", 1)
        self.assertEqual(
            win32api.RegQueryValueEx(win32api.RegOpenKey(win32con.HKEY_CURRENT_USER, r"software\winsys"), "winsys4"),
         (1, win32con.REG_DWORD)
        )

    #~ @with_setup(setup_set_value)
    def test_Registry_set_value_multi(self):
        registry.registry(TEST_KEY, access="F").set_value("winsys4", ['a', 'b', 'c'])
        self.assertEqual(
            win32api.RegQueryValueEx(win32api.RegOpenKey(win32con.HKEY_CURRENT_USER, r"software\winsys"), "winsys4"),
         (['a', 'b', 'c'], win32con.REG_MULTI_SZ)
        )

    #~ @with_setup(setup_set_value)
    def test_Registry_set_value_expand_even_percent(self):
        registry.registry(TEST_KEY, access="F").set_value("winsys4", "%TEMP%")
        self.assertEqual(
            win32api.RegQueryValueEx(win32api.RegOpenKey(win32con.HKEY_CURRENT_USER, r"software\winsys"), "winsys4"),
         ("%TEMP%", win32con.REG_EXPAND_SZ)
        )

    #~ @with_setup(setup_set_value)
    def test_Registry_set_value_expand_odd_percent(self):
        registry.registry(TEST_KEY, access="F").set_value("winsys4", "50%")
        self.assertEqual(
            win32api.RegQueryValueEx(win32api.RegOpenKey(win32con.HKEY_CURRENT_USER, r"software\winsys"), "winsys4"),
         ("50%", win32con.REG_SZ)
        )

    #~ @with_setup(setup_set_value)
    def test_Registry_set_value_empty_string(self):
        registry.registry(TEST_KEY, "F").set_value("winsys4", "")
        self.assertEqual(
            win32api.RegQueryValueEx(win32api.RegOpenKey(win32con.HKEY_CURRENT_USER, r"software\winsys"), "winsys4"),
         ("", win32con.REG_SZ)
        )

    #~ @with_setup(setup_set_value)
    def test_Registry_set_value_non_empty_string(self):
        registry.registry(TEST_KEY, access="F").set_value("winsys4", "winsys")
        self.assertEqual(
            win32api.RegQueryValueEx(win32api.RegOpenKey(win32con.HKEY_CURRENT_USER, r"software\winsys"), "winsys4"),
         ("winsys", win32con.REG_SZ)
        )

    #~ @with_setup(setup_set_value)
    def test_Registry_set_value_none(self):
        registry.registry(TEST_KEY, access="F").set_value("winsys4", None)
        self.assertEqual(
            win32api.RegQueryValueEx(win32api.RegOpenKey(win32con.HKEY_CURRENT_USER, r"software\winsys"), "winsys4"),
         ("", win32con.REG_SZ)
        )

    #~ @with_setup(setup_set_value)
    def test_Registry_set_value_default(self):
        registry.registry(TEST_KEY, access="F").set_value("", "test")
        self.assertEqual(
                win32api.RegQueryValueEx(win32api.RegOpenKey(win32con.HKEY_CURRENT_USER, r"software\winsys"), None),
             ("test", win32con.REG_SZ)
        )

    def test_Registry_add(self):
        key0 = registry.registry(TEST_KEY, access="F")
        new_key = key0.create("winsys1")
        self.assertEqual(new_key, key0 + "winsys1")

    def test_Registry_from_string(self):
        key = registry.Registry.from_string(TEST_KEY)
        self.assertEqual(key.moniker, TEST_KEY)
        self.assertEqual(key.access, registry.Registry._access(registry.Registry.DEFAULT_ACCESS))
        self.assertEqual(key.id, registry._parse_moniker(TEST_KEY.lower()))

    def test_Registry_from_string_value(self):
        self.assertEqual(
            registry.Registry.from_string(TEST_KEY + ":winsys1"),
            registry.Registry.from_string(TEST_KEY).get_value("winsys1")
        )

if __name__ == "__main__":
    unittest.main()
    if sys.stdout.isatty(): raw_input("Press enter...")
