/******************************************************************************
 * mod_uploader / TemplateExecutorr.cpp
 ******************************************************************************
 * Copyright (C) 2005 Tetsuya Kimata <kimata@acapulco.dyndns.org>
 *
 * All rights reserved.
 *
 * This software is provided 'as-is', without any express or implied
 * warranty.  In no event will the authors be held liable for any
 * damages arising from the use of this software.
 *
 * Permission is granted to anyone to use this software for any
 * purpose, including commercial applications, and to alter it and
 * redistribute it freely, subject to the following restrictions:
 *
 * 1. The origin of this software must not be misrepresented; you must
 *    not claim that you wrote the original software. If you use this
 *    software in a product, an acknowledgment in the product
 *    documentation would be appreciated but is not required.
 *
 * 2. Altered source versions must be plainly marked as such, and must
 *    not be misrepresented as being the original software.
 *
 * 3. This notice may not be removed or altered from any source
 *    distribution.
 *
 * $Id: TemplateExecutor.cpp 535 2005-08-30 20:15:17Z svn $
 *****************************************************************************/

#include "TemplateExecutor.h"
#include "Misc.h"

#include "http_protocol.h"
#include "apr_strings.h"

#ifdef DEBUG
#include <iostream>
#endif

#ifdef DEBUG
#define UNEXPECTED(value) throw "おそらくバグです．" // :-<
#else
#define UNEXPECTED(value) return value
#endif

#ifdef DEBUG_TemplateExecutor
#define TRACE(label) cerr << "call: " << label << endl;
#else
#define TRACE(label)
#endif

const apr_size_t TemplateExecutor::VARIABLE_POOL_NUM    = TEXECUTOR_VARIABLE_POOL_NUM;
const apr_size_t TemplateExecutor::SCALAR_POOL_NUM      = TEXECUTOR_SCALAR_POOL_NUM;


/******************************************************************************
 * public メソッド
 *****************************************************************************/
TemplateExecutor::TemplateExecutor()
{

}

void TemplateExecutor::exec(apr_pool_t *pool, request_rec *request,
                            node *node, ident_map *imap, variable_map *vmap)
{
    TRACE("exec");

    handle h(pool, request, imap, vmap);

    exec_stmt(&h, node);
}

apr_size_t TemplateExecutor::get_ident_id(ident_map *imap, const char *name)
{
    apr_size_t id;

    for (id = 0; id < imap->size(); id++) {
        if (strcmp(imap->at(id), name) == 0) {
            return id;
        }
    }

    return id;
}


/******************************************************************************
 * private メソッド
 *****************************************************************************/
void TemplateExecutor::exec_stmt(handle *h, node *node)
{
    TRACE("exec_stmt");

    while (node != NULL) {
        switch (node->branch.left->type) {
        case TemplateParser::FOREACH:       // FOREACH
            exec_foreach(h, node->branch.left);
            break;
        case TemplateParser::WHILE:         // WHILE
            exec_while(h, node->branch.left);
            break;
        case TemplateParser::IF:            // IF
            exec_if(h, node->branch.left);
            break;
        case TemplateParser::PRINT:         // PRINT
            exec_print(h, node->branch.left);
            break;
        default:
            calc_i_val(h, node->branch.left);
            break;
        }

        node = node->branch.right;
    }
}

void TemplateExecutor::exec_foreach(handle *h, node *node)
{
    TRACE("exec_foreach");

    variable *array_var = h->vmap->at(node->branch.center->i_val);
    apr_size_t index_id = node->branch.left->i_val;

    if (UNLIKELY(array_var == NULL)) {
        throw "初期化されていない変数を参照しました．";
    } else if (UNLIKELY(array_var->type != ARRAY)) {
        throw "配列が必要な場所にその他の変数が指定されています．";
    }

    apr_size_t array_size = array_var->a_val->nelts;
    for (apr_size_t i = 0; i < array_size; i++) {
        h->vmap->at(index_id) = *(reinterpret_cast<variable **>(array_var->a_val->elts) + i);

        exec_stmt(h, node->branch.right);
    }
}

