lys_check_id(struct lys_node *node, struct lys_node *parent, struct lys_module *module)
{
    struct lys_node *start, *stop, *iter;
    struct lys_node_grp *grp;
    int down, up;
    struct ly_set excluded_parents = {0};

    assert(node);

    if (!parent) {
        assert(module);
    } else {
        module = parent->module;
    }
    module = lys_main_module(module);

    switch (node->nodetype) {
    case LYS_GROUPING:
        /* 6.2.1, rule 6 */
        if (parent) {
            start = *lys_child(parent, LYS_GROUPING);
            if (!start) {
                down = 0;
                start = parent;
            } else {
                down = 1;
            }
            if (parent->nodetype == LYS_EXT) {
                up = 0;
            } else {
                up = 1;
            }
        } else {
            down = up = 1;
            start = module->data;
        }
        /* go up */
        if (up && lys_find_grouping_up(node->name, start)) {
            LOGVAL(module->ctx, LYE_DUPID, LY_VLOG_LYS, node, "grouping", node->name);
            return EXIT_FAILURE;
        }
        /* go down, because grouping can be defined after e.g. container in which is collision */
        if (down) {
            for (iter = start, stop = NULL; iter; iter = iter->prev) {
                if (!stop) {
                    stop = start;
                } else if (iter == stop) {
                    break;
                }
                if (!(iter->nodetype & (LYS_CONTAINER | LYS_CHOICE | LYS_LIST | LYS_GROUPING | LYS_INPUT | LYS_OUTPUT))) {
                    continue;
                }

                grp = NULL;
                while ((grp = lys_get_next_grouping(grp, iter))) {
                    if (ly_strequal(node->name, grp->name, 1)) {
                        LOGVAL(module->ctx, LYE_DUPID,LY_VLOG_LYS, node, "grouping", node->name);
                        return EXIT_FAILURE;
                    }
                }
            }
        }
        break;
    case LYS_LEAF:
    case LYS_LEAFLIST:
    case LYS_LIST:
    case LYS_CONTAINER:
    case LYS_CHOICE:
    case LYS_RPC:
    case LYS_NOTIF:
    case LYS_ACTION:
    case LYS_ANYDATA:
        /* 6.2.1, rule 7 */
        if (parent) {
            iter = parent;
            while (iter && (iter->nodetype & (LYS_USES | LYS_CASE | LYS_CHOICE | LYS_AUGMENT))) {
                if (iter->nodetype == LYS_AUGMENT) {
                    if (((struct lys_node_augment *)iter)->target) {
                        /* augment is resolved, go up */
                        iter = ((struct lys_node_augment *)iter)->target;
                        continue;
                    }
                    /* augment is not resolved, this is the final parent */
                    break;
                }
                ly_set_add(&excluded_parents, (void *)iter, LY_SET_OPT_USEASLIST);
                iter = iter->parent;
            }

            if (!iter) {
                stop = NULL;
                iter = module->data;
            } else if (iter->nodetype == LYS_EXT) {
                stop = iter;
                iter = (struct lys_node *)lys_child(iter, node->nodetype);
                if (iter) {
                    iter = *(struct lys_node **)iter;
                }
            } else {
                stop = iter;
                iter = iter->child;
            }
        } else {
            stop = NULL;
            iter = module->data;
        }
        while (iter) {
            if ((ly_set_contains(&excluded_parents, (void *)iter) != -1) || iter->nodetype & (LYS_USES | LYS_CASE)) {
                iter = iter->child;
                continue;
            }

            if (iter->nodetype & (LYS_LEAF | LYS_LEAFLIST | LYS_LIST | LYS_CONTAINER | LYS_CHOICE | LYS_RPC | LYS_NOTIF | LYS_ACTION | LYS_ANYDATA)) {
                if (lys_node_module(iter) == lys_node_module(node) && ly_strequal(iter->name, node->name, 1)) {
                    LOGVAL(module->ctx, LYE_DUPID, LY_VLOG_LYS, node, strnodetype(node->nodetype), node->name);
                    ly_set_clean(&excluded_parents);
                    return EXIT_FAILURE;
                }
            }

            /* special case for choice - we must check the choice's name as
             * well as the names of nodes under the choice
             */
            if (iter->nodetype == LYS_CHOICE) {
                iter = iter->child;
                continue;
            }

            /* go to siblings */
            if (!iter->next) {
                /* no sibling, go to parent's sibling */
                do {
                    /* for parent LYS_AUGMENT */
                    if (iter->parent == stop) {
                        iter = stop;
                        break;
                    }
                    iter = lys_parent(iter);
                    if (iter && iter->next) {
                        break;
                    }
                } while (iter != stop);

                if (iter == stop) {
                    break;
                }
            }
            iter = iter->next;
        }
        ly_set_clean(&excluded_parents);
        break;
    case LYS_CASE:
        /* 6.2.1, rule 8 */
        if (parent) {
            start = *lys_child(parent, LYS_CASE);
        } else {
            start = module->data;
        }

        LY_TREE_FOR(start, iter) {
            if (!(iter->nodetype & (LYS_ANYDATA | LYS_CASE | LYS_CONTAINER | LYS_LEAF | LYS_LEAFLIST | LYS_LIST))) {
                continue;
            }

            if (iter->module == node->module && ly_strequal(iter->name, node->name, 1)) {
                LOGVAL(module->ctx, LYE_DUPID, LY_VLOG_LYS, node, "case", node->name);
                return EXIT_FAILURE;
            }
        }
        break;
    default:
        /* no check needed */
        break;
    }

    return EXIT_SUCCESS;
}