/*
 * Properties.cpp
 *
 * Copyright 2002, Log4cpp Project. All rights reserved.
 *
 * See the COPYING file for the terms of usage and distribution.
 */

#include "Properties.hh"
#include "StringUtil.hh"
#include <cstdlib>

namespace log4cpp {

    Properties::Properties() {}

    Properties::~Properties() {}

    void Properties::load(std::istream& in) {
        clear();

        std::string fullLine, command;
        std::string leftSide, rightSide;
        char line[256];
        std::string::size_type length;
        bool partiallyRead(false); // fix for bug#137, for strings longer than 256 chars

        while (in) {
            if (in.getline(line, 256) || !in.bad()) {
                // either string is read fully or only partially (logical but not read/write error)
                if (partiallyRead)
                    fullLine.append(line);
                else
                    fullLine = line;
                partiallyRead = (in.fail() && !in.bad());
                if (partiallyRead && !in.eof()) {
                    in.clear(in.rdstate() & ~std::ios::failbit);
                    continue; // to get full line
                }
            } else {
                break;
            }
            /* if the line contains a # then it is a comment
               if we find it anywhere other than the beginning, then we assume
               there is a command on that line, and it we don't find it at all
               we assume there is a command on the line (we test for valid
               command later) if neither is true, we continue with the next line
            */
            length = fullLine.find('#');
            if (length == std::string::npos) {
                command = fullLine;
            } else if (length > 0) {
                command = fullLine.substr(0, length);
            } else {
                continue;
            }

            // check the command and handle it
            length = command.find('=');
            if (length != std::string::npos) {
                leftSide = StringUtil::trim(command.substr(0, length));
                rightSide = StringUtil::trim(command.substr(length + 1, command.size() - length));
                _substituteVariables(rightSide);
            } else {
                continue;
            }

            /* handle the command by determining what object the left side
               refers to and setting the value given on the right
               ASSUMPTIONS:
               1. first object given  on left side is "log4j" or "log4cpp"
               2. all class names on right side are ignored because we
               probably cannot resolve them anyway.
            */

            // strip off the "log4j" or "log4cpp"
            length = leftSide.find('.');
            if (leftSide.substr(0, length) == "log4j" || leftSide.substr(0, length) == "log4cpp")
                leftSide = leftSide.substr(length + 1);

            // add to the map of properties
            insert(value_type(leftSide, rightSide));
        }
    }

    void Properties::save(std::ostream& out) {
        for (const_iterator i = begin(); i != end(); ++i) {
            out << (*i).first << "=" << (*i).second << std::endl;
        }
    }

    int Properties::getInt(const std::string& property, int defaultValue) {
        const_iterator key = find(property);
        return (key == end()) ? defaultValue : std::atoi((*key).second.c_str());
    }

    bool Properties::getBool(const std::string& property, bool defaultValue) {
        const_iterator key = find(property);
        return (key == end()) ? defaultValue : ((*key).second == "true");
    }

    std::string Properties::getString(const std::string& property, const char* defaultValue) {
        const_iterator key = find(property);
        return (key == end()) ? std::string(defaultValue) : (*key).second;
    }

    void Properties::_substituteVariables(std::string& value) {
        std::string result;

        std::string::size_type left = 0;
        std::string::size_type right = value.find("${", left);
        if (right == std::string::npos) {
            // bail out early for 99% of cases
            return;
        }

        while (true) {
            result += value.substr(left, right - left);
            if (right == std::string::npos) {
                break;
            }

            left = right + 2;
            right = value.find('}', left);
            if (right == std::string::npos) {
                // no close tag, use string literally
                result += value.substr(left - 2);
                break;
            } else {
                const std::string key = value.substr(left, right - left);
                if (key == "${") {
                    result += "${";
                } else {
                    char* env_value = std::getenv(key.c_str());
                    if (env_value) {
                        result += env_value;
                    } else {
                        const_iterator it = find(key);
                        if (it == end()) {
                            // not found assume empty;
                        } else {
                            result += (*it).second;
                        }
                    }
                }
                left = right + 1;
            }

            right = value.find("${", left);
        }

        value = result;
    }
} // namespace log4cpp
