/*
 * Copyright 2008-2018 Aerospike, Inc.
 *
 * Portions may be licensed to Aerospike, Inc. under one or more contributor
 * license agreements.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */
#include "../test.h"
#include <aerospike/as_stream.h>
#include <aerospike/as_types.h>
#include <limits.h>
#include <stdlib.h>
#include <string.h>

#include <aerospike/as_module.h>
#include <aerospike/mod_lua.h>
#include <aerospike/mod_lua_config.h>

#include "../util/test_aerospike.h"
#include "../util/test_logger.h"
#include "../util/map_rec.h"
#include "../util/producer_stream.h"
#include "../util/consumer_stream.h"

/******************************************************************************
 * TEST CASES
 *****************************************************************************/

static uint32_t limit = 0;
static uint32_t produced = 0;
static uint32_t consumed = 0;

static as_val *produce1()
{
    if (produced >= limit) {
        return AS_STREAM_END;
    }

    produced++;

    return (as_val *)as_integer_new(produced);
}

static as_stream_status consume1(as_val *v)
{
    if (v != AS_STREAM_END) {
        consumed++;
    }

    as_val_destroy(v);

    return AS_STREAM_OK;
}

TEST(stream_udf_1, "filter even numbers from range (1-10)")
{

    limit = 10;
    produced = 0;
    consumed = 0;

    as_stream *istream = producer_stream_new(produce1);
    as_stream *ostream = consumer_stream_new(consume1);
    as_list *arglist = NULL;

    int rc = as_module_apply_stream(&mod_lua, &ctx, "aggr", "even", istream,
                                    arglist, ostream, NULL);

    assert_int_eq(rc, 0);
    assert_int_eq(produced, limit);
    assert_int_eq(consumed, produced / 2);

    as_stream_destroy(istream);
    as_stream_destroy(ostream);
}

TEST(stream_udf_2, "increment range (1-10)")
{

    limit = 10;
    produced = 0;
    consumed = 0;

    as_stream *istream = producer_stream_new(produce1);
    as_stream *ostream = consumer_stream_new(consume1);
    as_list *arglist = NULL;

    int rc = as_module_apply_stream(&mod_lua, &ctx, "aggr", "increment",
                                    istream, arglist, ostream, NULL);

    assert_int_eq(rc, 0);
    assert_int_eq(produced, limit);
    assert_int_eq(consumed, produced);

    as_stream_destroy(istream);
    as_stream_destroy(ostream);
}

static as_integer *result3 = NULL;

static as_val *produce3()
{
    if (produced >= limit) {
        return AS_STREAM_END;
    }

    produced++;

    return (as_val *)as_integer_new(produced);
}

static as_stream_status consume3(as_val *v)
{
    if (v != AS_STREAM_END) {
        consumed++;
        result3 = (as_integer *)v;
    }

    return AS_STREAM_OK;
}

TEST(stream_udf_3, "sum range (1-1,000,000)")
{
    limit = 1000 * 1000;
    produced = 0;
    consumed = 0;

    result3 = NULL;

    as_stream *istream = producer_stream_new(produce3);
    as_stream *ostream = consumer_stream_new(consume3);
    as_list *arglist = NULL;

    int rc = as_module_apply_stream(&mod_lua, &ctx, "aggr", "sum", istream,
                                    arglist, ostream, NULL);

    uint64_t result = (uint64_t)as_integer_get(result3);

    assert_int_eq(rc, 0);
    assert_int_eq(produced, limit);
    assert_int_eq(consumed, 1);
    assert_int_eq(result, 500000500000);

    as_integer_destroy(result3);
    as_stream_destroy(istream);
    as_stream_destroy(ostream);
}

TEST(stream_udf_4, "product range (1-10)")
{

    limit = 10;
    produced = 0;
    consumed = 0;

    result3 = NULL;

    as_stream *istream = producer_stream_new(produce3);
    as_stream *ostream = consumer_stream_new(consume3);
    as_list *arglist = NULL;

    int rc = as_module_apply_stream(&mod_lua, &ctx, "aggr", "product", istream,
                                    arglist, ostream, NULL);

    assert_int_eq(rc, 0);
    assert_int_eq(produced, limit);
    assert_int_eq(consumed, 1);
    assert_int_eq(as_integer_get(result3), 3628800);

    as_integer_destroy(result3);
    as_stream_destroy(istream);
    as_stream_destroy(ostream);
}

static as_map *result5 = NULL;

static as_val *produce5()
{
    if (produced >= limit) {
        return AS_STREAM_END;
    }

    produced++;

    as_rec *rec = map_rec_new();
    as_rec_set(rec, "id", (as_val *)as_integer_new(produced));
    as_rec_set(rec, "campaign", (as_val *)as_integer_new(produced % 10));
    as_rec_set(rec, "views", (as_val *)as_integer_new(produced * 2919 % 1000));

    return (as_val *)rec;
}

static as_stream_status consume5(as_val *v)
{
    if (v != AS_STREAM_END) {
        consumed++;
        result5 = (as_map *)v;
    }

    return AS_STREAM_OK;
}

