#!/usr/bin/env python
# -*- coding: utf-8 -*-

"""
Place holder for all windows specific frequently used classes.
Currently here:
  RegistryHandler - Registry based operations
  DecideArchitecture - Accurate way to decide operating system architecture
"""

import os
import argparse
import errno
from winreg import *  # noqa
from collections import OrderedDict  # noqa


def parse_arguments():
    """
    Argument parser, based on argparse module

    Keyword arguments:
    @return args -- arguments given by console
    """
    parser = argparse.ArgumentParser()
    parser.add_argument(
        "-g", "--global",
        help="Whether we want to edit registry globally or just locally",
        action="store_true")
    parser.add_argument(
        "-d", "--different",
        help="Only handle the architecture specific registry area",
        action="store_true")
    parser.add_argument(
        "-r", "--registry",
        help="Which HKEY_* const registry type the program should use",
        type=str, choices=['HKLM', 'HKCR', 'HKCU', 'HKU', 'HKPD', 'HKCC'],
        default="HKLM")
    args = parser.parse_args()
    return args


def main():
    return RegistryHandler(parse_arguments())


class Struct:
    """
    Parameter bypassing struct
    """
    pass


class ClientRegistry:
    @staticmethod
    def directory():
        custom_param = Struct()
        custom_param.registry = "HKCU"
        handler = RegistryHandler(custom_param)
        directory = None
        try:
            directory = handler.get_key_value(
                "Software\\CIRCLE Cloud Client", "running_directory")
        except LookupError:
            directory = os.path.dirname(os.path.abspath(__file__))
        return directory


