/*
 * Copyright 2013 Google Inc.
 *
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */

#include "SkRTConf.h"
#include "SkOSFile.h"

#include <stdlib.h>

SkRTConfRegistry::SkRTConfRegistry(): fConfs(100) {

    SkFILE *fp = sk_fopen(configFileLocation(), kRead_SkFILE_Flag);

    if (!fp) {
        return;
    }

    char line[1024];

    while (!sk_feof(fp)) {

        if (!sk_fgets(line, sizeof(line), fp)) {
            break;
        }

        char *commentptr = strchr(line, '#');
        if (commentptr == line) {
            continue;
        }
        if (commentptr) {
            *commentptr = '\0';
        }

        char sep[] = " \t\r\n";

        char *keyptr = strtok(line, sep);
        if (!keyptr) {
            continue;
        }

        char *valptr = strtok(nullptr, sep);
        if (!valptr) {
            continue;
        }

        SkString *key = new SkString(keyptr);
        SkString *val = new SkString(valptr);

        fConfigFileKeys.append(1, &key);
        fConfigFileValues.append(1, &val);
    }
    sk_fclose(fp);
}

SkRTConfRegistry::~SkRTConfRegistry() {
    ConfMap::Iter iter(fConfs);
    SkTDArray<SkRTConfBase *> *confArray;

    while (iter.next(&confArray)) {
        delete confArray;
    }

    for (int i = 0 ; i < fConfigFileKeys.count() ; i++) {
        delete fConfigFileKeys[i];
        delete fConfigFileValues[i];
    }
}

const char *SkRTConfRegistry::configFileLocation() const {
    return "skia.conf"; // for now -- should probably do something fancier like home directories or whatever.
}

// dump all known runtime config options to the file with their default values.
// to trigger this, make a config file of zero size.
void SkRTConfRegistry::possiblyDumpFile() const {
    const char *path = configFileLocation();
    SkFILE *fp = sk_fopen(path, kRead_SkFILE_Flag);
    if (!fp) {
        return;
    }
    size_t configFileSize = sk_fgetsize(fp);
    if (configFileSize == 0) {
        printAll(path);
    }
    sk_fclose(fp);
}

// Run through every provided configuration option and print a warning if the user hasn't
// declared a correponding configuration object somewhere.
void SkRTConfRegistry::validate() const {
    for (int i = 0 ; i < fConfigFileKeys.count() ; i++) {
        if (!fConfs.find(fConfigFileKeys[i]->c_str())) {
            SkDebugf("WARNING: You have config value %s in your configuration file, but I've never heard of that.\n", fConfigFileKeys[i]->c_str());
        }
    }
}

void SkRTConfRegistry::printAll(const char *fname) const {
    SkWStream *o;

    if (fname) {
        o = new SkFILEWStream(fname);
    } else {
        o = new SkDebugWStream();
    }

    ConfMap::Iter iter(fConfs);
    SkTDArray<SkRTConfBase *> *confArray;

    while (iter.next(&confArray)) {
        if (confArray->getAt(0)->isDefault()) {
            o->writeText("# ");
        }
        confArray->getAt(0)->print(o);
        o->newline();
    }

    delete o;
}

bool SkRTConfRegistry::hasNonDefault() const {
    ConfMap::Iter iter(fConfs);
    SkTDArray<SkRTConfBase *> *confArray;
    while (iter.next(&confArray)) {
        if (!confArray->getAt(0)->isDefault()) {
            return true;
        }
    }
    return false;
}

void SkRTConfRegistry::printNonDefault(const char *fname) const {
    SkWStream *o;

    if (fname) {
        o = new SkFILEWStream(fname);
    } else {
        o = new SkDebugWStream();
    }
    ConfMap::Iter iter(fConfs);
    SkTDArray<SkRTConfBase *> *confArray;

    while (iter.next(&confArray)) {
        if (!confArray->getAt(0)->isDefault()) {
            confArray->getAt(0)->print(o);
            o->newline();
        }
    }

    delete o;
}

// register a configuration variable after its value has been set by the parser.
// we maintain a vector of these things instead of just a single one because the
// user might set the value after initialization time and we need to have
// all the pointers lying around, not just one.
void SkRTConfRegistry::registerConf(SkRTConfBase *conf) {
    SkTDArray<SkRTConfBase *> *confArray;
    if (fConfs.find(conf->getName(), &confArray)) {
        if (!conf->equals(confArray->getAt(0))) {
            SkDebugf("WARNING: Skia config \"%s\" was registered more than once in incompatible ways.\n", conf->getName());
        } else {
            confArray->append(1, &conf);
        }
    } else {
        confArray = new SkTDArray<SkRTConfBase *>;
        confArray->append(1, &conf);
        fConfs.set(conf->getName(),confArray);
    }
}

