// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.prelude.query.parser;

import com.yahoo.language.Language;
import com.yahoo.prelude.IndexFacts;
import com.yahoo.search.query.parser.ParserEnvironment;

import java.util.Collections;
import java.util.Iterator;
import java.util.Set;

import static com.yahoo.prelude.query.parser.Token.Kind.*;

/**
 * Parser for queries of type any.
 *
 * @author Steinar Knutsen
 */
public class AnyParser extends SimpleParser {

    public AnyParser(ParserEnvironment environment) {
        super(environment);
    }

    protected Item parseItems() {
        return anyItems(true);
    }

    Item parseFilter(String filter, Language queryLanguage, Set<String> searchDefinitions) {
        return parseFilter(filter, queryLanguage, environment.getIndexFacts().newSession(searchDefinitions, Collections.emptySet()));
    }

    Item parseFilter(String filter, Language queryLanguage, IndexFacts.Session indexFacts) {
        Item filterRoot;

        setState(queryLanguage, indexFacts);
        tokenize(filter, null, indexFacts, queryLanguage);

        filterRoot = anyItems(true);

        if (filterRoot == null) {
            return null;
        }

        markAllTermsAsFilters(filterRoot);
        return filterRoot;
    }

    protected Item negativeItem() {
        int position = tokens.getPosition();
        Item item = null;

        try {
            tokens.skipMultiple(PLUS);

            if (!tokens.skipMultiple(MINUS)) {
                return null;
            }

            if (tokens.currentIsNoIgnore(SPACE)) {
                return null;
            }

            if (item == null) {
                item = indexableItem();
            }

            if (item == null) {
                item = compositeItem();

                if (item != null) {
                    if (item instanceof OrItem) { // Turn into And
                        AndItem and = new AndItem();

                        for (Iterator<Item> i = ((OrItem) item).getItemIterator(); i.hasNext();) {
                            and.addItem(i.next());
                        }
                        item = and;
                    }
                }
            }
            if (item!=null)
                item.setProtected(true);
            return item;
        } finally {
            if (item == null) {
                tokens.setPosition(position);
            }
        }
    }

    /**
     * Returns the top level item resulting from combining the given top
     * level item and the new item. This implements most of the weird transformation
     * rules of the parser.
     */
    protected Item combineItems(Item topLevelItem, Item item) {
        if (topLevelItem == null) {
            return item;
        } else if (topLevelItem instanceof OrItem && item instanceof OrItem) {
            OrItem newTopOr = new OrItem();

            newTopOr.addItem(topLevelItem);
            newTopOr.addItem(item);
            return newTopOr;
        } else if (!(topLevelItem instanceof RankItem)) {
            RankItem rank = new RankItem();

            if (topLevelItem instanceof NotItem) { // Strange rule, but that's how it is
                rank.addItem(topLevelItem);
                rank.addItem(item);
            } else {
                rank.addItem(item);
                rank.addItem(topLevelItem);
            }
            return rank;
        } else if ((topLevelItem instanceof RankItem)
                && (item instanceof RankItem)
                && (((RankItem) item).getItem(0) instanceof OrItem)) {
            RankItem itemAsRank = (RankItem) item;
            OrItem or = (OrItem) itemAsRank.getItem(0);

            ((RankItem) topLevelItem).addItem(0, or);
            for (int i = 1; i < itemAsRank.getItemCount(); i++) {
                or.addItem(0, itemAsRank.getItem(i));
            }
            return topLevelItem;
        } else {
            ((RankItem) topLevelItem).addItem(0, item);
            return topLevelItem;
        }
    }

    Item applyFilter(Item root, String filter, Language queryLanguage, IndexFacts.Session indexFacts) {
        setState(queryLanguage, indexFacts);
        tokenize(filter, null, indexFacts, queryLanguage);
        return filterItems(root);
    }

    private void markAllTermsAsFilters(Item root) {
        if (root instanceof BlockItem) {
            root.setFilter(true);
        }

        if (root instanceof TermItem) {
            root.setFilter(true);
        } else {
            if (root instanceof PhraseItem) {
                root.setFilter(true);
            }
            for (Iterator<Item> i = ((CompositeItem) root).getItemIterator(); i.hasNext();) {
                markAllTermsAsFilters(i.next());
            }
        }
    }

    private Item filterItems(Item root) {
        while (tokens.hasNext()) {
            Item item = null;

            item = positiveItem();
            root = addAndFilter(root, item);
            if (item == null) {
                item = negativeItem();
                root = addNotFilter(root, item);
            }
            if (item == null) {
                item = indexableItem();
                root = addRankFilter(root, item);
            }

            if (item != null) {
                markAllTermsAsFilters(item);
            } else {
                tokens.skip();
            }
        }
        return root;
    }

    private Item addAndFilter(Item root, Item item) {
        if (item == null) {
            return root;
        }

        if (root instanceof AndItem) {
            ((AndItem) root).addItem(item);
            return root;
        }

        if (root instanceof RankItem) {
            Item firstChild = ((RankItem) root).getItem(0);

            if (firstChild instanceof AndItem) {
                ((AndItem) firstChild).addItem(item);
                return root;
            } else if (firstChild instanceof NotItem) {
                ((NotItem) firstChild).addPositiveItem(item);
                return root;
            }
        }

        AndItem and = new AndItem();

        and.addItem(root);
        and.addItem(item);
        return and;
    }

    private Item addNotFilter(Item root, Item item) {
        if (item == null) {
            return root;
        }

        if (root instanceof NotItem) {
            ((NotItem) root).addNegativeItem(item);
            return root;
        }

        if (root instanceof RankItem) {
            RankItem rootAsRank = (RankItem) root;
            Item firstChild = rootAsRank.getItem(0);

            if (firstChild instanceof NotItem) {
                ((NotItem) firstChild).addNegativeItem(item);
                return root;
            } else {
                NotItem not = new NotItem();

                not.addPositiveItem(rootAsRank.removeItem(0));
                not.addNegativeItem(item);
                if (rootAsRank.getItemCount() == 0) {
                    return not;
                } else {
                    rootAsRank.addItem(0, not);
                    return root;
                }
            }
        }

        NotItem not = new NotItem();

        not.addPositiveItem(root);
        not.addNegativeItem(item);
        return not;
    }

    private Item addRankFilter(Item root, Item item) {
        if (item == null) {
            return root;
        }

        if (root instanceof RankItem) {
            ((RankItem) root).addItem(item);
            return root;
        }

        RankItem rank = new RankItem();

        rank.addItem(root);
        rank.addItem(item);
        return rank;
    }

}

@@ -226,16 +226,385 @@
 xFacts;%0A
+import com.yahoo.prelude.query.AndItem;%0Aimport com.yahoo.prelude.query.BlockItem;%0Aimport com.yahoo.prelude.query.CompositeItem;%0Aimport com.yahoo.prelude.query.Item;%0Aimport com.yahoo.prelude.query.NotItem;%0Aimport com.yahoo.prelude.query.OrItem;%0Aimport com.yahoo.prelude.query.PhraseItem;%0Aimport com.yahoo.prelude.query.RankItem;%0Aimport com.yahoo.prelude.query.TermItem;%0A
 import c

Add imports