import Cocoa
import objc

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 initialize 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
            aDict = {
                "name": nameStr or "",
                "phone": phoneStr or "",
                "city": cityStr or "",
                "state": stateStr or "",
                "url": Cocoa.NSURL.fileURLWithPath_(storePath),
            }
            self.mySearchResults.append(aDict)

    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 receive 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):
            predicate_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 predicate_type != Cocoa.NSNotPredicateType
                ):
                    return cleanSubpredicates[0]

                else:
                    return (
                        Cocoa.NSCompoundPredicate.alloc().initWithType_subpredicates_(
                            predicate_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] == "\r":
                # get the predicate, 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 accommodate 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
