""" applescript -- Easy-to-use Python wrapper for NSAppleScript """
"""
    This code is from py-applescript, a public domain package available at:
    https://github.com/rdhyee/py-applescript

    I've included the whole thing here for simplicity as there is more than one
    applescript packge on PyPi so there's ambiguity as to which one "import applescript"
    would use if user had installed another library. 
    
    This package is used instead of the others because it uses a native PyObjC 
    bridge and is thus much faster than others which use osascript.

"""

import sys

from Foundation import (
    NSAppleScript,
    NSAppleEventDescriptor,
    NSURL,
    NSAppleScriptErrorMessage,
    NSAppleScriptErrorBriefMessage,
    NSAppleScriptErrorNumber,
    NSAppleScriptErrorAppName,
    NSAppleScriptErrorRange,
)

from .aecodecs import Codecs, fourcharcode, AEType, AEEnum
from . import kae

__all__ = ["AppleScript", "ScriptError", "AEType", "AEEnum", "kMissingValue", "kae"]


######################################################################


class AppleScript:
    """ Represents a compiled AppleScript. The script object is persistent; its handlers may be called multiple times and its top-level properties will retain current state until the script object's disposal.
	
	
	"""

    _codecs = Codecs()

    def __init__(self, source=None, path=None):
        """
			source : str | None -- AppleScript source code
			path : str | None -- full path to .scpt/.applescript file
			
			Notes:
			
			- Either the path or the source argument must be provided.
			
			- If the script cannot be read/compiled, a ScriptError is raised.
		"""
        if path:
            url = NSURL.fileURLWithPath_(path)
            self._script, errorinfo = NSAppleScript.alloc().initWithContentsOfURL_error_(
                url, None
            )
            if errorinfo:
                raise ScriptError(errorinfo)
        elif source:
            self._script = NSAppleScript.alloc().initWithSource_(source)
        else:
            raise ValueError("Missing source or path argument.")
        if not self._script.isCompiled():
            errorinfo = self._script.compileAndReturnError_(None)[1]
            if errorinfo:
                raise ScriptError(errorinfo)

    def __repr__(self):
        s = self.source
        return "AppleScript({})".format(
            repr(s) if len(s) < 100 else "{}...{}".format(repr(s)[:80], repr(s)[-17:])
        )

        ##

    def _newevent(self, suite, code, args):
        evt = NSAppleEventDescriptor.appleEventWithEventClass_eventID_targetDescriptor_returnID_transactionID_(
            fourcharcode(suite),
            fourcharcode(code),
            NSAppleEventDescriptor.nullDescriptor(),
            0,
            0,
        )
        evt.setDescriptor_forKeyword_(
            self._codecs.pack(args), fourcharcode(kae.keyDirectObject)
        )
        return evt

    def _unpackresult(self, result, errorinfo):
        if not result:
            raise ScriptError(errorinfo)
        return self._codecs.unpack(result)

        ##

    source = property(
        lambda self: str(self._script.source()), doc="str -- the script's source code"
    )

    def run(self, *args):
        """ Run the script, optionally passing arguments to its run handler.
			
				args : anything -- arguments to pass to script, if any; see also supported type mappings documentation
				Result : anything | None -- the script's return value, if any
			
			Notes:
			
			- The run handler must be explicitly declared in order to pass arguments.
			
			- AppleScript will ignore excess arguments. Passing insufficient arguments will result in an error.
			
			- If execution fails, a ScriptError is raised.
		"""
        if args:
            evt = self._newevent(kae.kCoreEventClass, kae.kAEOpenApplication, args)
            return self._unpackresult(*self._script.executeAppleEvent_error_(evt, None))
        else:
            return self._unpackresult(*self._script.executeAndReturnError_(None))

    def call(self, name, *args):
        """ Call the specified user-defined handler.
				
				name : str -- the handler's name (case-sensitive)
				args : anything -- arguments to pass to script, if any; see documentation for supported types
				Result : anything | None -- the script's return value, if any
			
			Notes:
			
			- The handler's name must be a user-defined identifier, not an AppleScript keyword; e.g. 'myCount' is acceptable; 'count' is not.
			
			- AppleScript will ignore excess arguments. Passing insufficient arguments will result in an error.
			
			- If execution fails, a ScriptError is raised.
		"""
        evt = self._newevent(
            kae.kASAppleScriptSuite, kae.kASPrepositionalSubroutine, args
        )
        evt.setDescriptor_forKeyword_(
            NSAppleEventDescriptor.descriptorWithString_(name),
            fourcharcode(kae.keyASSubroutineName),
        )
        return self._unpackresult(*self._script.executeAppleEvent_error_(evt, None))


##


class ScriptError(Exception):
    """ Indicates an AppleScript compilation/execution error. """

    def __init__(self, errorinfo):
        self._errorinfo = dict(errorinfo)

    def __repr__(self):
        return "ScriptError({})".format(self._errorinfo)

    @property
    def message(self):
        """ str -- the error message """
        msg = self._errorinfo.get(NSAppleScriptErrorMessage)
        if not msg:
            msg = self._errorinfo.get(NSAppleScriptErrorBriefMessage, "Script Error")
        return msg

    number = property(
        lambda self: self._errorinfo.get(NSAppleScriptErrorNumber),
        doc="int | None -- the error number, if given",
    )

    appname = property(
        lambda self: self._errorinfo.get(NSAppleScriptErrorAppName),
        doc="str | None -- the name of the application that reported the error, where relevant",
    )

    @property
    def range(self):
        """ (int, int) -- the start and end points (1-indexed) within the source code where the error occurred """
        range = self._errorinfo.get(NSAppleScriptErrorRange)
        if range:
            start = range.rangeValue().location
            end = start + range.rangeValue().length
            return (start, end)
        else:
            return None

    def __str__(self):
        msg = self.message
        for s, v in [
            (" ({})", self.number),
            (" app={!r}", self.appname),
            (" range={0[0]}-{0[1]}", self.range),
        ]:
            if v is not None:
                msg += s.format(v)
        return (
            msg.encode("ascii", "replace") if sys.version_info.major < 3 else msg
        )  # 2.7 compatibility


##


kMissingValue = AEType(kae.cMissingValue)  # convenience constant