void TemplateExecutor::exec_while(handle *h, node *node)
{
    TRACE("exec_while");

    while (calc_b_val(h, node->branch.left)) {
        exec_stmt(h, node->branch.right);
    }
}

void TemplateExecutor::exec_if(handle *h, node *node)
{
    TRACE("exec_if");

    if (calc_b_val(h, node->branch.center)) {
        exec_stmt(h, node->branch.left);
    } else if (node->branch.right != NULL) {
        exec_stmt(h, node->branch.right);
    }
}

void TemplateExecutor::exec_print(handle *h, node *node)
{
    TRACE("exec_print");

    variable *var;
    node = node->branch.left;

    while (node != NULL) {
        switch (node->branch.left->type) {
        case TemplateParser::IDENTIFIER:    // 識別子
            var = h->vmap->at(node->branch.left->i_val);
            goto VAR;
        case TemplateParser::ARRAY_REF:     // []
            var = get_var(h, node->branch.left);
            goto VAR;
        case TemplateParser::HASH_REF:      // .
            var = get_var(h, node->branch.left);
            goto VAR;
        VAR:
            if (UNLIKELY(var == NULL)) {
                throw "初期化されていない変数を参照しました．";
            }

            if ((var->type == SCALAR) && (var->s_val->type == STRING)) {
                print(h, var->s_val->s_val);
            } else {
                print(h, calc_i_val(var));
            }
            break;
        case TemplateParser::STRING:        // 文字列
            print(h, node->branch.left->s_val);
            break;
        default:
            print(h, calc_i_val(h, node->branch.left));
            break;
        }

        node = node->branch.right;
    }
}

bool TemplateExecutor::calc_b_val(handle *h, node *node)
{
    TRACE("calc_b_val");

    switch (node->type) {
    case TemplateParser::EQUAL:
        return calc_i_val(h, node->branch.left) == calc_i_val(h, node->branch.right);
    case TemplateParser::NOTEQUAL:
        return calc_i_val(h, node->branch.left) != calc_i_val(h, node->branch.right);
    case TemplateParser::GREATER_THAN:
        return calc_i_val(h, node->branch.left) > calc_i_val(h, node->branch.right);
    case TemplateParser::LESS_THAN:
        return calc_i_val(h, node->branch.left) < calc_i_val(h, node->branch.right);
    default:
        UNEXPECTED(false);
    }
}

