"""This class provides all photoshop API core functions."""
import winreg
import os

from comtypes.client import CreateObject
from photoshop import constants
from photoshop.errors import PhotoshopPythonAPIError
import platform


class Photoshop(object):
    _root = 'Photoshop'
    REG_PATH = 'SOFTWARE\\Adobe\\Photoshop'
    _object_name = 'Application'
    object_name = None

    def __init__(self, ps_version=None, parent=None):
        self._program_name = None
        version_mappings = constants.PHOTOSHOP_VERSION_MAPPINGS
        self.photoshop_version = os.getenv('PS_VERSION', ps_version)
        self.app_id = version_mappings.get(self.photoshop_version,
                                           self._get_program_id())
        try:
            self.app = self.instance_app(self.app_id)
        except OSError:
            try:
                self.app = self.instance_app(self._get_program_id())
            except OSError:
                raise PhotoshopPythonAPIError(
                    'Please check if you have '
                    'Photoshop installed correctly.',
                )
        if parent:
            self.adobe = self.app
            self.app = parent

    @property
    def typename(self):
        return self.__class__.__name__

    def __call__(self, *args, **kwargs):
        return self.app

    def __str__(self):
        return f'{self.__class__.__name__} <{self._program_name}>'

    def __repr__(self):
        return self

    def __getattribute__(self, item):
        try:
            return super().__getattribute__(item)
        except AttributeError:
            return getattr(self.app, item)

    @staticmethod
    def open_key(key):
        """Open the register key.

        Args:
            key (str): The key of register.

        Returns:
            str: The handle to the specified key.

        """
        machine_type = platform.machine()
        mappings = {
            'AMD64': winreg.KEY_WOW64_64KEY
        }
        return winreg.OpenKey(
            winreg.HKEY_LOCAL_MACHINE,
            key,
            access=winreg.KEY_READ | mappings.get(machine_type,
                                                  winreg.KEY_WOW64_32KEY)
        )

    def get_application_path(self):
        """str: The absolute path of Photoshop installed location."""
        key = self.open_key(f'{self.REG_PATH}\\{self.get_program_id()}')
        return winreg.QueryValueEx(key, 'ApplicationPath')[0]

    def get_plugin_path(self):
        """str: The absolute plugin path of Photoshop."""
        return os.path.join(self.get_application_path(), 'Plug-ins')

    def get_presets_path(self):
        """str: The absolute presets path of Photoshop."""
        return os.path.join(self.get_application_path(), 'Presets')

    def get_script_path(self):
        """str: The absolute scripts path of Photoshop."""
        return os.path.join(self.get_presets_path(), 'Scripts')

    def instance_app(self, ps_id):
        naming_space = [self._root]
        if not self.object_name:
            naming_space.append(self._object_name)
        else:
            naming_space.append(self.object_name)
        naming_space.append(ps_id)
        self._program_name = self._assemble_program_name(naming_space)
        return CreateObject(self._program_name, dynamic=True)

    def get_program_id(self):
        key = self.open_key(self.REG_PATH)
        return winreg.EnumKey(key, 0)

    def _get_program_id(self):
        self.app_id = self.get_program_id().split('.')[0]
        return self.app_id

    @staticmethod
    def _assemble_program_name(names):
        """Assemble program name of Photoshop.

        Args:
            names (list of str): The name to be assembled.
                .e.g:
                    [
                        'Photoshop',
                        'ActionDescriptor',
                        '140'
                    ]

        Returns:
            str: Assembled name.

        Examples:
            Photoshop.ActionDescriptor
            Photoshop.ActionDescriptor.140
            Photoshop.ActionList
            Photoshop.ActionList.140
            Photoshop.ActionReference
            Photoshop.ActionReference.140
            Photoshop.Application
            Photoshop.Application.140
            Photoshop.BatchOptions
            Photoshop.BatchOptions.140
            Photoshop.BitmapConversionOptions
            Photoshop.BMPSaveOptions
            Photoshop.BMPSaveOptions.140
            Photoshop.CameraRAWOpenOptions
            Photoshop.CameraRAWOpenOptions.140

        """
        return '.'.join(names)

    def eval_javascript(self, javascript, Arguments=None, ExecutionMode=None):
        return self.adobe.doJavaScript(javascript, Arguments, ExecutionMode)
