##############################################################################
#
# Copyright (c) 2002 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (ZPL).  A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################

"""Generic parser support: exception and parse tree nodes."""

from BTrees.IIBTree import difference

from Products.ZCTextIndex.IQueryParseTree import IQueryParseTree
from Products.ZCTextIndex.SetOps import mass_weightedIntersection, \
                                        mass_weightedUnion

class QueryError(Exception):
    pass

class ParseError(Exception):
    pass

class ParseTreeNode:

    __implements__ = IQueryParseTree

    _nodeType = None

    def __init__(self, value):
        self._value = value

    def nodeType(self):
        return self._nodeType

    def getValue(self):
        return self._value

    def __repr__(self):
        return "%s(%r)" % (self.__class__.__name__, self.getValue())

    def terms(self):
        t = []
        for v in self.getValue():
            t.extend(v.terms())
        return t

    def executeQuery(self, index):
        raise NotImplementedError

class NotNode(ParseTreeNode):

    _nodeType = "NOT"

    def terms(self):
        return []

    def executeQuery(self, index):
        raise QueryError, "NOT parse tree node cannot be executed directly"

class AndNode(ParseTreeNode):

    _nodeType = "AND"

    def executeQuery(self, index):
        L = []
        Nots = []
        for subnode in self.getValue():
            if subnode.nodeType() == "NOT":
                r = subnode.getValue().executeQuery(index)
                # If None, technically it matches every doc, but we treat
                # it as if it matched none (we want
                #     real_word AND NOT stop_word
                # to act like plain real_word).
                if r is not None:
                    Nots.append((r, 1))
            else:
                r = subnode.executeQuery(index)
                # If None, technically it matches every doc, so needn't be
                # included.
                if r is not None:
                    L.append((r, 1))
        set = mass_weightedIntersection(L)
        if Nots:
            notset = mass_weightedUnion(Nots)
            set = difference(set, notset)
        return set

class OrNode(ParseTreeNode):

    _nodeType = "OR"

    def executeQuery(self, index):
        weighted = []
        for node in self.getValue():
            r = node.executeQuery(index)
            # If None, technically it matches every doc, but we treat
            # it as if it matched none (we want
            #     real_word OR stop_word
            # to act like plain real_word).
            if r is not None:
                weighted.append((r, 1))
        return mass_weightedUnion(weighted)

class AtomNode(ParseTreeNode):

    _nodeType = "ATOM"

    def terms(self):
        return [self.getValue()]

    def executeQuery(self, index):
        return index.search(self.getValue())

class PhraseNode(AtomNode):

    _nodeType = "PHRASE"

    def executeQuery(self, index):
        return index.search_phrase(self.getValue())

class GlobNode(AtomNode):

    _nodeType = "GLOB"

    def executeQuery(self, index):
        return index.search_glob(self.getValue())