int TemplateExecutor::calc_i_val(handle *h, node *node)
{
    TRACE("calc_i_val");

    int value = 0;
    int diff;
    apr_size_t ident_id;
    variable *var = NULL;
    variable *ref_var;
    scalar *s_val;

    switch (node->type) {
    case TemplateParser::STRING:            // STRING
        return static_cast<int>(strlen(node->s_val));
    case TemplateParser::IDENTIFIER:        // IDENTIFIER
        var = h->vmap->at(node->i_val);

        if (UNLIKELY(var == NULL)) {
            throw "初期化されていない変数を参照しました．";
        }

        return calc_i_val(var);
    case TemplateParser::INTEGER:           // INTEGR
        return node->i_val;
    case TemplateParser::ASSIGNMENT:        // =
        ident_id = node->branch.left->i_val;

        switch (node->branch.right->type) {
        case TemplateParser::IDENTIFIER:    // 識別子
        case TemplateParser::ARRAY_REF:     // []
        case TemplateParser::HASH_REF:      // .
            ref_var = get_var(h, node->branch.right);
            if (UNLIKELY(ref_var == NULL)) {
                throw "初期化されていない変数を参照しました．";
            }

            if ((ref_var->type == SCALAR) && (ref_var->s_val->type == INTEGER)) {
                var = h->vmap->at(ident_id);
                if (var == NULL) {
                    var = create_variable(h, SCALAR);
                    var->s_val = create_scalar(h, INTEGER);

                    h->vmap->at(ident_id) = var;
                } else {
                    var->type = SCALAR;
                    var->s_val->type = INTEGER;
                }

                var->s_val->i_val = ref_var->s_val->i_val;
            } else { // 数値以外はコピーしない
                h->vmap->at(ident_id) = ref_var;
            }

            return calc_i_val(var);
        case TemplateParser::STRING:        // 文字列
            var = h->vmap->at(ident_id);
            if (var == NULL) {
                var = create_variable(h, SCALAR);
                var->s_val = create_scalar(h, STRING);

                // コピーはしない
                var->s_val->s_val = node->branch.right->s_val;

                h->vmap->at(ident_id) = var;
            } else {
                var->type = SCALAR;

                if (var->s_val == NULL) {
                    var->s_val = create_scalar(h, STRING);
                } else {
                    var->s_val->type = STRING;
                }

                // コピーはしない
                var->s_val->s_val = node->branch.right->s_val;
            }

            return calc_i_val(var);
        default:                            // 数値
            var = h->vmap->at(ident_id);
            if (var == NULL) {
                var = create_variable(h, SCALAR);
                var->s_val = create_scalar(h, INTEGER);
                var->s_val->i_val = calc_i_val(h, node->branch.right);

                h->vmap->at(ident_id) = var;
            } else {
                var->type = SCALAR;

                if (var->s_val == NULL) {
                    var->s_val = create_scalar(h, INTEGER);
                } else {
                    var->s_val->type = INTEGER;
                }
                var->s_val->i_val = calc_i_val(h, node->branch.right);
            }
            return calc_i_val(var);
        }
    case TemplateParser::PLUS_ASSIGNMENT:   // +=
        diff = calc_i_val(h, node->branch.right);
        goto ASSIGN2;
    case TemplateParser::MINUS_ASSIGNMENT:  // -=
        diff = -calc_i_val(h, node->branch.right);
        goto ASSIGN2;
    ASSIGN2:
        ident_id = node->branch.left->i_val;
        var = h->vmap->at(ident_id);

        if (var == NULL) {
            var = create_variable(h, SCALAR);
            var->s_val = create_scalar(h, INTEGER);
            var->s_val->i_val = diff;

            h->vmap->at(ident_id) = var;

            return var->s_val->i_val;
        } else {
            var->type = SCALAR;
            var->s_val->type = INTEGER;
            var->s_val->i_val = calc_i_val(var) + diff;

            return var->s_val->i_val;
        }
    case TemplateParser::PLUSPLUS:          // ++
        diff = 1;
        goto MONADIC;
    case TemplateParser::MINUSMINUS:        // --
        diff = -1;
        goto MONADIC;
    MONADIC:
        if (node->branch.left != NULL) {
            ident_id = node->branch.left->i_val;
            var = h->vmap->at(ident_id);

            if (UNLIKELY(var == NULL)) {
                throw "初期化されていない変数を参照しました．";
            } else if (UNLIKELY(var->type != SCALAR)) {
                throw "SCALAR が必要な場所でそれ以外の変数が見つかりました．";
            }

            var->type = SCALAR;

            s_val = var->s_val;
            s_val->type = INTEGER;
            s_val->i_val = calc_i_val(var);
            value = s_val->i_val;
            s_val->i_val += diff;
        } else if (node->branch.right != NULL) {
            ident_id = node->branch.right->i_val;
            var = h->vmap->at(ident_id);

            if (UNLIKELY(var == NULL)) {
                throw "初期化されていない変数を参照しました．";
            } else if (UNLIKELY(var->type != SCALAR)) {
                throw "SCALAR が必要な場所でそれ以外の変数が見つかりました．";
            }

            var->type = SCALAR;

            s_val = var->s_val;
            s_val->type = INTEGER;
            s_val->i_val = calc_i_val(var);
            s_val->i_val += diff;
            value = s_val->i_val;
        } else {
            UNEXPECTED(0);
        }
        return value;
    case TemplateParser::PLUS:              // +
        return calc_i_val(h, node->branch.left) + calc_i_val(h, node->branch.right);
    case TemplateParser::MINUS:             // -
        return calc_i_val(h, node->branch.left) - calc_i_val(h, node->branch.right);
    case TemplateParser::RESIDUE:           // %
        return calc_i_val(h, node->branch.left) % calc_i_val(h, node->branch.right);
    default:
        UNEXPECTED(0);
    }
}