class RegistryHandler:
    """
    Registry handling class, makes registry based queries and
    manipulations easier.
    This class can handle WOW64 based application differently and
    similarly (default)
    """

    def __init__(self, args=None):
        """Initialise RegistryHandler

        Keyword arguments:
        @param args  -- The arguments that decide how the we should
                        handle the registry
        args.registry    -- What base registry should we use
                            (default: HKLM)
        Raises AttributeError if not supported base registry type is
        given
        args.different -- Whether we handle registry redirection or not
        """
        if args is None:
            self.args = Struct()
            self.args.different = None
            self.args.registry = None
        else:
            if not hasattr(args, 'different'):
                args.different = None
            if not hasattr(args, 'registry'):
                args.registry = None
            self.args = args
        if self.args.different is None:
            self.args.different = False
        if self.args.registry is None or self.args.registry == "HKLM":
            self.args.registry = HKEY_LOCAL_MACHINE
        elif self.args.registry == "HKCR":
            self.args.registry = HKEY_CLASSES_ROOT
        elif self.args.registry == "HKCU":
            self.args.registry = HKEY_CURRENT_USER
        elif self.args.registry == "HKU":
            self.args.registry = HKEY_USERS
        elif self.args.registry == "HKPD":
            self.args.registry = HKEY_PERFORMANCE_DATA
        elif self.args.registry == "HKCC":
            self.args.registry = HKEY_CURRENT_CONFIG
        else:
            # print "Non supported registry type"
            raise AttributeError

    def connect_registry(self):
        """
        Getting a registry open

        Keyword arguments:
        @return connected_registy    -- Reference to the newly opened
                                        registry
        """
        return ConnectRegistry(None, self.args.registry)

    def create_registry_from_dict_chain(
            self, dict_chain, both=False, architect=KEY_WOW64_64KEY,
            needed_rights=KEY_ALL_ACCESS):
        """"
        Create registry key and value multilevel tree by chained
        dictionaries.
        Can raise AttributeError if the provided dict chain isn't
        correct

        Keyword arguments:
        @param key_value_chain -- The dict chain containing all the
                                  information
        @param both            -- Whether create the registry in WOW64
                                  node too
        @param architect       -- The current registry view (
                                  only change it if we want to fill the
                                  WOW6432 registry only [both = False],
                                  on x64 windows)
        @param needed_rights   -- SAM rights to access the key
        """
        if both and architect is KEY_WOW64_64KEY:
            self.create_registry_from_dict_chain(
                dict_chain, False, KEY_WOW64_32KEY, needed_rights)
        if not DecideArchitecture.Is64Windows():
            architect = 0
        connected_registy = self.connect_registry()
        if (isinstance(dict_chain, dict)
                or isinstance(dict_chain, OrderedDict)):
            for key, value in dict_chain.items():
                if isinstance(value, dict) or isinstance(value, OrderedDict):
                    temp_dict = OrderedDict()
                    for my_key, my_value in value.items():
                        temp_dict[key + "\\" + my_key] = my_value
                    self.create_registry_from_dict_chain(
                        temp_dict, False, architect, needed_rights)
                else:
                    if isinstance(value, list):
                        if len(value) % 2 != 0:
                            # print "Not enough member in the list"
                            raise AttributeError
                        else:
                            new_key = CreateKeyEx(
                                connected_registy, key, 0,
                                needed_rights | architect)
                            temp_dict = OrderedDict(
                                value[i:i + 2] for i in range(
                                    0, len(value), 2))
                            for my_key, my_value in temp_dict.items():
                                if my_key == "default":
                                    my_key = None
                                SetValueEx(
                                    new_key, my_key, 0, REG_SZ, my_value)
                    else:
                        new_key = CreateKeyEx(
                            connected_registy, key, 0,
                            needed_rights | architect)
                        SetValueEx(new_key, None, 0, REG_SZ, value)
        else:
            print("The provided attribute wasn't a dictionary chain")
            raise AttributeError

    def get_key(self, key_name, needed_rights=KEY_ALL_ACCESS):
        """
        Getting a registry value by it's key's name
        Can raise KeyError if key is not found in the registry

        Keyword arguments:
        @param key_name      -- The specific key name of which value we
                                are interested in
        @param needed_rights -- SAM rights to access the key
        @return              -- [key, architect]
                                key          -- Reference to the opened
                                                key handler
                                architect    -- 0 for x86
                                                KEY_WOW64_32KEY or
                                                KEY_WOW64_64KEY
                                                depending where we
                                                found the key on x64
        """
        connected_registy = self.connect_registry()
        architect = 0
        if DecideArchitecture.Is64Windows():
            architect = KEY_WOW64_64KEY
        try:
            key = OpenKey(connected_registy, key_name, 0,
                          needed_rights | architect)
        except WindowsError:
            if DecideArchitecture.Is64Windows() and not self.args.different:
                try:
                    architect = KEY_WOW64_32KEY
                    key = OpenKey(connected_registy, key_name,
                                  0, needed_rights | architect)
                except WindowsError:
                    raise KeyError
            else:
                raise KeyError
        return [key, architect]

    def get_key_values(
            self, key_name, subkey_list, subroutine=False,
            depth="subkeys"):
        """
        Getting registry subkeys value by it's key's name and subkeys
        name
        Can raise LookupError exception if there are missing data
        Can raise AttributeError exception if depth attribute is wrong

        Keyword arguments:
        @param key_name      -- The specific key name of which subkey's
                                we are interested in
        @param subkey_list   -- List containing all the subkeys names
                                which values we are interested in
        @param subroutine    -- Whether suppress exception about not
                                having enough results or not
                                (default: False)
        @param depth         -- How depth the search should go for
                                [options: key, subkeys, all]
                                (default: subkeys)
        @return results{}    -- Dictionary with the subkey_name - value
                                combinations as keys and values
        """
        if depth == "key":
            int_depth = 0
        elif depth == "subkeys":
            int_depth = 1
        elif depth == "all":
            int_depth = 2
        else:
            raise AttributeError
        try:
            key_and_architect = self.get_key(key_name)
            key = key_and_architect[0]
            architect = key_and_architect[1]
        except KeyError:
            # print "%s doesn't exist in the registry" % key_name
            raise LookupError
        # print "%s found in the registry" % key_name
        results = {}
        if int_depth >= 1:
            for i in xrange(0, QueryInfoKey(key)[0] - 1):
                skey_name = EnumKey(key, i)
                skey = OpenKey(key, skey_name, 0, KEY_ALL_ACCESS | architect)
                if int_depth == 2 and QueryInfoKey(skey)[0] > 0:
                    for key, value in self.get_key_values(
                            skey_name, subkey_list, True, depth).items():
                        results[key] = value
                for subkey_name in subkey_list:
                    try:
                        results[subkey_name] = QueryValueEx(
                            skey, subkey_name)[0]
                    except OSError as e:
                        if e.errno == errno.ENOENT:
                            # print ("%s doesn't exist in this" % subkey_name
                            #        " subkey")
                            pass
                skey.Close()
        if not results or len(results) != len(subkey_list):
            for subkey_name in subkey_list:
                try:
                    results[subkey_name] = QueryValueEx(key, subkey_name)[0]
                except OSError as e:
                    pass
        key.Close()
        if len(results) != len(subkey_list):
            # print "We are missing important variables"
            raise LookupError
        return results

    def get_key_value(
            self, key_name, subkey_name, subroutine=None, depth=None):
        """
        This is a wrapper for the get_key_values to be easier to use
        for single subkeys.
        Getting registry subkey value by it's key's name and subkey
        name

        Keyword arguments:
        @param key_name      -- The specific key name of which subkey's
                                we are interested in
        @param subkey_name   -- The specific subkey name which value we
                                are interested in
        @param subroutine    -- can be found at: get_key_values
        @param depth         -- can be found at: get_key_values
        @return value        -- Value of the specific subkey
        """
        try:
            if subroutine is None:
                return self.get_key_values(
                    key_name, [subkey_name])[subkey_name]
            elif depth is None:
                return self.get_key_values(
                    key_name, [subkey_name], subroutine)[subkey_name]
            else:
                return self.get_key_values(
                    key_name, [subkey_name], subroutine, depth)[subkey_name]
        except:
            raise


class DecideArchitecture:
    """
    Helper class to get the true ProgramFiles directory.
    This class doesn't depend on Phyton or Windows architecture.
    """

    @staticmethod
    def Is64Windows():
        return 'PROGRAMFILES(X86)' in os.environ

    @staticmethod
    def GetProgramFiles32():
        if DecideArchitecture.Is64Windows():
            return os.environ['PROGRAMFILES(X86)']
        else:
            return os.environ['PROGRAMFILES']

    @staticmethod
    def GetProgramFiles64():
        if DecideArchitecture.Is64Windows():
            return os.environ['PROGRAMW6432']
        else:
            return None
