/*
 * Copyright 2010 JBoss Inc
 *
 * 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.
 */

package org.drools.ide.common.assistant.engine;

import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.drools.ide.common.assistant.info.RuleRefactorInfo;
import org.drools.ide.common.assistant.info.drl.DRLContentTypeEnum;
import org.drools.ide.common.assistant.info.drl.DRLRuleRefactorInfo;
import org.drools.ide.common.assistant.info.drl.RuleLineContentInfo;

/**
 * A simple DRL parser implemented with regular expressions to get the offset of rule components
 */
public class DRLParserEngine extends AbstractParserEngine {

    private static final String RULE_DECLARATION = "(rule|RULE)";
    private static final String PACKAGE_DECLARATION = "(package|PACKAGE)";
    private static final String IMPORT_DECLARATION = "(import|IMPORT)";
    private static final String GLOBAL_DECLARATION = "(global|GLOBAL)";
    private static final String RULE_WHEN_DECLARATION = "(when|WHEN)";
    private static final String RULE_THEN_DECLARATION = "(then|THEN)";
    private static final String RULE_END_DECLARATION = "(end|END)";

    private static final String OPTIONAL_TAB = "[\t]*";

    private static final String FULLY_QUALIFIED_NAME = "[\\w\\.]*";
    private static final String ONE_OR_MORE_SPACES = "[\\s]+";
    private static final String RULE_NAME = "\"[\\s\\w]*\"";

    // Regulars expressions to match DRL Rule
    private static final String PACKAGE_PATTERN = PACKAGE_DECLARATION + ONE_OR_MORE_SPACES + FULLY_QUALIFIED_NAME + ";?"; // OK
    private static final String IMPORT_PATTERN = IMPORT_DECLARATION + ONE_OR_MORE_SPACES + FULLY_QUALIFIED_NAME + ";"; // OK
    private static final String GLOBAL_PATTERN = GLOBAL_DECLARATION + ONE_OR_MORE_SPACES + FULLY_QUALIFIED_NAME + ONE_OR_MORE_SPACES + "[\\w]*" + ONE_OR_MORE_SPACES + ""; // OK

    private static final String RULE_NAME_PATTERN = RULE_DECLARATION + ONE_OR_MORE_SPACES + RULE_NAME;
    private static final String RULE_LHS_PATTERN = "[\\t\\s]*" + RULE_WHEN_DECLARATION + ONE_OR_MORE_SPACES + OPTIONAL_TAB + "[\\w\\W]*" + "(?=" + RULE_THEN_DECLARATION + ")";
    private static final String RULE_RHS_PATTERN = "[\\t\\s]*" + RULE_THEN_DECLARATION + ONE_OR_MORE_SPACES + "[\\w\\W]*" + RULE_END_DECLARATION;

    private static final Pattern rulePattern = Pattern.compile("rule.+?end\\s*$", Pattern.MULTILINE | Pattern.DOTALL);

    public DRLParserEngine(String rule) {
        this.ruleRefactorInfo = new DRLRuleRefactorInfo();
        this.rule = rule;
    }

    public RuleRefactorInfo parse() {
        detectPackage(rule);
        detectGlobals(rule);
        detectImports(rule);
        detectRules(rule);
        return ruleRefactorInfo;
    }

    private void detectPackage(CharSequence rule) {
        pattern = Pattern.compile(PACKAGE_PATTERN);
        matcher = pattern.matcher(rule);
        if (matcher.find())
            ((DRLRuleRefactorInfo) ruleRefactorInfo).addContent(DRLContentTypeEnum.PACKAGE, matcher.start(), matcher.group());
    }

    private void detectImports(CharSequence rule) {
        pattern = Pattern.compile(IMPORT_PATTERN);
        matcher = pattern.matcher(rule);
        while (matcher.find())
            ((DRLRuleRefactorInfo) ruleRefactorInfo).addContent(DRLContentTypeEnum.IMPORT, matcher.start(), matcher.group());
    }

    private void detectGlobals(CharSequence rule) {
        pattern = Pattern.compile(GLOBAL_PATTERN);
        matcher = pattern.matcher(rule);
        while (matcher.find())
            ((DRLRuleRefactorInfo) ruleRefactorInfo).addContent(DRLContentTypeEnum.GLOBAL, matcher.start(), matcher.group());
    }

    private void detectRules(CharSequence rule) {
        Matcher ruleMatcher = rulePattern.matcher(rule);
        while (ruleMatcher.find()) {
            for( int position = 0; position < ruleMatcher.groupCount()+1; position++ ){
                String value = ruleMatcher.group(position);
                int offset = ruleMatcher.start();
                String ruleName = detectRuleName(value);
                List<RuleLineContentInfo> lhs = detectLHS(value, offset);
                List<RuleLineContentInfo> rhs = detectRHS(value, offset);
                ((DRLRuleRefactorInfo) ruleRefactorInfo).addContent(DRLContentTypeEnum.RULE, offset, value, ruleName, lhs, rhs);
            }
        }
    }

    private String detectRuleName(CharSequence rule) {
        pattern = Pattern.compile(RULE_NAME_PATTERN);
        matcher = pattern.matcher(rule);
        if (matcher.find())
            return matcher.group();
        return null;
    }

    private List<RuleLineContentInfo> detectLHS(CharSequence rule, int ruleOffset) {
        pattern = Pattern.compile(RULE_LHS_PATTERN);
        matcher = pattern.matcher(rule);
        if (matcher.find()) {
            return detectLines(matcher.group(), matcher.start() + ruleOffset, DRLContentTypeEnum.RULE_LHS_LINE);
        }
        return null;
    }

    private List<RuleLineContentInfo> detectRHS(CharSequence rule, int ruleOffset) {
        pattern = Pattern.compile(RULE_RHS_PATTERN);
        matcher = pattern.matcher(rule);
        if (matcher.find())
            return detectLines(matcher.group(), matcher.start() + ruleOffset, DRLContentTypeEnum.RULE_RHS_LINE);
        return null;
    }

    private List<RuleLineContentInfo> detectLines(CharSequence rule, int lineOffset, DRLContentTypeEnum type) {
        List<RuleLineContentInfo> ruleLines = new ArrayList<RuleLineContentInfo>();
        pattern = Pattern.compile(".*");
        matcher = pattern.matcher(rule);
        while (matcher.find()) {
            if (matcher.start()!=matcher.end()) {
                if (matcher.group().trim().length() > 0) {
                    ruleLines.add(new RuleLineContentInfo(matcher.start()+lineOffset, matcher.group(), type));
                }
            }
        }
        return ruleLines;
    }

}
