/**
 * @file NCDAst.c
 * @author Ambroz Bizjak <ambrop7@gmail.com>
 * 
 * @section LICENSE
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of the author nor the
 *    names of its contributors may be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include <stdlib.h>
#include <limits.h>
#include <string.h>

#include <misc/offset.h>
#include <misc/strdup.h>

#include "NCDAst.h"

struct NCDValue__list_element {
    LinkedList1Node list_node;
    NCDValue v;
};

struct NCDValue__map_element {
    LinkedList1Node list_node;
    NCDValue key;
    NCDValue val;
};

struct ProgramElem {
    LinkedList1Node elems_list_node;
    NCDProgramElem elem;
};

struct BlockStatement {
    LinkedList1Node statements_list_node;
    NCDStatement s;
};

struct IfBlockIf {
    LinkedList1Node ifs_list_node;
    NCDIf ifc;
};

static void value_assert (NCDValue *o)
{
    switch (o->type) {
        case NCDVALUE_STRING:
        case NCDVALUE_LIST:
        case NCDVALUE_MAP:
        case NCDVALUE_VAR:
            return;
        default:
            ASSERT(0);
    }
}

void NCDValue_Free (NCDValue *o)
{
    switch (o->type) {
        case NCDVALUE_STRING: {
            free(o->string);
        } break;
        
        case NCDVALUE_LIST: {
            LinkedList1Node *n;
            while (n = LinkedList1_GetFirst(&o->list)) {
                struct NCDValue__list_element *e = UPPER_OBJECT(n, struct NCDValue__list_element, list_node);
                
                NCDValue_Free(&e->v);
                LinkedList1_Remove(&o->list, &e->list_node);
                free(e);
            }
        } break;
        
        case NCDVALUE_MAP: {
            LinkedList1Node *n;
            while (n = LinkedList1_GetFirst(&o->map_list)) {
                struct NCDValue__map_element *e = UPPER_OBJECT(n, struct NCDValue__map_element, list_node);
                
                LinkedList1_Remove(&o->map_list, &e->list_node);
                NCDValue_Free(&e->key);
                NCDValue_Free(&e->val);
                free(e);
            }
        } break;
        
        case NCDVALUE_VAR: {
            free(o->var_name);
        } break;
        
        default:
            ASSERT(0);
    }
}

int NCDValue_Type (NCDValue *o)
{
    value_assert(o);
    
    return o->type;
}

int NCDValue_InitString (NCDValue *o, const char *str)
{
    return NCDValue_InitStringBin(o, (const uint8_t *)str, strlen(str));
}

int NCDValue_InitStringBin (NCDValue *o, const uint8_t *str, size_t len)
{
    if (len == SIZE_MAX) {
        return 0;
    }
    
    if (!(o->string = malloc(len + 1))) {
        return 0;
    }
    
    memcpy(o->string, str, len);
    o->string[len] = '\0';
    o->string_len = len;
    
    o->type = NCDVALUE_STRING;
    
    return 1;
}

const char * NCDValue_StringValue (NCDValue *o)
{
    ASSERT(o->type == NCDVALUE_STRING)
    
    return (char *)o->string;
}

size_t NCDValue_StringLength (NCDValue *o)
{
    ASSERT(o->type == NCDVALUE_STRING)
    
    return o->string_len;
}

void NCDValue_InitList (NCDValue *o)
{
    o->type = NCDVALUE_LIST;
    LinkedList1_Init(&o->list);
    o->list_count = 0;
}

size_t NCDValue_ListCount (NCDValue *o)
{
    value_assert(o);
    ASSERT(o->type == NCDVALUE_LIST)
    
    return o->list_count;
}

int NCDValue_ListAppend (NCDValue *o, NCDValue v)
{
    value_assert(o);
    ASSERT(o->type == NCDVALUE_LIST)
    value_assert(&v);
    
    if (o->list_count == SIZE_MAX) {
        return 0;
    }
    
    struct NCDValue__list_element *e = malloc(sizeof(*e));
    if (!e) {
        return 0;
    }
    
    e->v = v;
    LinkedList1_Append(&o->list, &e->list_node);
    
    o->list_count++;
    
    return 1;
}

int NCDValue_ListPrepend (NCDValue *o, NCDValue v)
{
    value_assert(o);
    ASSERT(o->type == NCDVALUE_LIST)
    value_assert(&v);
    
    if (o->list_count == SIZE_MAX) {
        return 0;
    }
    
    struct NCDValue__list_element *e = malloc(sizeof(*e));
    if (!e) {
        return 0;
    }
    
    e->v = v;
    LinkedList1_Prepend(&o->list, &e->list_node);
    
    o->list_count++;
    
    return 1;
}

NCDValue * NCDValue_ListFirst (NCDValue *o)
{
    value_assert(o);
    ASSERT(o->type == NCDVALUE_LIST)
    
    LinkedList1Node *ln = LinkedList1_GetFirst(&o->list);
    
    if (!ln) {
        return NULL;
    }
    
    struct NCDValue__list_element *e = UPPER_OBJECT(ln, struct NCDValue__list_element, list_node);
    
    return &e->v;
}

NCDValue * NCDValue_ListNext (NCDValue *o, NCDValue *ev)
{
    value_assert(o);
    ASSERT(o->type == NCDVALUE_LIST)
    
    struct NCDValue__list_element *cur_e = UPPER_OBJECT(ev, struct NCDValue__list_element, v);
    LinkedList1Node *ln = LinkedList1Node_Next(&cur_e->list_node);
    
    if (!ln) {
        return NULL;
    }
    
    struct NCDValue__list_element *e = UPPER_OBJECT(ln, struct NCDValue__list_element, list_node);
    
    return &e->v;
}

void NCDValue_InitMap (NCDValue *o)
{
    o->type = NCDVALUE_MAP;
    LinkedList1_Init(&o->map_list);
    o->map_count = 0;
}

size_t NCDValue_MapCount (NCDValue *o)
{
    value_assert(o);
    ASSERT(o->type == NCDVALUE_MAP)
    
    return o->map_count;
}

int NCDValue_MapPrepend (NCDValue *o, NCDValue key, NCDValue val)
{
    value_assert(o);
    ASSERT(o->type == NCDVALUE_MAP)
    value_assert(&key);
    value_assert(&val);
    
    if (o->map_count == SIZE_MAX) {
        return 0;
    }
    
    struct NCDValue__map_element *e = malloc(sizeof(*e));
    if (!e) {
        return 0;
    }
    
    e->key = key;
    e->val = val;
    LinkedList1_Prepend(&o->map_list, &e->list_node);
    
    o->map_count++;
    
    return 1;
}

NCDValue * NCDValue_MapFirstKey (NCDValue *o)
{
    value_assert(o);
    ASSERT(o->type == NCDVALUE_MAP)
    
    LinkedList1Node *ln = LinkedList1_GetFirst(&o->map_list);
    
    if (!ln) {
        return NULL;
    }
    
    struct NCDValue__map_element *e = UPPER_OBJECT(ln, struct NCDValue__map_element, list_node);
    
    value_assert(&e->key);
    value_assert(&e->val);
    
    return &e->key;
}

NCDValue * NCDValue_MapNextKey (NCDValue *o, NCDValue *ekey)
{
    value_assert(o);
    ASSERT(o->type == NCDVALUE_MAP)
    
    struct NCDValue__map_element *e0 = UPPER_OBJECT(ekey, struct NCDValue__map_element, key);
    value_assert(&e0->key);
    value_assert(&e0->val);
    
    LinkedList1Node *ln = LinkedList1Node_Next(&e0->list_node);
    
    if (!ln) {
        return NULL;
    }
    
    struct NCDValue__map_element *e = UPPER_OBJECT(ln, struct NCDValue__map_element, list_node);
    
    value_assert(&e->key);
    value_assert(&e->val);
    
    return &e->key;
}

NCDValue * NCDValue_MapKeyValue (NCDValue *o, NCDValue *ekey)
{
    value_assert(o);
    ASSERT(o->type == NCDVALUE_MAP)
    
    struct NCDValue__map_element *e = UPPER_OBJECT(ekey, struct NCDValue__map_element, key);
    value_assert(&e->key);
    value_assert(&e->val);
    
    return &e->val;
}

int NCDValue_InitVar (NCDValue *o, const char *var_name)
{
    ASSERT(var_name)
    
    if (!(o->var_name = strdup(var_name))) {
        return 0;
    }
    
    o->type = NCDVALUE_VAR;
    
    return 1;
}

const char * NCDValue_VarName (NCDValue *o)
{
    value_assert(o);
    ASSERT(o->type == NCDVALUE_VAR)
    
    return o->var_name;
}

void NCDProgram_Init (NCDProgram *o)
{
    LinkedList1_Init(&o->elems_list);
    o->num_elems = 0;
}

void NCDProgram_Free (NCDProgram *o)
{
    LinkedList1Node *ln;
    while (ln = LinkedList1_GetFirst(&o->elems_list)) {
        struct ProgramElem *e = UPPER_OBJECT(ln, struct ProgramElem, elems_list_node);
        NCDProgramElem_Free(&e->elem);
        LinkedList1_Remove(&o->elems_list, &e->elems_list_node);
        free(e);
    }
}

NCDProgramElem * NCDProgram_PrependElem (NCDProgram *o, NCDProgramElem elem)
{
    if (o->num_elems == SIZE_MAX) {
        return NULL;
    }
    
    struct ProgramElem *e = malloc(sizeof(*e));
    if (!e) {
        return NULL;
    }
    
    LinkedList1_Prepend(&o->elems_list, &e->elems_list_node);
    e->elem = elem;
    
    o->num_elems++;
    
    return &e->elem;
}

NCDProgramElem * NCDProgram_FirstElem (NCDProgram *o)
{
    LinkedList1Node *ln = LinkedList1_GetFirst(&o->elems_list);
    if (!ln) {
        return NULL;
    }
    
    struct ProgramElem *e = UPPER_OBJECT(ln, struct ProgramElem, elems_list_node);
    
    return &e->elem;
}

NCDProgramElem * NCDProgram_NextElem (NCDProgram *o, NCDProgramElem *ee)
{
    ASSERT(ee)
    
    struct ProgramElem *cur_e = UPPER_OBJECT(ee, struct ProgramElem, elem);
    
    LinkedList1Node *ln = LinkedList1Node_Next(&cur_e->elems_list_node);
    if (!ln) {
        return NULL;
    }
    
    struct ProgramElem *e = UPPER_OBJECT(ln, struct ProgramElem, elems_list_node);
    
    return &e->elem;
}

size_t NCDProgram_NumElems (NCDProgram *o)
{
    return o->num_elems;
}

int NCDProgram_ContainsElemType (NCDProgram *o, int elem_type)
{
    for (NCDProgramElem *elem = NCDProgram_FirstElem(o); elem; elem = NCDProgram_NextElem(o, elem)) {
        if (NCDProgramElem_Type(elem) == elem_type) {
            return 1;
        }
    }
    
    return 0;
}

void NCDProgram_RemoveElem (NCDProgram *o, NCDProgramElem *ee)
{
    ASSERT(ee)
    
    struct ProgramElem *e = UPPER_OBJECT(ee, struct ProgramElem, elem);
    NCDProgramElem_Free(&e->elem);
    LinkedList1_Remove(&o->elems_list, &e->elems_list_node);
    free(e);
    
    ASSERT(o->num_elems > 0)
    o->num_elems--;
}

int NCDProgram_ReplaceElemWithProgram (NCDProgram *o, NCDProgramElem *ee, NCDProgram replace_prog)
{
    ASSERT(ee)
    
    if (replace_prog.num_elems > SIZE_MAX - o->num_elems) {
        return 0;
    }
    
    struct ProgramElem *e = UPPER_OBJECT(ee, struct ProgramElem, elem);
    
    LinkedList1_InsertListAfter(&o->elems_list, replace_prog.elems_list, &e->elems_list_node);
    o->num_elems += replace_prog.num_elems;
    
    NCDProgram_RemoveElem(o, ee);
    
    return 1;
}

void NCDProgramElem_InitProcess (NCDProgramElem *o, NCDProcess process)
{
    o->type = NCDPROGRAMELEM_PROCESS;
    o->process = process;
}

int NCDProgramElem_InitInclude (NCDProgramElem *o, const char *path_data, size_t path_length)
{
    if (!(o->include.path_data = b_strdup_bin(path_data, path_length))) {
        return 0;
    }
    
    o->type = NCDPROGRAMELEM_INCLUDE;
    o->include.path_length = path_length;
    
    return 1;
}

int NCDProgramElem_InitIncludeGuard (NCDProgramElem *o, const char *id_data, size_t id_length)
{
    if (!(o->include_guard.id_data = b_strdup_bin(id_data, id_length))) {
        return 0;
    }
    
    o->type = NCDPROGRAMELEM_INCLUDE_GUARD;
    o->include_guard.id_length = id_length;
    
    return 1;
}


void NCDProgramElem_Free (NCDProgramElem *o)
{
    switch (o->type) {
        case NCDPROGRAMELEM_PROCESS: {
            NCDProcess_Free(&o->process);
        } break;
        
        case NCDPROGRAMELEM_INCLUDE: {
            free(o->include.path_data);
        } break;
        
        case NCDPROGRAMELEM_INCLUDE_GUARD: {
            free(o->include_guard.id_data);
        } break;
        
        default: ASSERT(0);
    }
}

int NCDProgramElem_Type (NCDProgramElem *o)
{
    return o->type;
}

NCDProcess * NCDProgramElem_Process (NCDProgramElem *o)
{
    ASSERT(o->type == NCDPROGRAMELEM_PROCESS)
    
    return &o->process;
}

const char * NCDProgramElem_IncludePathData (NCDProgramElem *o)
{
    ASSERT(o->type == NCDPROGRAMELEM_INCLUDE)
    
    return o->include.path_data;
}

size_t NCDProgramElem_IncludePathLength (NCDProgramElem *o)
{
    ASSERT(o->type == NCDPROGRAMELEM_INCLUDE)
    
    return o->include.path_length;
}

const char * NCDProgramElem_IncludeGuardIdData (NCDProgramElem *o)
{
    ASSERT(o->type == NCDPROGRAMELEM_INCLUDE_GUARD)
    
    return o->include_guard.id_data;
}

size_t NCDProgramElem_IncludeGuardIdLength (NCDProgramElem *o)
{
    ASSERT(o->type == NCDPROGRAMELEM_INCLUDE_GUARD)
    
    return o->include_guard.id_length;
}

int NCDProcess_Init (NCDProcess *o, int is_template, const char *name, NCDBlock block)
{
    ASSERT(is_template == !!is_template)
    ASSERT(name)
    
    if (!(o->name = strdup(name))) {
        return 0;
    }
    
    o->is_template = is_template;
    o->block = block;
    
    return 1;
}

void NCDProcess_Free (NCDProcess *o)
{
    NCDBlock_Free(&o->block);
    free(o->name);
}

int NCDProcess_IsTemplate (NCDProcess *o)
{
    return o->is_template;
}

const char * NCDProcess_Name (NCDProcess *o)
{
    return o->name;
}

NCDBlock * NCDProcess_Block (NCDProcess *o)
{
    return &o->block;
}

void NCDBlock_Init (NCDBlock *o)
{
    LinkedList1_Init(&o->statements_list);
    o->count = 0;
}

void NCDBlock_Free (NCDBlock *o)
{
    LinkedList1Node *ln;
    while (ln = LinkedList1_GetFirst(&o->statements_list)) {
        struct BlockStatement *e = UPPER_OBJECT(ln, struct BlockStatement, statements_list_node);
        NCDStatement_Free(&e->s);
        LinkedList1_Remove(&o->statements_list, &e->statements_list_node);
        free(e);
    }
}

int NCDBlock_PrependStatement (NCDBlock *o, NCDStatement s)
{
    return NCDBlock_InsertStatementAfter(o, NULL, s);
}

int NCDBlock_InsertStatementAfter (NCDBlock *o, NCDStatement *after, NCDStatement s)
{
    struct BlockStatement *after_e = NULL;
    if (after) {
        after_e = UPPER_OBJECT(after, struct BlockStatement, s);
    }
    
    if (o->count == SIZE_MAX) {
        return 0;
    }
    
    struct BlockStatement *e = malloc(sizeof(*e));
    if (!e) {
        return 0;
    }
    
    if (after_e) {
        LinkedList1_InsertAfter(&o->statements_list, &e->statements_list_node, &after_e->statements_list_node);
    } else {
        LinkedList1_Prepend(&o->statements_list, &e->statements_list_node);
    }
    e->s = s;
    
    o->count++;
    
    return 1;
}

NCDStatement * NCDBlock_ReplaceStatement (NCDBlock *o, NCDStatement *es, NCDStatement s)
{
    ASSERT(es)
    
    struct BlockStatement *e = UPPER_OBJECT(es, struct BlockStatement, s);
    
    NCDStatement_Free(&e->s);
    e->s = s;
    
    return &e->s;
}

NCDStatement * NCDBlock_FirstStatement (NCDBlock *o)
{
    LinkedList1Node *ln = LinkedList1_GetFirst(&o->statements_list);
    if (!ln) {
        return NULL;
    }
    
    struct BlockStatement *e = UPPER_OBJECT(ln, struct BlockStatement, statements_list_node);
    
    return &e->s;
}

NCDStatement * NCDBlock_NextStatement (NCDBlock *o, NCDStatement *es)
{
    ASSERT(es)
    
    struct BlockStatement *cur_e = UPPER_OBJECT(es, struct BlockStatement, s);
    
    LinkedList1Node *ln = LinkedList1Node_Next(&cur_e->statements_list_node);
    if (!ln) {
        return NULL;
    }
    
    struct BlockStatement *e = UPPER_OBJECT(ln, struct BlockStatement, statements_list_node);
    
    return &e->s;
}

size_t NCDBlock_NumStatements (NCDBlock *o)
{
    return o->count;
}

int NCDStatement_InitReg (NCDStatement *o, const char *name, const char *objname, const char *cmdname, NCDValue args)
{
    ASSERT(cmdname)
    ASSERT(NCDValue_Type(&args) == NCDVALUE_LIST)
    
    o->name = NULL;
    o->reg.objname = NULL;
    o->reg.cmdname = NULL;
    
    if (name && !(o->name = strdup(name))) {
        goto fail;
    }
    
    if (objname && !(o->reg.objname = strdup(objname))) {
        goto fail;
    }
    
    if (!(o->reg.cmdname = strdup(cmdname))) {
        goto fail;
    }
    
    o->type = NCDSTATEMENT_REG;
    o->reg.args = args;
    
    return 1;
    
fail:
    free(o->name);
    free(o->reg.objname);
    free(o->reg.cmdname);
    return 0;
}

int NCDStatement_InitIf (NCDStatement *o, const char *name, NCDIfBlock ifblock)
{
    o->name = NULL;
    
    if (name && !(o->name = strdup(name))) {
        return 0;
    }
    
    o->type = NCDSTATEMENT_IF;
    o->ifc.ifblock = ifblock;
    o->ifc.have_else = 0;
    
    return 1;
}

int NCDStatement_InitForeach (NCDStatement *o, const char *name, NCDValue collection, const char *name1, const char *name2, NCDBlock block)
{
    ASSERT(name1)
    
    o->name = NULL;
    o->foreach.name1 = NULL;
    o->foreach.name2 = NULL;
    
    if (name && !(o->name = strdup(name))) {
        goto fail;
    }
    
    if (!(o->foreach.name1 = strdup(name1))) {
        goto fail;
    }
    
    if (name2 && !(o->foreach.name2 = strdup(name2))) {
        goto fail;
    }
    
    o->type = NCDSTATEMENT_FOREACH;
    o->foreach.collection = collection;
    o->foreach.block = block;
    o->foreach.is_grabbed = 0;
    
    return 1;
    
fail:
    free(o->name);
    free(o->foreach.name1);
    free(o->foreach.name2);
    return 0;
}

void NCDStatement_Free (NCDStatement *o)
{
    switch (o->type) {
        case NCDSTATEMENT_REG: {
            NCDValue_Free(&o->reg.args);
            free(o->reg.cmdname);
            free(o->reg.objname);
        } break;
        
        case NCDSTATEMENT_IF: {
            if (o->ifc.have_else) {
                NCDBlock_Free(&o->ifc.else_block);
            }
            
            NCDIfBlock_Free(&o->ifc.ifblock);
        } break;
        
        case NCDSTATEMENT_FOREACH: {
            if (!o->foreach.is_grabbed) {
                NCDBlock_Free(&o->foreach.block);
                NCDValue_Free(&o->foreach.collection);
            }
            free(o->foreach.name2);
            free(o->foreach.name1);
        } break;
        
        default: ASSERT(0);
    }
    
    free(o->name);
}

int NCDStatement_Type (NCDStatement *o)
{
    return o->type;
}

const char * NCDStatement_Name (NCDStatement *o)
{
    return o->name;
}

const char * NCDStatement_RegObjName (NCDStatement *o)
{
    ASSERT(o->type == NCDSTATEMENT_REG)
    
    return o->reg.objname;
}

const char * NCDStatement_RegCmdName (NCDStatement *o)
{
    ASSERT(o->type == NCDSTATEMENT_REG)
    
    return o->reg.cmdname;
}

NCDValue * NCDStatement_RegArgs (NCDStatement *o)
{
    ASSERT(o->type == NCDSTATEMENT_REG)
    
    return &o->reg.args;
}

NCDIfBlock * NCDStatement_IfBlock (NCDStatement *o)
{
    ASSERT(o->type == NCDSTATEMENT_IF)
    
    return &o->ifc.ifblock;
}

void NCDStatement_IfAddElse (NCDStatement *o, NCDBlock else_block)
{
    ASSERT(o->type == NCDSTATEMENT_IF)
    ASSERT(!o->ifc.have_else)
    
    o->ifc.have_else = 1;
    o->ifc.else_block = else_block;
}

NCDBlock * NCDStatement_IfElse (NCDStatement *o)
{
    ASSERT(o->type == NCDSTATEMENT_IF)
    
    if (!o->ifc.have_else) {
        return NULL;
    }
    
    return &o->ifc.else_block;
}

NCDBlock NCDStatement_IfGrabElse (NCDStatement *o)
{
    ASSERT(o->type == NCDSTATEMENT_IF)
    ASSERT(o->ifc.have_else)
    
    o->ifc.have_else = 0;
    
    return o->ifc.else_block;
}

NCDValue * NCDStatement_ForeachCollection (NCDStatement *o)
{
    ASSERT(o->type == NCDSTATEMENT_FOREACH)
    ASSERT(!o->foreach.is_grabbed)
    
    return &o->foreach.collection;
}

const char * NCDStatement_ForeachName1 (NCDStatement *o)
{
    ASSERT(o->type == NCDSTATEMENT_FOREACH)
    
    return o->foreach.name1;
}

const char * NCDStatement_ForeachName2 (NCDStatement *o)
{
    ASSERT(o->type == NCDSTATEMENT_FOREACH)
    
    return o->foreach.name2;
}

NCDBlock * NCDStatement_ForeachBlock (NCDStatement *o)
{
    ASSERT(o->type == NCDSTATEMENT_FOREACH)
    ASSERT(!o->foreach.is_grabbed)
    
    return &o->foreach.block;
}

void NCDStatement_ForeachGrab (NCDStatement *o, NCDValue *out_collection, NCDBlock *out_block)
{
    ASSERT(o->type == NCDSTATEMENT_FOREACH)
    ASSERT(!o->foreach.is_grabbed)
    
    *out_collection = o->foreach.collection;
    *out_block = o->foreach.block;
    o->foreach.is_grabbed = 1;
}

void NCDIfBlock_Init (NCDIfBlock *o)
{
    LinkedList1_Init(&o->ifs_list);
}

void NCDIfBlock_Free (NCDIfBlock *o)
{
    LinkedList1Node *ln;
    while (ln = LinkedList1_GetFirst(&o->ifs_list)) {
        struct IfBlockIf *e = UPPER_OBJECT(ln, struct IfBlockIf, ifs_list_node);
        NCDIf_Free(&e->ifc);
        LinkedList1_Remove(&o->ifs_list, &e->ifs_list_node);
        free(e);
    }
}

int NCDIfBlock_PrependIf (NCDIfBlock *o, NCDIf ifc)
{
    struct IfBlockIf *e = malloc(sizeof(*e));
    if (!e) {
        return 0;
    }
    
    LinkedList1_Prepend(&o->ifs_list, &e->ifs_list_node);
    e->ifc = ifc;
    
    return 1;
}

NCDIf * NCDIfBlock_FirstIf (NCDIfBlock *o)
{
    LinkedList1Node *ln = LinkedList1_GetFirst(&o->ifs_list);
    if (!ln) {
        return NULL;
    }
    
    struct IfBlockIf *e = UPPER_OBJECT(ln, struct IfBlockIf, ifs_list_node);
    
    return &e->ifc;
}

NCDIf * NCDIfBlock_NextIf (NCDIfBlock *o, NCDIf *ei)
{
    ASSERT(ei)
    
    struct IfBlockIf *cur_e = UPPER_OBJECT(ei, struct IfBlockIf, ifc);
    
    LinkedList1Node *ln = LinkedList1Node_Next(&cur_e->ifs_list_node);
    if (!ln) {
        return NULL;
    }
    
    struct IfBlockIf *e = UPPER_OBJECT(ln, struct IfBlockIf, ifs_list_node);
    
    return &e->ifc;
}

NCDIf NCDIfBlock_GrabIf (NCDIfBlock *o, NCDIf *ei)
{
    ASSERT(ei)
    
    struct IfBlockIf *e = UPPER_OBJECT(ei, struct IfBlockIf, ifc);
    
    NCDIf old_ifc = e->ifc;
    
    LinkedList1_Remove(&o->ifs_list, &e->ifs_list_node);
    free(e);
    
    return old_ifc;
}

void NCDIf_Init (NCDIf *o, NCDValue cond, NCDBlock block)
{
    o->cond = cond;
    o->block = block;
}

void NCDIf_Free (NCDIf *o)
{
    NCDValue_Free(&o->cond);
    NCDBlock_Free(&o->block);
}

void NCDIf_FreeGrab (NCDIf *o, NCDValue *out_cond, NCDBlock *out_block)
{
    *out_cond = o->cond;
    *out_block = o->block;
}

NCDValue * NCDIf_Cond (NCDIf *o)
{
    return &o->cond;
}

NCDBlock * NCDIf_Block (NCDIf *o)
{
    return &o->block;
}
