/*
 *
 * Copyright (c) 2006 The Apache Software Foundation
 *
 * 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 "qpid/log/Selector.h"
#include "qpid/log/Options.h"
#include <boost/bind.hpp>
#include <algorithm>
#include <string.h>

namespace qpid {
namespace log {

using namespace std;

const char LOG_SYMBOL_DISABLE  ('!');
const char LOG_SYMBOL_SEPERATOR(':');
const char LOG_SYMBOL_AND_ABOVE('+');
const char LOG_SYMBOL_AND_BELOW('-');

//
// Parse an enable or disable entry into usable fields.
// Throws if 'level' field is not recognized.
//
SelectorElement::SelectorElement(const std::string cliEntry) :
    level(qpid::log::debug),
    category(qpid::log::unspecified),
    isDisable(false),
    isCategory(false),
    isLevelAndAbove(false),
    isLevelAndBelow(false)
{
    if (cliEntry.empty())
        return;
    std::string working(cliEntry);
    if (LOG_SYMBOL_DISABLE == working[0]) {
        isDisable = true;
        working = working.substr(1);
    }
    size_t c=working.find(LOG_SYMBOL_SEPERATOR);
    if (c==string::npos) {
        levelStr=working;
    } else {
        levelStr=working.substr(0,c);
        patternStr=working.substr(c+1);
    }
    if (!levelStr.empty()) {
        if (levelStr[levelStr.size()-1]==LOG_SYMBOL_AND_ABOVE) {
            isLevelAndAbove = true;
            levelStr = levelStr.substr(0, levelStr.size()-1);
        } else if (levelStr[levelStr.size()-1]==LOG_SYMBOL_AND_BELOW) {
            isLevelAndBelow = true;
            levelStr = levelStr.substr(0, levelStr.size()-1);
        }
    }
    level = LevelTraits::level(levelStr);  // throws if bad level name
    isCategory = CategoryTraits::isCategory(patternStr);
    if (isCategory) {
        category = CategoryTraits::category(patternStr);
    }
}

// Empty selector
Selector::Selector() {
    reset();
}


// Selector from options
Selector::Selector(const Options& opt){
    reset();
    for_each(opt.selectors.begin(), opt.selectors.end(),
             boost::bind(&Selector::enable, this, _1));
    for_each(opt.deselectors.begin(), opt.deselectors.end(),
             boost::bind(&Selector::disable, this, _1));
}


// Selector from single level
Selector::Selector(Level l, const std::string& s) {
    reset();
    enable(l, s);
}


// Selector from single enable
Selector::Selector(const std::string& selector) {
    reset();
    enable(selector);
}


/**
 * Process a single CLI --log-enable option
 */
void Selector::enable(const string& enableStr) {
    if (enableStr.empty())
        return;
    SelectorElement se(enableStr);
    if (se.isDisable) {
        // Disable statements are allowed in an enable string as a convenient
        // way to process management strings that have enable/disable mixed.
        disable(enableStr);
    } else if (se.isLevelAndAbove) {
        for (int lvl = se.level; lvl < LevelTraits::COUNT; ++lvl) {
            if (se.isCategory) {
                enableFlags[lvl][se.category] = true;
            } else {
                enable(Level(lvl), se.patternStr);
            }
        }
    } else if (se.isLevelAndBelow) {
        for (int lvl = se.level; lvl >= 0; --lvl) {
            if (se.isCategory) {
                enableFlags[lvl][se.category] = true;
            } else {
                enable(Level(lvl), se.patternStr);
            }
        }
    } else {
        if (se.isCategory) {
            enableFlags[se.level][se.category] = true;
        } else {
            enable(se.level, se.patternStr);
        }
    }
}

void Selector::disable(const string& disableStr) {
    if (disableStr.empty())
        return;
    SelectorElement se(disableStr);
    if (se.isLevelAndAbove) {
        for (int lvl = se.level; lvl < LevelTraits::COUNT; ++lvl) {
            if (se.isCategory) {
                disableFlags[lvl][se.category] = true;
            } else {
                disable(Level(lvl), se.patternStr);
            }
        }
    } else if (se.isLevelAndBelow) {
        for (int lvl = se.level; lvl >= 0; --lvl) {
            if (se.isCategory) {
                disableFlags[lvl][se.category] = true;
            } else {
                disable(Level(lvl), se.patternStr);
            }
        }
    } else {
        if (se.isCategory) {
            disableFlags[se.level][se.category] = true;
        } else {
            disable(se.level, se.patternStr);
        }
    }
}


/**
* Enable/disable messages with level in levels where the file
* name contains substring.
*/
void Selector::enable(Level level, const std::string& substring) {
    enabledFunctions[level].push_back(substring);
}


void Selector::disable(Level level, const std::string& substring) {
    disabledFunctions[level].push_back(substring);
}


void Selector::reset() {
    // Initialize fields in a Selector that are not automatically set
    for (int lt = 0; lt < LevelTraits::COUNT; ++lt)
        for (int ct = 0; ct < CategoryTraits::COUNT; ++ct)
            enableFlags[lt][ct] = disableFlags[lt][ct] = false;
}


bool Selector::lookupFuncName(Level level, const char* function, FunctionNameTable& table) {
    const char* functionEnd = function+::strlen(function);
    for (std::vector<std::string>::iterator i=table[level].begin();
         i != table[level].end();
         ++i)
    {
        if (std::search(function, functionEnd, i->begin(), i->end()) != functionEnd)
            return true;
    }
    return false;
}


bool Selector::isEnabled(Level level, const char* function) {
    return lookupFuncName(level, function, enabledFunctions);
}

bool Selector::isDisabled(Level level, const char* function) {
    return lookupFuncName(level, function, disabledFunctions);
}

//
// isEnabled
//
// Determines if all the fields in this Selector enable or disable a
// level/function/category set from an actual QPID_LOG Statement.
//
bool Selector::isEnabled(Level level, const char* function, Category category) {
    if (isDisabled(level, function))
        return false;                   // Disabled by function name
    if (disableFlags[level][category])
        return false;                   // Disabled by category name
    if (isEnabled(level, function))
        return true;                    // Enabled by function name
    if (enableFlags[level][category])
        return true;                    // Enabled by category name
    else
        return false;                   // Unspecified defaults to disabled
}

}} // namespace qpid::log
