from Foundation import NSObject, NSBundle
from objc import selector, getClassList, objc_object, IBOutlet, IBAction

import objc
import AppKit
objc.setVerbose(1)

try:
    import AddressBook
except ImportError:
    pass

try:
    import PreferencePanes
except ImportError:
    pass

try:
    import InterfaceBuilder
except ImportError:
    pass

WRAPPED={}
class Wrapper (NSObject):
    """
    NSOutlineView doesn't retain values, which means we cannot use normal
    python values as values in an outline view.
    """
    def init_(self, value):
        self.value = value
        return self

    def __str__(self):
        return '<Wrapper for %s>'%self.value

    def description(self):
        return str(self)

def wrap_object(obj):
    if WRAPPED.has_key(obj):
        return WRAPPED[obj]
    else:
        WRAPPED[obj] = Wrapper.alloc().init_(obj)
        return WRAPPED[obj]

def unwrap_object(obj):
    if obj is None:
        return obj
    return obj.value

methodIdentifier = {
    'objc_method':0,
    'python_method':1,
    'signature':2
}

def classPath(cls):
    if cls == objc_object:
        return ''
    elif cls.__bases__[0] == objc_object:
        return cls.__name__
    else:
        return '%s : %s'%(classPath(cls.__bases__[0]), cls.__name__)

SYSFRAMEWORKS_DIR='/System/Library/Frameworks/'
def classBundle(cls):
    framework = NSBundle.bundleForClass_(cls).bundlePath()
    if framework.startswith(SYSFRAMEWORKS_DIR):
        framework = framework[len(SYSFRAMEWORKS_DIR):]
        if framework.endswith('.framework'):
            framework = framework[:-len('.framework')]
    return framework

class ClassesDataSource (NSObject):
    __slots__ = ('_classList', '_classTree', '_methodInfo', '_classInfo')

    classLabel = IBOutlet()
    classTable = IBOutlet()
    frameworkLabel = IBOutlet()
    methodTable = IBOutlet()
    searchBox = IBOutlet()
    window = IBOutlet()

    def clearClassInfo(self):
        self._methodInfo = []
        self.methodTable.reloadData()
        self.window.setTitle_('iClass')


    def setClassInfo(self, cls):
        self.window.setTitle_('iClass: %s'%cls.__name__)
        self.classLabel.setStringValue_(classPath(cls))
        self.frameworkLabel.setStringValue_(classBundle(cls))
        self._methodInfo = []
        for nm, meth in cls.pyobjc_instanceMethods.__dict__.items():
            if not isinstance(meth, selector):
                continue
            self._methodInfo.append(
                ('-'+meth.selector, nm, meth.signature))

        for nm, meth in cls.pyobjc_classMethods.__dict__.items():
            if not isinstance(meth, selector):
                continue
            self._methodInfo.append(
                ('+'+meth.selector, nm, meth.signature))
        self._methodInfo.sort()
        self.methodTable.reloadData()

    def outlineViewSelectionDidChange_(self, notification):
        rowNr = self.classTable.selectedRow()
        if rowNr == -1:
            self.clearClassInfo()
        else:
            item = self.classTable.itemAtRow_(rowNr)
            self.setClassInfo(unwrap_object(item))

    def showClass(self, cls):
        # First expand the tree (to make item visible)
        super = cls.__bases__[0]
        if  super == objc_object:
            return

        self.showClass(super)
        item = wrap_object(super)
        rowNr = self.classTable.rowForItem_(item)
        self.classTable.expandItem_(item)

    def selectClass(self, cls):
        self.showClass(cls)

        item = wrap_object(cls)
        rowNr = self.classTable.rowForItem_(item)

        self.classTable.scrollRowToVisible_(rowNr)
        self.classTable.selectRow_byExtendingSelection_(rowNr, False)

    @IBAction
    def searchClass_(self, event):
        val = self.searchBox.stringValue()
        if not val:
            return

        found = None
        for cls in self._classTree.keys():
            if not cls: continue
            if cls.__name__ == val:
                self.setClassInfo(cls)
                self.selectClass(cls)
                return
            elif cls.__name__.startswith(val):
                if not found:
                    found = cls
                elif len(cls.__name__) > len(found.__name__):
                    found = cls

        # mvl 2009-03-02: fix case where no match is found caused exception:
        if (found is None): return

        self.setClassInfo(found)
        self.selectClass(found)


    def refreshClasses(self):
        self._classList = getClassList()
        self._classTree = {}
        self._methodInfo = []

        for cls in self._classList:
            super = cls.__bases__[0]
            if super == objc_object:
                super = None
            else:
                super = super
            if not self._classTree.has_key(cls):
                self._classTree[cls] = []

            if self._classTree.has_key(super):
                self._classTree[super].append(cls)
            else:
                self._classTree[super] = [ cls ]

        for lst in self._classTree.values():
            lst.sort()

    def init(self):
        self._classInfo = getClassList()
        self.refreshClasses()
        return self

    def awakeFromNib(self):
        self._classInfo = getClassList()
        self.refreshClasses()


    def outlineView_child_ofItem_(self, outlineview, index, item):
        return wrap_object(self._classTree[unwrap_object(item)][index])

    def outlineView_isItemExpandable_(self, outlineview, item):
        return len(self._classTree[unwrap_object(item)]) != 0


    def outlineView_numberOfChildrenOfItem_(self, outlineview, item):
        return len(self._classTree[unwrap_object(item)])

    def outlineView_objectValueForTableColumn_byItem_(self, outlineview, column, item):
        if item is None:
            return '<None>'
        else:
            v = item.value
            return v.__name__


    def numberOfRowsInTableView_(self, aTableView):
        return len(self._methodInfo)

    def tableView_objectValueForTableColumn_row_(self,
                             aTableView, aTableColumn, rowIndex):
        return self._methodInfo[rowIndex][methodIdentifier[aTableColumn.identifier()]]