int TemplateExecutor::calc_i_val(TemplateExecutor::variable* var)
{
    switch (var->type) {
    case SCALAR:
        if (var->s_val->type == INTEGER) {
            return var->s_val->i_val;
        } else {
            return static_cast<int>(strlen(var->s_val->s_val));
        }
    case ARRAY:
        return var->a_val->nelts;
    case HASH:
        return apr_hash_count(var->h_val);
    default:
        UNEXPECTED(0);
    }
}

TemplateExecutor::variable *TemplateExecutor::get_var(handle *h, node *node)
{
    // 連想配列まわりの仕様はびみょー．

    TRACE("get_var");

    variable *var = NULL;
    const char *key;

    switch (node->type) {
    case TemplateParser::IDENTIFIER:    // IDENTIFIER
        var = h->vmap->at(node->i_val);
        break;
    case TemplateParser::ARRAY_REF:     // []
        var = h->vmap->at(node->branch.left->i_val);
        if (var == NULL) {
            break;
        } else if (UNLIKELY(var->type != ARRAY)) {
            throw "配列が必要な場所にその他の変数が指定されています．";
        }

        if (node->branch.right->type == TemplateParser::INTEGER) { // foo[var]
            var = *(reinterpret_cast<variable **>(var->a_val->elts) +
                    calc_i_val(h, node->branch.right));
        } else if (node->branch.right->type == TemplateParser::HASH_REF) { // foo[var].baz
            var = *(reinterpret_cast<variable **>(var->a_val->elts) +
                    calc_i_val(h, node->branch.right->branch.left));

            if (var == NULL) {
                break;
            } else if (UNLIKELY(var->type != HASH)) {
                throw "連想配列が必要な場所にその他の変数が指定されています．";
            }
            key = node->branch.right->branch.right->s_val;
            var = static_cast<variable *>(apr_hash_get(var->h_val, key,
                                                            APR_HASH_KEY_STRING));
        }
        break;
    case TemplateParser::HASH_REF:      // .
        var = h->vmap->at(node->branch.left->i_val);
        if (var == NULL) {
            break;
        } else if (UNLIKELY(var->type != HASH)) {
            throw "連想配列が必要な場所にその他の変数が指定されています．";
        }

        key = h->imap->at(node->branch.right->i_val);

        var = static_cast<variable *>(apr_hash_get(var->h_val, key,
                                                        APR_HASH_KEY_STRING));
        break;
    default:
        if (UNLIKELY(var == NULL)) {
            throw "バグです．";
        }
    }

    if (UNLIKELY(var == NULL)) {
        throw "初期化されていない変数を参照しました．";
    }

    return var;
}

TemplateExecutor::variable *TemplateExecutor::create_variable(handle *h,
                                                                     variable_type type)
{
    if (h->variable_pool == h->variable_pool_end) {
        APR_PALLOC(h->variable_pool, variable *, h->pool, sizeof(variable)*VARIABLE_POOL_NUM);

        h->variable_pool_end = h->variable_pool + VARIABLE_POOL_NUM;
    }

    variable *variable = h->variable_pool++;
    variable->type = type;

    return variable;
}

TemplateExecutor::scalar *TemplateExecutor::create_scalar(handle *h,
                                                                 scalar_type type)
{
    if (h->scalar_pool == h->scalar_pool_end) {
        APR_PALLOC(h->scalar_pool, scalar *, h->pool, sizeof(scalar)*SCALAR_POOL_NUM);

        h->scalar_pool_end = h->scalar_pool + SCALAR_POOL_NUM;
    }

    scalar *scalar = h->scalar_pool++;
    scalar->type = type;

    return scalar;
}