TEST(stream_udf_5, "campaign rollup w/ map & reduce")
{
    limit = 100;
    produced = 0;
    consumed = 0;

    result5 = NULL;

    as_stream *istream = producer_stream_new(produce5);
    as_stream *ostream = consumer_stream_new(consume5);
    as_list *arglist = (as_list *)as_arraylist_new(0, 0);

    int rc = as_module_apply_stream(&mod_lua, &ctx, "aggr", "rollup", istream,
                                    arglist, ostream, NULL);

    assert_int_eq(rc, 0);
    assert_int_eq(produced, limit);
    assert_int_eq(consumed, 1);

    as_integer i;

    assert_int_eq(as_integer_get((as_integer *)as_map_get(
                      result5, (as_val *)as_integer_init(&i, 0))),
                  5450);
    assert_int_eq(as_integer_get((as_integer *)as_map_get(
                      result5, (as_val *)as_integer_init(&i, 1))),
                  4740);
    assert_int_eq(as_integer_get((as_integer *)as_map_get(
                      result5, (as_val *)as_integer_init(&i, 2))),
                  4930);
    assert_int_eq(as_integer_get((as_integer *)as_map_get(
                      result5, (as_val *)as_integer_init(&i, 3))),
                  5120);
    assert_int_eq(as_integer_get((as_integer *)as_map_get(
                      result5, (as_val *)as_integer_init(&i, 4))),
                  4310);
    assert_int_eq(as_integer_get((as_integer *)as_map_get(
                      result5, (as_val *)as_integer_init(&i, 5))),
                  5500);
    assert_int_eq(as_integer_get((as_integer *)as_map_get(
                      result5, (as_val *)as_integer_init(&i, 6))),
                  4690);
    assert_int_eq(as_integer_get((as_integer *)as_map_get(
                      result5, (as_val *)as_integer_init(&i, 7))),
                  4880);
    assert_int_eq(as_integer_get((as_integer *)as_map_get(
                      result5, (as_val *)as_integer_init(&i, 8))),
                  5070);
    assert_int_eq(as_integer_get((as_integer *)as_map_get(
                      result5, (as_val *)as_integer_init(&i, 9))),
                  5260);

    as_list_destroy(arglist);
    as_map_destroy(result5);
    as_stream_destroy(istream);
    as_stream_destroy(ostream);
}

TEST(stream_udf_6, "campaign rollup w/ aggregate")
{
    limit = 100;
    produced = 0;
    consumed = 0;

    result5 = NULL;

    as_stream *istream = producer_stream_new(produce5);
    as_stream *ostream = consumer_stream_new(consume5);
    as_list *arglist = (as_list *)as_arraylist_new(0, 0);

    int rc = as_module_apply_stream(&mod_lua, &ctx, "aggr", "rollup2", istream,
                                    arglist, ostream, NULL);

    assert_int_eq(rc, 0);
    assert_int_eq(produced, limit);
    assert_int_eq(consumed, 1);

    as_integer i;

    assert_int_eq(as_integer_get((as_integer *)as_map_get(
                      result5, (as_val *)as_integer_init(&i, 0))),
                  5450);
    assert_int_eq(as_integer_get((as_integer *)as_map_get(
                      result5, (as_val *)as_integer_init(&i, 1))),
                  4740);
    assert_int_eq(as_integer_get((as_integer *)as_map_get(
                      result5, (as_val *)as_integer_init(&i, 2))),
                  4930);
    assert_int_eq(as_integer_get((as_integer *)as_map_get(
                      result5, (as_val *)as_integer_init(&i, 3))),
                  5120);
    assert_int_eq(as_integer_get((as_integer *)as_map_get(
                      result5, (as_val *)as_integer_init(&i, 4))),
                  4310);
    assert_int_eq(as_integer_get((as_integer *)as_map_get(
                      result5, (as_val *)as_integer_init(&i, 5))),
                  5500);
    assert_int_eq(as_integer_get((as_integer *)as_map_get(
                      result5, (as_val *)as_integer_init(&i, 6))),
                  4690);
    assert_int_eq(as_integer_get((as_integer *)as_map_get(
                      result5, (as_val *)as_integer_init(&i, 7))),
                  4880);
    assert_int_eq(as_integer_get((as_integer *)as_map_get(
                      result5, (as_val *)as_integer_init(&i, 8))),
                  5070);
    assert_int_eq(as_integer_get((as_integer *)as_map_get(
                      result5, (as_val *)as_integer_init(&i, 9))),
                  5260);

    as_list_destroy(arglist);
    as_map_destroy(result5);
    as_stream_destroy(istream);
    as_stream_destroy(ostream);
}

/******************************************************************************
 * TEST SUITE
 *****************************************************************************/

SUITE(stream_udf, "stream udf tests")
{
    suite_before(test_suite_before);
    suite_after(test_suite_after);

    suite_add(stream_udf_1);
    suite_add(stream_udf_2);
    suite_add(stream_udf_3);
    suite_add(stream_udf_4);
    suite_add(stream_udf_5);
    suite_add(stream_udf_6);
}
