import Cocoa
import objc

import math

searchIndex = 0

class MyWindowController (Cocoa.NSWindowController):
    query = objc.ivar()
    previousRowCount = objc.ivar(type=objc._C_INT)

    myTableView = objc.IBOutlet()
    mySearchResults = objc.IBOutlet()
    predicateEditor = objc.IBOutlet()
    progressView = objc.IBOutlet()        # the progress search view
    progressSearch = objc.IBOutlet()      # spinning gear
    progressSearchLabel = objc.IBOutlet() # search result #


    def dealloc(self):
        Cocoa.NSNotificationCenter.defaultCenter().removeObserver_(self)

    def awakeFromNib(self):
        # no vertical scrolling, we always want to show all rows
        self.predicateEditor.enclosingScrollView().setHasVerticalScroller_(False)

        self.previousRowCount = 3
        self.predicateEditor.addRow_(self)

        # put the focus in the first text field
        displayValue = self.predicateEditor.displayValuesForRow_(1).lastObject()
        if isinstance(displayValue,  Cocoa.NSControl):
            self.window().makeFirstResponder_(displayValue)

        # create and initalize our query
        self.query = Cocoa.NSMetadataQuery.alloc().init()

        # setup our Spotlight notifications
        nf = Cocoa.NSNotificationCenter.defaultCenter()
        nf.addObserver_selector_name_object_(self, 'queryNotification:', None, self.query)

        # initialize our Spotlight query, sort by contact name

        self.query.setSortDescriptors_([Cocoa.NSSortDescriptor.alloc().initWithKey_ascending_(
            'kMDItemContactKeywords', True)])
        self.query.setDelegate_(self)

        # start with our progress search label empty
        self.progressSearchLabel.setStringValue_("")

        return

    def applicationShouldTerminateAfterLastWindowClosed_(self, sender):
        return True

    def loadResultsFromQuery_(self, notif):
        results = notif.object().results()

        Cocoa.NSLog("search count = %d", len(results))
        foundResultsStr = "Results found: %d"%(len(results),)
        self.progressSearchLabel.setStringValue_(foundResultsStr)

        if len(results) == 0:
            return

        # iterate through the array of results, and match to the existing stores
        for item in results:
            cityStr = item.valueForAttribute_('kMDItemCity')
            nameStr = item.valueForAttribute_('kMDItemDisplayName')
            stateStr = item.valueForAttribute_('kMDItemStateOrProvince')
            phoneNumbers = item.valueForAttribute_('kMDItemPhoneNumbers')
            phoneStr = None
            if phoneNumbers:
                phoneStr = phoneNumbers[0]

            storePath = item.valueForAttribute_('kMDItemPath').stringByResolvingSymlinksInPath()

            # create a dictionary entry to be added to our search results array
            emptyStr = ""
            dict = {
                    'name': nameStr or "",
                    'phone': phoneStr or "",
                    'city': cityStr or "",
                    'state': stateStr or "",
                    'url': Cocoa.NSURL.fileURLWithPath_(storePath),
            }
            self.mySearchResults.append(dict)

    def queryNotification_(self, note):
        # the NSMetadataQuery will send back a note when updates are happening.
        # By looking at the [note name], we can tell what is happening
        if note.name() == Cocoa.NSMetadataQueryDidStartGatheringNotification:
            # the query has just started
            Cocoa.NSLog("search: started gathering")

            self.progressSearch.setHidden_(False)
            self.progressSearch.startAnimation_(self)
            self.progressSearch.animate_(self)
            self.progressSearchLabel.setStringValue_("Searching...")

        elif note.name() == Cocoa.NSMetadataQueryDidFinishGatheringNotification:
            # at this point, the query will be done. You may recieve an update
            # later on.
            Cocoa.NSLog("search: finished gathering");

            self.progressSearch.setHidden_(True)
            self.progressSearch.stopAnimation_(self)

            self.loadResultsFromQuery_(note)

        elif note.name() == Cocoa.NSMetadataQueryGatheringProgressNotification:
            # the query is still gathering results...
            Cocoa.NSLog("search: progressing...")

            self.progressSearch.animate_(self)

        elif note.name() == Cocoa.NSMetadataQueryDidUpdateNotification:
            # an update will happen when Spotlight notices that a file as
            # added, removed, or modified that affected the search results.
            Cocoa.NSLog("search: an update happened.")

    # -------------------------------------------------------------------------
    #   inspect:selectedObjects
    #
    #   This method obtains the selected object (in our case for single selection,
    #   it's the first item), and opens its URL.
    # -------------------------------------------------------------------------
    def inspect_(self, selectedObjects):
        objectDict = selectedObjects[0]
        if objectDict is not None:
            url = objectDict['url']
            Cocoa.NSWorkspace.sharedWorkspace().openURL_(url)

    # ------------------------------------------------------------------------
    #   spotlightFriendlyPredicate:predicate
    #
    #   This method will "clean up" an NSPredicate to make it ready for Spotlight, or return nil if the predicate can't be cleaned.
    #
    #   Foundation's Spotlight support in NSMetdataQuery places the following requirements on an NSPredicate:
    #           - Value-type (always YES or NO) predicates are not allowed
    #           - Any compound predicate (other than NOT) must have at least two subpredicates
    # -------------------------------------------------------------------------
    def spotlightFriendlyPredicate_(self, predicate):
        if predicate == Cocoa.NSPredicate.predicateWithValue_(True) or predicate == Cocoa.NSPredicate.predicateWithValue_(False):
            return False

        elif isinstance(predicate, Cocoa.NSCompoundPredicate):

            type = predicate.compoundPredicateType()
            cleanSubpredicates = []
            for dirtySubpredicate in predicate.subpredicates():
                cleanSubpredicate = self.spotlightFriendlyPredicate_(
                    dirtySubpredicate)
                if cleanSubpredicate:
                    cleanSubpredicates.append(cleanSubpredicate)

            if len(cleanSubpredicates) == 0:
                return None

            else:
                if len(cleanSubpredicates) == 1 and type != Cocoa.NSNotPredicateType:
                    return cleanSubpredicates[0]

                else:
                    return Cocoa.NSCompoundPredicate.alloc().initWithType_subpredicates_(type, cleanSubpredicates)

        else:
            return predicate

    # -------------------------------------------------------------------------
    #   createNewSearchForPredicate:predicate:withTitle
    #
    # -------------------------------------------------------------------------
    def createNewSearchForPredicate_withTitle_(self, predicate, title):
        if predicate is not None:
            self.mySearchResults.removeObjects_(
                self.mySearchResults.arrangedObjects());        # remove the old search results

            # always search for items in the Address Book
            addrBookPredicate = Cocoa.NSPredicate.predicateWithFormat_(
                "(kMDItemKind = 'Address Book Person Data')")
            predicate = Cocoa.NSCompoundPredicate.andPredicateWithSubpredicates_(
                [addrBookPredicate, predicate])

            self.query.setPredicate_(predicate)
            self.query.startQuery()

    # --------------------------------------------------------------------------
    #   predicateEditorChanged:sender
    #
    #  This method gets called whenever the predicate editor changes.
    #   It is the action of our predicate editor and the single plate for all our updates.
    #
    #   We need to do potentially three things:
    #           1) Fire off a search if the user hits enter.
    #           2) Add some rows if the user deleted all of them, so the user isn't left without any rows.
    #           3) Resize the window if the number of rows changed (the user hit + or -).
    # --------------------------------------------------------------------------
    @objc.IBAction
    def predicateEditorChanged_(self, sender):
        # check NSApp currentEvent for the return key
        event = Cocoa.NSApp.currentEvent()
        if event is None:
            return

        if event.type() == Cocoa.NSKeyDown:
            characters = event.characters()
            if len(characters) > 0 and characters[0] == u'\r':
                # get the predicat, which is the object value of our view
                predicate = self.predicateEditor.objectValue()

                # make it Spotlight friendly
                predicate = self.spotlightFriendlyPredicate_(predicate)
                if predicate is not None:
                    global searchIndex
                    title = Cocoa.NSLocalizedString("Search #%ld", "Search title");
                    self.createNewSearchForPredicate_withTitle_(
                            predicate, title % searchIndex)
                    searchIndex += 1

        # if the user deleted the first row, then add it again - no sense leaving the user with no rows
        if self.predicateEditor.numberOfRows() == 0:
            self.predicateEditor.addRow_(self)

        # resize the window vertically to accomodate our views:

        # get the new number of rows, which tells us the needed change in height,
        # note that we can't just get the view frame, because it's currently animating - this method is called before the animation is finished.
        newRowCount = self.predicateEditor.numberOfRows()

        # if there's no change in row count, there's no need to resize anything
        if newRowCount == self.previousRowCount:
            return

        # The autoresizing masks, by default, allows the NSTableView to grow and keeps the predicate editor fixed.
        # We need to temporarily grow the predicate editor, and keep the NSTableView fixed, so we have to change the autoresizing masks.
        # Save off the old ones; we'll restore them after changing the window frame.
        tableScrollView = self.myTableView.enclosingScrollView()
        oldOutlineViewMask = tableScrollView.autoresizingMask()

        predicateEditorScrollView = self.predicateEditor.enclosingScrollView()
        oldPredicateEditorViewMask = predicateEditorScrollView.autoresizingMask()

        tableScrollView.setAutoresizingMask_(
                Cocoa.NSViewWidthSizable | Cocoa.NSViewMaxYMargin)
        predicateEditorScrollView.setAutoresizingMask_(
                Cocoa.NSViewWidthSizable | Cocoa.NSViewHeightSizable)

        # determine if we need to grow or shrink the window
        growing = (newRowCount > self.previousRowCount)

        # if growing, figure out by how much.  Sizes must contain nonnegative values, which is why we avoid negative floats here.
        heightDifference = abs(self.predicateEditor.rowHeight() * (newRowCount - self.previousRowCount))

        # convert the size to window coordinates -
        # if we didn't do this, we would break under scale factors other than 1.
        # We don't care about the horizontal dimension, so leave that as 0.
        #
        sizeChange = self.predicateEditor.convertSize_toView_(
                Cocoa.NSMakeSize(0, heightDifference), None)

        # offset our status view
        frame = self.progressView.frame()
        self.progressView.setFrameOrigin_(Cocoa.NSMakePoint(
            frame.origin.x,
            frame.origin.y - self.predicateEditor.rowHeight() * (newRowCount - self.previousRowCount)))

        # change the window frame size:
        # - if we're growing, the height goes up and the origin goes down (corresponding to growing down).
        # - if we're shrinking, the height goes down and the origin goes up.
        windowFrame = self.window().frame()
        if growing:
            windowFrame.size.height += sizeChange.height
            windowFrame.origin.y -= sizeChange.height
        else:
            windowFrame.size.height -= sizeChange.height
            windowFrame.origin.y += sizeChange.height

        self.window().setFrame_display_animate_(windowFrame, True, True)

        # restore the autoresizing mask
        tableScrollView.setAutoresizingMask_(oldOutlineViewMask)
        predicateEditorScrollView.setAutoresizingMask_(oldPredicateEditorViewMask)

        self.previousRowCount = newRowCount     # save our new row count
