/*
 * Copyright (C) 2017, The Android Open Source Project
 *
 * 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 <gtest/gtest.h>

#include "frameworks/base/tools/stats_log_api_gen/test.pb.h"
#include "Collation.h"

#include <stdio.h>

namespace android {
namespace stats_log_api_gen {

using std::map;
using std::set;
using std::vector;

/**
 * Return whether the set contains a vector of the elements provided.
 */
static bool
set_contains_vector(const set<vector<java_type_t>>& s, int count, ...)
{
    va_list args;
    vector<java_type_t> v;

    va_start(args, count);
    for (int i=0; i<count; i++) {
        v.push_back((java_type_t)va_arg(args, int));
    }
    va_end(args);

    return s.find(v) != s.end();
}

/**
 * Expect that the provided set contains the elements provided.
 */
#define EXPECT_SET_CONTAINS_SIGNATURE(s, ...) \
    do { \
        int count = sizeof((int[]){__VA_ARGS__})/sizeof(int); \
        EXPECT_TRUE(set_contains_vector(s, count, __VA_ARGS__)); \
    } while(0)

/** Expects that the provided atom has no enum values for any field. */
#define EXPECT_NO_ENUM_FIELD(atom) \
    do { \
        for (vector<AtomField>::const_iterator field = atom->fields.begin(); \
             field != atom->fields.end(); field++) { \
            EXPECT_TRUE(field->enumValues.empty()); \
        } \
    } while(0)

/** Expects that exactly one specific field has expected enum values. */
#define EXPECT_HAS_ENUM_FIELD(atom, field_name, values)        \
    do { \
        for (vector<AtomField>::const_iterator field = atom->fields.begin(); \
             field != atom->fields.end(); field++) { \
            if (field->name == field_name) { \
                EXPECT_EQ(field->enumValues, values); \
            } else { \
                EXPECT_TRUE(field->enumValues.empty()); \
            } \
        } \
    } while(0)


/**
 * Test a correct collation, with all the types.
 */
TEST(CollationTest, CollateStats) {
    Atoms atoms;
    int errorCount = collate_atoms(Event::descriptor(), &atoms);

    EXPECT_EQ(0, errorCount);
    EXPECT_EQ(3ul, atoms.signatures.size());

    // IntAtom, AnotherIntAtom
    EXPECT_SET_CONTAINS_SIGNATURE(atoms.signatures, JAVA_TYPE_INT);

    // OutOfOrderAtom
    EXPECT_SET_CONTAINS_SIGNATURE(atoms.signatures, JAVA_TYPE_INT, JAVA_TYPE_INT);

    // AllTypesAtom
    EXPECT_SET_CONTAINS_SIGNATURE(
        atoms.signatures,
        JAVA_TYPE_ATTRIBUTION_CHAIN, // AttributionChain
        JAVA_TYPE_DOUBLE,            // double
        JAVA_TYPE_FLOAT,             // float
        JAVA_TYPE_LONG,              // int64
        JAVA_TYPE_LONG,              // uint64
        JAVA_TYPE_INT,               // int32
        JAVA_TYPE_LONG,              // fixed64
        JAVA_TYPE_INT,               // fixed32
        JAVA_TYPE_BOOLEAN,           // bool
        JAVA_TYPE_STRING,            // string
        JAVA_TYPE_INT,               // uint32
        JAVA_TYPE_INT,               // AnEnum
        JAVA_TYPE_INT,               // sfixed32
        JAVA_TYPE_LONG,              // sfixed64
        JAVA_TYPE_INT,               // sint32
        JAVA_TYPE_LONG               // sint64
    );

    set<AtomDecl>::const_iterator atom = atoms.decls.begin();
    EXPECT_EQ(1, atom->code);
    EXPECT_EQ("int_atom", atom->name);
    EXPECT_EQ("IntAtom", atom->message);
    EXPECT_NO_ENUM_FIELD(atom);
    atom++;

    EXPECT_EQ(2, atom->code);
    EXPECT_EQ("out_of_order_atom", atom->name);
    EXPECT_EQ("OutOfOrderAtom", atom->message);
    EXPECT_NO_ENUM_FIELD(atom);
    atom++;

    EXPECT_EQ(3, atom->code);
    EXPECT_EQ("another_int_atom", atom->name);
    EXPECT_EQ("AnotherIntAtom", atom->message);
    EXPECT_NO_ENUM_FIELD(atom);
    atom++;

    EXPECT_EQ(4, atom->code);
    EXPECT_EQ("all_types_atom", atom->name);
    EXPECT_EQ("AllTypesAtom", atom->message);
    map<int, string> enumValues;
    enumValues[0] = "VALUE0";
    enumValues[1] = "VALUE1";
    EXPECT_HAS_ENUM_FIELD(atom, "enum_field", enumValues);
    atom++;

    EXPECT_TRUE(atom == atoms.decls.end());
}

/**
 * Test that event class that contains stuff other than the atoms is rejected.
 */
TEST(CollationTest, NonMessageTypeFails) {
    Atoms atoms;
    int errorCount = collate_atoms(IntAtom::descriptor(), &atoms);

    EXPECT_EQ(1, errorCount);
}

/**
 * Test that atoms that have non-primitive types are rejected.
 */
TEST(CollationTest, FailOnBadTypes) {
    Atoms atoms;
    int errorCount = collate_atoms(BadTypesEvent::descriptor(), &atoms);

    EXPECT_EQ(2, errorCount);
}

/**
 * Test that atoms that skip field numbers (in the first position) are rejected.
 */
TEST(CollationTest, FailOnSkippedFieldsSingle) {
    Atoms atoms;
    int errorCount = collate_atoms(BadSkippedFieldSingle::descriptor(), &atoms);

    EXPECT_EQ(1, errorCount);
}

/**
 * Test that atoms that skip field numbers (not in the first position, and multiple
 * times) are rejected.
 */
TEST(CollationTest, FailOnSkippedFieldsMultiple) {
    Atoms atoms;
    int errorCount = collate_atoms(BadSkippedFieldMultiple::descriptor(), &atoms);

    EXPECT_EQ(2, errorCount);
}

/**
 * Test that atoms that have an attribution chain not in the first position are
 * rejected.
 */
TEST(CollationTest, FailBadAttributionNodePosition) {
  Atoms atoms;
  int errorCount =
      collate_atoms(BadAttributionNodePosition::descriptor(), &atoms);

  EXPECT_EQ(1, errorCount);
}

}  // namespace stats_log_api_gen
}  // namespace android