private GeneratorNode generateNode()
    {
        HashSet<GeneratorNode> spawnedNodes;
        HashSet<GeneratorNode> newNodes = new HashSet<GeneratorNode>();
        int size = 0, atoms = 0;

        long timeBegin = Stopwatch.GetTimestamp();

        // Takes each node and applies its decomposition
        foreach (GeneratorNode wn in m_nodes)
        {
            GeneratorNode wn2 = new GeneratorNode();

            wn2.setGamma(wn.getDelta());
            spawnedNodes = wn2.spawn();

            foreach (GeneratorNode gn in spawnedNodes)
            {
                newNodes.Add(gn);
            }

            // Shortcut: as soon as one branch returns something, we skip the others
            if (spawnedNodes.Count > 0)
            {
                break;
            }
        }

        long timeEnd = Stopwatch.GetTimestamp();

        System.Console.WriteLine("Finished producing the " + newNodes.Count + "nodes " + (timeEnd - timeBegin));
        timeBegin = Stopwatch.GetTimestamp();

        // Remember the new nodes
        m_nodes = newNodes;

        // Updates statistics about maximum number of nodes
        size = m_nodes.Count;

        if (size > m_maxNodes)
        {
            m_maxNodes = size;
        }

        // Updates statistics about total number of formulae
        size = 0;
        atoms = 0;

        foreach (GeneratorNode wn in m_nodes)
        {
            size += wn.getSize();
            atoms += wn.getAtoms();
        }

        if (size > m_maxFormulae)
        {
            m_maxFormulae = size;
        }

        if (atoms > m_maxAtoms)
        {
            m_maxAtoms = atoms;
        }

        // We remove nodes that don't contain any OPlus
        // (i.e., that don't tell us to do anything)
        // and check at the same time if at least one node is sound
        bool hasSound = false;

        foreach (GeneratorNode wn in m_nodes)
        {
            HashSet<OPlus> opluses = wn.getOPluses();
            bool contains_element = false;

            foreach (OPlus op in opluses)
            {
                // Remove conditions that only assert path existence
                if (!op.getOperand().Equals(Operator.m_falseAtom) &&
                    !op.getOperand().Equals(Operator.m_trueAtom))
                {
                    contains_element = true;
                }
            }

            if (!contains_element)
            {
                m_nodes.Remove(wn);
            }

            else
            {
                if (wn.Sound)
                {
                    hasSound = true;
                }
            }
        }

        // Can we produce a new message?
        if (m_nodes.Count == 0)
        {
            // No!
            return null;
        }

        // We remove all nodes whose OPluses are contradictory
        /*it = m_nodes.iterator();
        while (it.hasNext())
        {
          GeneratorNode wn = it.next();
          if (wn.containsOPlusContradiction())
            it.remove();
        }*/

        // Is there at least one remaining sound node?
        if (hasSound)
        {
            // Yes: then let's remove any unsound node
            foreach (GeneratorNode wn in m_nodes)
            {
                if (!wn.Sound)
                {
                    m_nodes.Remove(wn);
                }
            }
        }

        timeEnd = Stopwatch.GetTimestamp();
        System.Console.WriteLine("Finished pruning " + (timeEnd - timeBegin));

        // Pick *one* of the nodes randomly
        long pickIndex = (long)System.Math.Round(new System.Random().NextDouble() * (double)(m_nodes.Count - 1));
        int i = 0;
        GeneratorNode pickedNode = null;

        foreach (GeneratorNode gn in m_nodes)
        {
            if (i == pickIndex)
            {
                pickedNode = gn;
                break;
            }

            i++;
        }

        // Keep only the selected node for the next state
        m_nodes = new HashSet<GeneratorNode>();
        m_nodes.Add(pickedNode);

        // Return the picked node
        return pickedNode;
    }
    public HashSet<GeneratorNode> spawn(OperatorV op)
    {
        HashSet<GeneratorNode> spawnedSet, outSet = new HashSet<GeneratorNode>();

        // Do for left node
        GeneratorNode wn = new GeneratorNode(this);
        Operator o2 = op.getLeftOperand();

        wn.addToGamma(o2);
        o2 = op.getRightOperand();
        wn.addToGamma(o2);
        spawnedSet = wn.spawn();

        if (spawnedSet != null)
        {
            foreach (GeneratorNode gn in spawnedSet)
            {
                outSet.Add(gn);
            }
        }

        // Do for right node
        wn = new GeneratorNode(this);
        o2 = op.getRightOperand();
        wn.addToGamma(o2);
        wn.addToDelta(op);
        spawnedSet = wn.spawn();

        if (spawnedSet != null)
        {
            foreach (GeneratorNode gn in spawnedSet)
            {
                outSet.Add(gn);
            }
        }

        return outSet;
    }
    public HashSet<GeneratorNode> spawn(OperatorX op)
    {
        HashSet<GeneratorNode> spawnedSet, outSet = new HashSet<GeneratorNode>();
        GeneratorNode wn = new GeneratorNode(this);
        Operator o2 = op.getOperand();

        wn.addToDelta(o2);
        spawnedSet = wn.spawn();

        if (spawnedSet != null)
        {
            foreach (GeneratorNode gn in spawnedSet)
            {
                outSet.Add(gn);
            }
        }

        return outSet;
    }
    public HashSet<GeneratorNode> spawn(OperatorImplies op)
    {
        HashSet<GeneratorNode> spawnedSet, outSet = new HashSet<GeneratorNode>();

        // Do for right operand
        GeneratorNode wn = new GeneratorNode(this);
        Operator o2 = op.getRightOperand();

        wn.addToGamma(o2);
        spawnedSet = wn.spawn();

        if (spawnedSet != null)
        {
            foreach (GeneratorNode gn in spawnedSet)
            {
                outSet.Add(gn);
            }
        }

        // Do for right operand
        wn = new GeneratorNode(this);
        o2 = op.getLeftOperand().getNegated();
        wn.addToGamma(o2.getNegatedNormalForm());
        spawnedSet = wn.spawn();

        if (spawnedSet != null)
        {
            foreach (GeneratorNode gn in spawnedSet)
            {
                outSet.Add(gn);
            }
        }

        // Do for both (NEW)
        wn = new GeneratorNode(this);
        o2 = op.getRightOperand();
        wn.addToGamma(o2);
        o2 = op.getLeftOperand();
        wn.addToGamma(o2);
        spawnedSet = wn.spawn();

        if (spawnedSet != null)
        {
            foreach (GeneratorNode gn in spawnedSet)
            {
                outSet.Add(gn);
            }
        }

        return outSet;
    }
    public HashSet<GeneratorNode> spawn(FOForAll op)
    {
        HashSet<GeneratorNode> spawnedSet, outSet = new HashSet<GeneratorNode>();
        Atom x = op.getQuantifiedVariable();
        string qualifier = op.getQualifier();

        // Iterate over domain
        HashSet<Constant> oplus_domain = getOPlusDomain(qualifier);
        HashSet<Constant> domain = op.getDomain();
        SubsetIterator<Constant> it;

        if (!m_encounteredQualifiers.Contains(qualifier))
        {
            // We haven't decomposed a For All in the past, so we can
          		// add elements to the message
            it = new SubsetIterator<Constant>(domain, oplus_domain);
        }

        else
        {
            // Otherwise, we stick to the elements we already have to
          		// evaluate this quantifier
            it = new SubsetIterator<Constant>(oplus_domain);
        }

        m_encounteredQualifiers.Add(op.getQualifier());
        m_decomposedAForAll = true;

        if (op.isPathNegation())
        {
            // The quantifier asserts the absence of a path
            GeneratorNode wn = new GeneratorNode(this);
            OPlus opl = new OPlus();

            opl.setQualifier(op.getQualifier());
            opl.setOperand(Operator.m_falseAtom);

            if (!wn.addToOPluses(opl))
            {
                // We can't add this OPlus to the current set. Contradiction! Return the empty set
                return new HashSet<GeneratorNode>();
            }

            return wn.spawn();
        }

        if (op.isPathAssertion())
        {
            // In negated form, the quantifier may assert the existence of a path
            GeneratorNode wn = new GeneratorNode(this);
            OPlus opl = new OPlus();

            opl.setQualifier(op.getQualifier());
            opl.setOperand(Operator.m_trueAtom);

            if (!wn.addToOPluses(opl))
            {
                // We can't add this OPlus to the current set. Contradiction! Return the empty set
                return new HashSet<GeneratorNode>();
            }

            return wn.spawn();
        }

        while (it.hasNext())
        {
            GeneratorNode wn = new GeneratorNode(this);
            HashSet<Constant> subset = it.next();

            foreach (Atom v in subset)
            {
                Operator o2 = op.getOperand();
                Operator o3 = o2.evaluate(x, v);
                OPlus opl = new OPlus(qualifier, v);

                wn.addToGamma(o3);

                if (!wn.addToOPluses(opl))
                {
                    // Contradiction! Skip that branch
                    continue;
                }
            }

            spawnedSet = wn.spawn();

            if (spawnedSet != null)
            {
                foreach (GeneratorNode gn in spawnedSet)
                {
                    outSet.Add(gn);
                }
            }
        }

        return outSet;
    }
    public HashSet<GeneratorNode> spawn(FOExists op)
    {
        HashSet<GeneratorNode> outSet = new HashSet<GeneratorNode>();
        Atom x = op.getQuantifiedVariable();
        string qualifier = op.getQualifier();

        if (m_encounteredQualifiers.Contains(qualifier))
        {
            // We add something along a path where a ForAll has already
          		// been evaluated: soundness is no longer guaranteed for this node
            m_sound = false;
        }

        if (op.isAnOPlus())
        {
            // This is an OPlus; return a node with op transferred to the OPlus set
            GeneratorNode wn = new GeneratorNode(this);

            if (!wn.addToOPluses(op.toOPlus()))
            {
                // We can't add this OPlus to the current set. Contradiction! Return the empty set
                return new HashSet<GeneratorNode>();
            }

            return wn.spawn();
        }

        if (op.isPathAssertion())
        {
            GeneratorNode wn = new GeneratorNode();
            OPlus opl = new OPlus();

            opl.setQualifier(op.getQualifier());
            opl.setOperand(Operator.m_trueAtom);

            if (!wn.addToOPluses(opl))
            {
                // We can't add this OPlus to the current set. Contradiction! Return the empty set
                return new HashSet<GeneratorNode>();
            }

            return wn.spawn();
        }

        // Iterate over domain
        //HashSet<Constant> oplus_domain = getOPlusDomain(qualifier);
        HashSet<Constant> domain = op.getDomain();
        SubsetIterator<Constant> it = new SubsetIterator<Constant>(domain); //,oplus_domain

        while (it.hasNext())
        {
            GeneratorNode wn = new GeneratorNode(this);
            HashSet<Constant> subset = it.next();

            foreach (Atom v in subset)
            {
                Operator o2 = op .getOperand();
                Operator o3 = o2.evaluate(x, v);

                if (!op.isPathAssertion())
                {
                    wn.addToGamma(o3);
                }

                OPlus opl = new OPlus(qualifier, v);

                if (!wn.addToOPluses(opl))
                {
                    // We can't add this OPlus to the current set. Contradiction! Skip that branch
                    continue;
                }
            }

            HashSet<GeneratorNode> spawnedSet = wn.spawn();

            if (spawnedSet != null)
            {
                foreach (GeneratorNode gn in spawnedSet)
                {
                    outSet.Add(gn);
                }
            }
        }

        return outSet;
    }