/*
 * Copyright (c) 2011-2014, Intel Corporation
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification,
 * are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice, this
 * list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation and/or
 * other materials provided with the distribution.
 *
 * 3. Neither the name of the copyright holder nor the names of its contributors
 * may be used to endorse or promote products derived from this software without
 * specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
#include "RuleParser.h"
#include "CompoundRule.h"
#include "SelectionCriterionRule.h"
#include "AlwaysAssert.hpp"
#include <assert.h>

using std::string;

// Matches
const char *CRuleParser::_acDelimiters[CRuleParser::ENbStatuses] = {
    "{",   // EInit
    "{} ", // EBeginCompoundRule
    ",}",  // EEndCompoundRule
    ",}",  // ECriterionRule
    "{ ",  // EContinue
    ""     // EDone
};

CRuleParser::CRuleParser(const string &strApplicationRule,
                         const CSelectionCriteriaDefinition *pSelectionCriteriaDefinition)
    : _strApplicationRule(strApplicationRule),
      _pSelectionCriteriaDefinition(pSelectionCriteriaDefinition)
{
}

CRuleParser::~CRuleParser()
{
    delete _pRootRule;
}

// Parse
bool CRuleParser::parse(CCompoundRule *pParentRule, string &strError)
{
    while (true) {
        // Iterate till next relevant delimiter
        if (!iterate(strError)) {

            return false;
        }
        switch (_eStatus) {
        case EBeginCompoundRule: {

            // Create new compound rule
            CCompoundRule *pCompoundRule = new CCompoundRule;

            // Parse
            if (!pCompoundRule->parse(*this, strError)) {

                delete pCompoundRule;

                return false;
            }
            // Parent rule creation context?
            if (pParentRule) {

                // Chain
                pParentRule->addChild(pCompoundRule);
            } else {
                // Root rule
                delete _pRootRule;
                _pRootRule = pCompoundRule;
            }
            // Parse
            if (!parse(pCompoundRule, strError)) {

                return false;
            }
            // Go on
            break;
        }
        case EEndCompoundRule:
            return true;
        case EContinue:
            // Seek for new rule
            break;
        case ECriterionRule: {
            // Create new criterion rule
            CSelectionCriterionRule *pCriterionRule = new CSelectionCriterionRule;

            // Parse
            if (!pCriterionRule->parse(*this, strError)) {

                delete pCriterionRule;

                return false;
            }

            ALWAYS_ASSERT(pParentRule != NULL, "Invalid parent rule given to rule parser");
            // Chain
            pParentRule->addChild(pCriterionRule);

            // Go on
            break;
        }
        case EDone: {
            // If the current state is EDone, check that at least one rule has been found.
            if (_pRootRule) {

                // At least one rule found
                return true;
            } else {

                strError = "Syntax error, no rule found";

                return false;
            }
        }
        default:
            assert(0);
            return false;
        }
    }

    return true;
}

// Iterate
bool CRuleParser::iterate(string &strError)
{
    string::size_type delimiter;

    ALWAYS_ASSERT(_uiCurrentPos <= _strApplicationRule.length(), "Current Position outside range");

    // Consume spaces
    if ((delimiter = _strApplicationRule.find_first_not_of(" ", _uiCurrentPos)) != string::npos) {

        // New pos
        _uiCurrentPos = delimiter;
    }

    // Parse
    if ((_uiCurrentPos != _strApplicationRule.length()) &&
        ((delimiter = _strApplicationRule.find_first_of(_acDelimiters[_eStatus], _uiCurrentPos)) !=
         string::npos)) {

        switch (_strApplicationRule[delimiter]) {

        case '{':
            _eStatus = EBeginCompoundRule;
            // Extract type
            _strRuleType = _strApplicationRule.substr(_uiCurrentPos, delimiter - _uiCurrentPos);
            _currentDeepness++;
            break;
        case '}':
            _eStatus = EEndCompoundRule;

            if (!_currentDeepness--) {

                strError = "Missing opening brace";

                return false;
            }
            break;
        case ' ':
            _eStatus = ECriterionRule;
            // Extract type
            _strRuleType = _strApplicationRule.substr(_uiCurrentPos, delimiter - _uiCurrentPos);
            break;
        case ',':
            _eStatus = EContinue;
            break;
        }
        // New pos
        _uiCurrentPos = delimiter + 1;
    } else {

        if (_currentDeepness) {

            strError = "Missing closing brace";

            return false;
        }

        // Remaining characters
        if (_uiCurrentPos != _strApplicationRule.length()) {

            strError = "Syntax error";

            return false;
        }
        // Done
        _eStatus = EDone;
    }
    return true;
}

// Rule type
const string &CRuleParser::getType() const
{
    return _strRuleType;
}

// Criteria defintion
const CSelectionCriteriaDefinition *CRuleParser::getSelectionCriteriaDefinition() const
{
    return _pSelectionCriteriaDefinition;
}

// Root rule
CCompoundRule *CRuleParser::grabRootRule()
{
    CCompoundRule *pRootRule = _pRootRule;

    assert(pRootRule);

    _pRootRule = NULL;

    return pRootRule;
}

// Next word
bool CRuleParser::next(string &strNext, string &strError)
{
    string::size_type delimiter;

    // Consume spaces
    if ((delimiter = _strApplicationRule.find_first_not_of(" ", _uiCurrentPos)) != string::npos) {

        // New pos
        _uiCurrentPos = delimiter;
    }

    if ((delimiter = _strApplicationRule.find_first_of("{} ,", _uiCurrentPos)) == string::npos) {

        strError = "Syntax error";

        return false;
    }

    strNext = _strApplicationRule.substr(_uiCurrentPos, delimiter - _uiCurrentPos);

    // New pos
    _uiCurrentPos = delimiter;

    return true;
}