void TemplateExecutor::print(handle *h, int i_val)
{
#ifdef DEBUG_TEMPLATE
    cout << i_val;
#else
    ap_rputs(apr_itoa(h->pool, i_val), h->request);
#endif
}

void TemplateExecutor::print(handle *h, const char *s_val)
{
#ifdef DEBUG_TEMPLATE
    cout << s_val;
#else
    ap_rputs(s_val, h->request);
#endif
}

/******************************************************************************
 * テスト
 *****************************************************************************/
#ifdef DEBUG_TemplateExecutor
#include "apr_general.h"
#include "apr_file_io.h"
#include "apr_mmap.h"

#undef PACKAGE_BUGREPORT
#undef PACKAGE_NAME
#undef PACKAGE_STRING
#undef PACKAGE_TARNAME
#undef PACKAGE_VERSION

#include "UploadItemList.h"
#include "mod_uploader.h"

const apr_size_t TOTAL_NUMBER_LIMIT = 100;
const apr_size_t TOTAL_SIZE_LIMIT   = 1024*1024*1024;

void usage(const char *prog_name)
{
    cerr << "Usage: " << prog_name << " <DIRECTORY> <TEMPLATE>" << endl;
}

int main(int argc, const char * const *argv)
{
    apr_pool_t *pool;
    apr_file_t *fd;
    apr_mmap_t *file_map;
    apr_finfo_t info;
    TemplateParser::node *node;
    apr_size_t ident_id;

    apr_app_initialize(&argc, &argv, NULL);
    apr_pool_create(&pool, NULL);

    try {
        if (argc != 3) {
            throw "ファイル用ディレクトリと入力用のファイルが指定されていません．";
        }

        if (apr_file_open(&fd, argv[2], APR_READ,
                          APR_OS_DEFAULT, pool) != APR_SUCCESS) {
            throw "ファイルの open に失敗しました．";
        }

        if (apr_file_info_get(&info, APR_FINFO_SIZE, fd) != APR_SUCCESS) {
            throw "ファイルサイズを取得できませんでした．";
        }

        if (apr_mmap_create(&file_map, fd, 0,
                            static_cast<apr_size_t>(info.size), APR_MMAP_READ,
                            pool) != APR_SUCCESS) {
            throw "ファイルの mmap に失敗しました．";
        }

        TemplateLexer lexer(pool,
                            static_cast<const char *>(file_map->mm),
                            static_cast<apr_size_t>(info.size));
        TemplateParser parser(pool);
        node = parser.parse(lexer.get_token_list(), lexer.get_ident_map());

        TemplateExecutor executor;
        TemplateExecutor::variable_map vmap(lexer.get_ident_map()->size()+SYSTEM_KEY_NUMBER, NULL);

        auto_ptr<UploadItemList> item_list(UploadItemList::load(pool, argv[1], argv[1],
                                                                TOTAL_NUMBER_LIMIT, TOTAL_SIZE_LIMIT));
        ident_id = TemplateExecutor::get_ident_id(lexer.get_ident_map(), FILE_LIST_KEY);
        vmap.at(ident_id) = item_list->to_varray(pool, 0, item_list->size());

        executor.exec(pool, NULL, node, lexer.get_ident_map(), &vmap);

        if (apr_mmap_delete(file_map) != APR_SUCCESS) {
            throw "ファイルの unmap に失敗しました．";
        }

        if (apr_file_close(fd) != APR_SUCCESS) {
            throw "ファイルの close に失敗しました．";
        }
    } catch(const char *message) {
        cerr << "Error: " << message << endl;
        usage(argv[0]);

        return EXIT_FAILURE;
    }

    apr_terminate();

    return EXIT_SUCCESS;

}
#endif

// Local Variables:
// mode: c++
// buffer-file-coding-system: euc-japan-dos
// End:
