/* Copyright (C) 2019 Open Information Security Foundation
 *
 * You can copy, redistribute or modify this Program under the terms of
 * the GNU General Public License version 2 as published by the Free
 * Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * version 2 along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 * 02110-1301, USA.
 */

/**
 * \file
 *
 * \author Jeff Lucovsky <jeff@lucovsky.org>
 *
 * Implements feature tracking
 */

#include "suricata-common.h"
#include "feature.h"
#include "threads.h"

#include "util-debug.h"
#include "util-hashlist.h"

typedef struct FeatureEntryType {
	const char *feature;
} FeatureEntryType;

static SCMutex feature_table_mutex = SCMUTEX_INITIALIZER;
static HashListTable *feature_hash_table;

static uint32_t FeatureHashFunc(HashListTable *ht, void *data,
                                uint16_t datalen)
{
    FeatureEntryType *f = (FeatureEntryType *)data;
    uint32_t hash = 0;
    int len = strlen(f->feature);

    for (int i = 0; i < len; i++)
        hash += u8_tolower((unsigned char)f->feature[i]);

    return (hash % ht->array_size);
}

static char FeatureHashCompareFunc(void *data1, uint16_t datalen1,
                                   void *data2, uint16_t datalen2)
{
    FeatureEntryType *f1 = (FeatureEntryType *)data1;
    FeatureEntryType *f2 = (FeatureEntryType *)data2;
    int len1 = 0;
    int len2 = 0;

    if (f1 == NULL || f2 == NULL)
        return 0;

    if (f1->feature == NULL || f2->feature == NULL)
        return 0;

    len1 = strlen(f1->feature);
    len2 = strlen(f2->feature);

    return (len1 == len2 && memcmp(f1->feature, f2->feature, len1) == 0);
}

static void FeatureHashFreeFunc(void *data)
{
    FeatureEntryType *f = data;
    if (f->feature) {
        SCFree((void *)f->feature);
    }
    SCFree(data);
}

static void FeatureInit(void) {
    feature_hash_table = HashListTableInit(256, FeatureHashFunc,
                                           FeatureHashCompareFunc,
                                           FeatureHashFreeFunc);

    if (!feature_hash_table) {
        FatalError("Unable to allocate feature hash table.");
    }
}

static void FeatureAddEntry(const char *feature_name)
{
    int rc;

    FeatureEntryType *feature = SCCalloc(1, sizeof(*feature));
    if (!feature) {
        FatalError("Unable to allocate feature entry memory.");
    }

    feature->feature = SCStrdup(feature_name);
    if (feature->feature) {
        rc = HashListTableAdd(feature_hash_table, feature, sizeof(*feature));
        if (rc == 0)
            return;
    }

    FeatureHashFreeFunc(feature);
}

void ProvidesFeature(const char *feature_name)
{
    FeatureEntryType f = { feature_name };

    SCMutexLock(&feature_table_mutex);

    FeatureEntryType *feature = HashListTableLookup(feature_hash_table, &f, sizeof(f));

    if (!feature) {
        FeatureAddEntry(feature_name);
    }

    SCMutexUnlock(&feature_table_mutex);
}

bool RequiresFeature(const char *feature_name)
{
    FeatureEntryType f = { feature_name };

    SCMutexLock(&feature_table_mutex);
    FeatureEntryType *feature = HashListTableLookup(feature_hash_table, &f, sizeof(f));
    SCMutexUnlock(&feature_table_mutex);
    return feature != NULL;
}

void FeatureTrackingRelease(void)
{
    if (feature_hash_table != NULL) {
        HashListTableFree(feature_hash_table);
        feature_hash_table = NULL;
    }
}

void FeatureDump(void)
{
    HashListTableBucket *hb = HashListTableGetListHead(feature_hash_table);
    for (; hb != NULL; hb = HashListTableGetListNext(hb)) {
        FeatureEntryType *f = HashListTableGetListData(hb);
        printf("provided feature name: %s\n", f->feature);
    }
}
void FeatureTrackingRegister(void)
{
    FeatureInit();
}
