import axdebug, gateways
from .util import _wrap, _wrap_remove, RaiseNotImpl
import io, traceback
from pprint import pprint
from win32com.server.exception import COMException
import winerror
import string
import sys

# Given an object, return a nice string
def MakeNiceString(ob):
    stream = io.StringIO()
    pprint(ob, stream)
    return string.strip(stream.getvalue())

class ProvideExpressionContexts(gateways.ProvideExpressionContexts):
    pass

class ExpressionContext(gateways.DebugExpressionContext):
    def __init__(self, frame):
        self.frame = frame
    def ParseLanguageText(self, code, radix, delim, flags):
        return _wrap(Expression(self.frame, code, radix, delim, flags), axdebug.IID_IDebugExpression)
    def GetLanguageInfo(self):
#               print "GetLanguageInfo"
        return "Python", "{DF630910-1C1D-11d0-AE36-8C0F5E000000}"

class Expression(gateways.DebugExpression):
    def __init__(self, frame, code, radix, delim, flags):
        self.callback = None
        self.frame = frame
        self.code = code
        self.radix = radix
        self.delim = delim
        self.flags = flags
        self.isComplete = 0
        self.result=None
        self.hresult = winerror.E_UNEXPECTED
    def Start(self, callback):
        try:
            try:
                try:
                    self.result = eval(self.code, self.frame.f_globals, self.frame.f_locals)
                except SyntaxError:
                    exec(self.code, self.frame.f_globals, self.frame.f_locals)
                    self.result = ""
                self.hresult = 0
            except:
                l = traceback.format_exception_only(sys.exc_info()[0], sys.exc_info()[1])
                # l is a list of strings with trailing "\n"
                self.result = string.join(map(lambda s:s[:-1], l), "\n")
                self.hresult = winerror.E_FAIL
        finally:
            self.isComplete = 1
            callback.onComplete()
    def Abort(self):
        print("** ABORT **")

    def QueryIsComplete(self):
        return self.isComplete

    def GetResultAsString(self):
#               print "GetStrAsResult returning", self.result
        return self.hresult, MakeNiceString(self.result)

    def GetResultAsDebugProperty(self):
        result = _wrap(DebugProperty(self.code, self.result, None, self.hresult), axdebug.IID_IDebugProperty)
        return self.hresult, result

def MakeEnumDebugProperty(object, dwFieldSpec, nRadix, iid, stackFrame = None):
    name_vals = []
    if hasattr(object, "items") and hasattr(object, "keys"): # If it is a dict.
        name_vals = iter(object.items())
        dictionary = object
    elif hasattr(object, "__dict__"):  #object with dictionary, module
        name_vals = iter(object.__dict__.items())
        dictionary = object.__dict__
    infos = []
    for name, val in name_vals:
        infos.append(GetPropertyInfo(name, val, dwFieldSpec, nRadix, 0, dictionary, stackFrame))
    return _wrap(EnumDebugPropertyInfo(infos), axdebug.IID_IEnumDebugPropertyInfo)

def GetPropertyInfo(obname, obvalue, dwFieldSpec, nRadix, hresult=0, dictionary = None, stackFrame = None):
    # returns a tuple
    name = typ = value = fullname = attrib = dbgprop = None
    if dwFieldSpec & axdebug.DBGPROP_INFO_VALUE:
        value = MakeNiceString(obvalue)
    if dwFieldSpec & axdebug.DBGPROP_INFO_NAME:
        name = obname
    if dwFieldSpec & axdebug.DBGPROP_INFO_TYPE:
        if hresult:
            typ = "Error"
        else:
            try:
                typ = type(obvalue).__name__
            except AttributeError:
                typ = str(type(obvalue))
    if dwFieldSpec & axdebug.DBGPROP_INFO_FULLNAME:
        fullname = obname
    if dwFieldSpec & axdebug.DBGPROP_INFO_ATTRIBUTES:
        if hasattr(obvalue, "has_key") or hasattr(obvalue, "__dict__"): # If it is a dict or object
            attrib = axdebug.DBGPROP_ATTRIB_VALUE_IS_EXPANDABLE
        else:
            attrib = 0
    if dwFieldSpec & axdebug.DBGPROP_INFO_DEBUGPROP:
        dbgprop = _wrap(DebugProperty(name, obvalue, None, hresult, dictionary, stackFrame), axdebug.IID_IDebugProperty)
    return name, typ, value, fullname, attrib, dbgprop

from win32com.server.util import ListEnumeratorGateway
class EnumDebugPropertyInfo(ListEnumeratorGateway):
    """A class to expose a Python sequence as an EnumDebugCodeContexts

    Create an instance of this class passing a sequence (list, tuple, or
    any sequence protocol supporting object) and it will automatically
    support the EnumDebugCodeContexts interface for the object.

    """
    _public_methods_ = ListEnumeratorGateway._public_methods_ + ["GetCount"]
    _com_interfaces_ = [ axdebug.IID_IEnumDebugPropertyInfo]
    def GetCount(self):
        return len(self._list_)
    def _wrap(self, ob):
        return ob

class DebugProperty:
    _com_interfaces_ = [axdebug.IID_IDebugProperty]
    _public_methods_ = ['GetPropertyInfo', 'GetExtendedInfo', 'SetValueAsString',
                        'EnumMembers', 'GetParent'
    ]
    def __init__(self, name, value, parent = None, hresult = 0, dictionary = None, stackFrame = None):
        self.name = name
        self.value = value
        self.parent = parent
        self.hresult = hresult
        self.dictionary = dictionary
        self.stackFrame = stackFrame

    def GetPropertyInfo(self, dwFieldSpec, nRadix):
        return GetPropertyInfo(self.name, self.value, dwFieldSpec, nRadix, self.hresult, dictionary, stackFrame)

    def GetExtendedInfo(self): ### Note - not in the framework.
        RaiseNotImpl("DebugProperty::GetExtendedInfo")

    def SetValueAsString(self, value, radix):
        if self.stackFrame and self.dictionary:
            self.dictionary[self.name]= eval(value,self.stackFrame.f_globals, self.stackFrame.f_locals)
        else:
            RaiseNotImpl("DebugProperty::SetValueAsString")

    def EnumMembers(self, dwFieldSpec, nRadix, iid):
        # Returns IEnumDebugPropertyInfo
        return MakeEnumDebugProperty(self.value, dwFieldSpec, nRadix, iid, self.stackFrame)

    def GetParent(self):
        # return IDebugProperty
        RaiseNotImpl("DebugProperty::GetParent")