template <typename T> T doParse(const char *, bool *success ) {
    SkDebugf("WARNING: Invoked non-specialized doParse function...\n");
    if (success) {
        *success = false;
    }
    return (T) 0;
}

template<> bool doParse<bool>(const char *s, bool *success) {
    if (success) {
        *success = true;
    }
    if (!strcmp(s,"1") || !strcmp(s,"true")) {
        return true;
    }
    if (!strcmp(s,"0") || !strcmp(s,"false")) {
        return false;
    }
    if (success) {
        *success = false;
    }
    return false;
}

template<> const char * doParse<const char *>(const char * s, bool *success) {
    if (success) {
        *success = true;
    }
    return s;
}

template<> int doParse<int>(const char * s, bool *success) {
    if (success) {
        *success = true;
    }
    return atoi(s);
}

template<> unsigned int doParse<unsigned int>(const char * s, bool *success) {
    if (success) {
        *success = true;
    }
    return (unsigned int) atoi(s);
}

template<> float doParse<float>(const char * s, bool *success) {
    if (success) {
        *success = true;
    }
    return (float) atof(s);
}

template<> double doParse<double>(const char * s, bool *success) {
    if (success) {
        *success = true;
    }
    return atof(s);
}

static inline void str_replace(char *s, char search, char replace) {
    for (char *ptr = s ; *ptr ; ptr++) {
        if (*ptr == search) {
            *ptr = replace;
        }
    }
}

template<typename T> bool SkRTConfRegistry::parse(const char *name, T* value) {
    const char *str = nullptr;

    for (int i = fConfigFileKeys.count() - 1 ; i >= 0; i--) {
        if (fConfigFileKeys[i]->equals(name)) {
            str = fConfigFileValues[i]->c_str();
            break;
        }
    }

    SkString environment_variable("skia.");
    environment_variable.append(name);

    const char *environment_value = getenv(environment_variable.c_str());
    if (environment_value) {
        str = environment_value;
    } else {
        // apparently my shell doesn't let me have environment variables that
        // have periods in them, so also let the user substitute underscores.
        SkAutoTMalloc<char> underscore_name(SkStrDup(environment_variable.c_str()));
        str_replace(underscore_name.get(), '.', '_');
        environment_value = getenv(underscore_name.get());
        if (environment_value) {
            str = environment_value;
        }
    }

    if (!str) {
        return false;
    }

    bool success;
    T new_value = doParse<T>(str, &success);
    if (success) {
        *value = new_value;
    } else {
        SkDebugf("WARNING: Couldn't parse value \'%s\' for variable \'%s\'\n",
                 str, name);
    }
    return success;
}

// need to explicitly instantiate the parsing function for every config type we might have...

template bool SkRTConfRegistry::parse(const char *name, bool *value);
template bool SkRTConfRegistry::parse(const char *name, int *value);
template bool SkRTConfRegistry::parse(const char *name, unsigned int *value);
template bool SkRTConfRegistry::parse(const char *name, float *value);
template bool SkRTConfRegistry::parse(const char *name, double *value);
template bool SkRTConfRegistry::parse(const char *name, const char **value);

template <typename T> void SkRTConfRegistry::set(const char *name,
                                                 T value,
                                                 bool warnIfNotFound) {
    SkTDArray<SkRTConfBase *> *confArray;
    if (!fConfs.find(name, &confArray)) {
        if (warnIfNotFound) {
            SkDebugf("WARNING: Attempting to set configuration value \"%s\","
                     " but I've never heard of that.\n", name);
        }
        return;
    }
    SkASSERT(confArray != nullptr);
    for (SkRTConfBase **confBase = confArray->begin(); confBase != confArray->end(); confBase++) {
        // static_cast here is okay because there's only one kind of child class.
        SkRTConf<T> *concrete = static_cast<SkRTConf<T> *>(*confBase);

        if (concrete) {
            concrete->set(value);
        }
    }
}

template void SkRTConfRegistry::set(const char *name, bool value, bool);
template void SkRTConfRegistry::set(const char *name, int value, bool);
template void SkRTConfRegistry::set(const char *name, unsigned int value, bool);
template void SkRTConfRegistry::set(const char *name, float value, bool);
template void SkRTConfRegistry::set(const char *name, double value, bool);
template void SkRTConfRegistry::set(const char *name, char * value, bool);

SkRTConfRegistry &skRTConfRegistry() {
    static SkRTConfRegistry r;
    return r;
}
