Parses a powerful and simple string criteria and generates a proper
        mongo syntax criteria.

        Args:
            criteria_string (str): A string representing a search criteria.
                Also supports wild cards. E.g.,
                something like "*2O" gets converted to
                {'pretty_formula': {'$in': [u'B2O', u'Xe2O', u"Li2O", ...]}}

                Other syntax examples:
                    mp-1234: Interpreted as a Materials ID.
                    Fe2O3 or *2O3: Interpreted as reduced formulas.
                    Li-Fe-O or *-Fe-O: Interpreted as chemical systems.

                You can mix and match with spaces, which are interpreted as
                "OR". E.g., "mp-1234 FeO" means query for all compounds with
                reduced formula FeO or with materials_id mp-1234.

        Returns:
            A mongo query dict.

def parse_criteria(criteria_string):
        """
        Parses a powerful and simple string criteria and generates a proper
        mongo syntax criteria.

        Args:
            criteria_string (str): A string representing a search criteria.
                Also supports wild cards. E.g.,
                something like "*2O" gets converted to
                {'pretty_formula': {'$in': [u'B2O', u'Xe2O', u"Li2O", ...]}}

                Other syntax examples:
                    mp-1234: Interpreted as a Materials ID.
                    Fe2O3 or *2O3: Interpreted as reduced formulas.
                    Li-Fe-O or *-Fe-O: Interpreted as chemical systems.

                You can mix and match with spaces, which are interpreted as
                "OR". E.g., "mp-1234 FeO" means query for all compounds with
                reduced formula FeO or with materials_id mp-1234.

        Returns:
            A mongo query dict.
        """
        toks = criteria_string.split()

        def parse_sym(sym):
            if sym == "*":
                return [el.symbol for el in Element]
            else:
                m = re.match(r"\{(.*)\}", sym)
                if m:
                    return [s.strip() for s in m.group(1).split(",")]
                else:
                    return [sym]

        def parse_tok(t):
            if re.match(r"\w+-\d+", t):
                return {"task_id": t}
            elif "-" in t:
                elements = [parse_sym(sym) for sym in t.split("-")]
                chemsyss = []
                for cs in itertools.product(*elements):
                    if len(set(cs)) == len(cs):
                        # Check for valid symbols
                        cs = [Element(s).symbol for s in cs]
                        chemsyss.append("-".join(sorted(cs)))
                return {"chemsys": {"$in": chemsyss}}
            else:
                all_formulas = set()
                explicit_els = []
                wild_card_els = []
                for sym in re.findall(
                        r"(\*[\.\d]*|\{.*\}[\.\d]*|[A-Z][a-z]*)[\.\d]*", t):
                    if ("*" in sym) or ("{" in sym):
                        wild_card_els.append(sym)
                    else:
                        m = re.match(r"([A-Z][a-z]*)[\.\d]*", sym)
                        explicit_els.append(m.group(1))
                nelements = len(wild_card_els) + len(set(explicit_els))
                parts = re.split(r"(\*|\{.*\})", t)
                parts = [parse_sym(s) for s in parts if s != ""]
                for f in itertools.product(*parts):
                    c = Composition("".join(f))
                    if len(c) == nelements:
                        # Check for valid Elements in keys.
                        for e in c.keys():
                            Element(e.symbol)
                        all_formulas.add(c.reduced_formula)
                return {"pretty_formula": {"$in": list(all_formulas)}}

        if len(toks) == 1:
            return parse_tok(toks[0])
        else:
            return {"$or": list(map(parse_tok, toks))}