/*
 * Copyright 2010 Red Hat, Inc. and/or its affiliates.
 *
 * 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.eclipse;

import java.util.ArrayList;
import java.util.List;

import org.drools.compiler.compiler.BaseKnowledgeBuilderResultImpl;
import org.drools.compiler.compiler.Dialect;
import org.drools.compiler.compiler.DialectCompiletimeRegistry;
import org.drools.compiler.compiler.DroolsError;
import org.drools.compiler.lang.descr.AttributeDescr;
import org.drools.compiler.lang.descr.BaseDescr;
import org.drools.compiler.lang.descr.ConditionalElementDescr;
import org.drools.compiler.lang.descr.FunctionDescr;
import org.drools.compiler.lang.descr.PackageDescr;
import org.drools.compiler.lang.descr.PatternDescr;
import org.drools.compiler.lang.descr.RuleDescr;
import org.drools.core.base.ClassObjectType;
import org.drools.core.definitions.InternalKnowledgePackage;
import org.drools.core.definitions.rule.impl.RuleImpl;
import org.drools.core.rule.DialectRuntimeRegistry;
import org.drools.core.rule.GroupElement;
import org.drools.core.rule.LineMappings;
import org.drools.core.rule.Pattern;
import org.drools.core.rule.RuleConditionElement;
import org.drools.core.spi.ObjectType;
import org.eclipse.core.resources.IResource;

public class DRLInfo {

    private static final DroolsError[] EMPTY_DROOLS_ERROR_ARRAY = new DroolsError[0];

    private final String sourcePathName;
    private final PackageDescr packageDescr;
    private List<BaseKnowledgeBuilderResultImpl> parserErrors;
    private InternalKnowledgePackage compiledPackage;
    private DroolsError[] builderErrors;
    // cached entry
    private transient RuleInfo[] ruleInfos;
    private transient FunctionInfo[] functionInfos;
    private DialectCompiletimeRegistry dialectRegistry;
    
    private IResource resource;

    public DRLInfo( String sourcePathName, 
    				PackageDescr packageDescr, 
    				List<BaseKnowledgeBuilderResultImpl> parserErrors, 
    				DialectCompiletimeRegistry dialectRegistry ) {
        if (sourcePathName == null) {
            throw new IllegalArgumentException("Invalid sourcePathName " + sourcePathName);
        }
        this.sourcePathName = sourcePathName;
        this.packageDescr = packageDescr;
        this.parserErrors = parserErrors;
        this.builderErrors = EMPTY_DROOLS_ERROR_ARRAY;
        this.dialectRegistry = dialectRegistry;
    }

    public DRLInfo( String pathName, 
    				PackageDescr packageDescr, 
    				List<BaseKnowledgeBuilderResultImpl> parserErrors, 
    				InternalKnowledgePackage compiledPackage, 
    				DroolsError[] builderErrors, 
    				DialectCompiletimeRegistry dialectRegistry ) {
        this(pathName, packageDescr, parserErrors, dialectRegistry);
        if (compiledPackage == null) {
            throw new IllegalArgumentException("Null package");
        }
        this.compiledPackage = compiledPackage;
        this.builderErrors =
            builderErrors == null ? EMPTY_DROOLS_ERROR_ARRAY : builderErrors;
    }

    public String getSourcePathName() {
        return sourcePathName;
    }

    public PackageDescr getPackageDescr() {
        return packageDescr;
    }

    public List<BaseKnowledgeBuilderResultImpl> getParserErrors() {
        return parserErrors;
    }
    
    public void addError(DroolsError error) {
    	parserErrors.add(error);
    }

    public InternalKnowledgePackage getPackage() {
        return compiledPackage;
    }

    public DroolsError[] getBuilderErrors() {
        return builderErrors;
    }

    public String getPackageName() {
        return packageDescr == null ? null : packageDescr.getName();
    }

    public boolean isCompiled() {
        return compiledPackage != null;
    }

    public RuleInfo[] getRuleInfos() {
        if (ruleInfos == null) {
            List<RuleInfo> ruleInfosList = new ArrayList<RuleInfo>();
            if (packageDescr != null) {
                for (RuleDescr ruleDescr: packageDescr.getRules()) {
                    RuleInfo ruleInfo = new RuleInfo(packageDescr, compiledPackage, ruleDescr, dialectRegistry, sourcePathName);
                    ruleInfosList.add(ruleInfo);
                }
            }
            ruleInfos = (RuleInfo[]) ruleInfosList.toArray(new RuleInfo[0]);
        }
        return ruleInfos;
    }

    public RuleInfo getRuleInfo(int drlLineNumber) {
        RuleInfo[] ruleInfos = getRuleInfos();

        int ruleLine = -1;
        RuleInfo result = null;
        for (int i = 0; i < ruleInfos.length; i++) {
            int ruleDrlLineNumber = ruleInfos[i].getDrlLineNumber();
            if (ruleDrlLineNumber > ruleLine
                    && ruleDrlLineNumber <= drlLineNumber + 1) {
                ruleLine = ruleDrlLineNumber;
                result = ruleInfos[i];
            }
        }
        return result;
    }

    public DialectCompiletimeRegistry getDialectRegistry() {
        return dialectRegistry;
    }
    
    public IResource getResource() {
    	return resource;
    }
    
    public void setResource(IResource resource) {
    	this.resource = resource;
    }

    public static class RuleInfo {

        private final PackageDescr packageDescr;
        private final InternalKnowledgePackage compiledPackage;
        private final RuleDescr ruleDescr;
        private final DialectCompiletimeRegistry dialectRegistry;
        private final String sourcePathName;
        
        // cached entries
        private transient String className;
        private transient int consequenceJavaLineNumber = -1;
        private transient List<PatternInfo> patternInfos = null;
        private transient int endPatternsCharacter = -1;

        public RuleInfo(PackageDescr packageDescr, InternalKnowledgePackage compiledPackage, RuleDescr ruleDescr, DialectCompiletimeRegistry dialectRegistry, String sourcePathName) {
            if (ruleDescr == null) {
                throw new IllegalArgumentException("Null ruleDescr");
            }
            this.packageDescr = packageDescr;
            this.compiledPackage = compiledPackage;
            this.ruleDescr = ruleDescr;
            this.dialectRegistry = dialectRegistry;
            this.sourcePathName = sourcePathName;
        }

        public String getDialectName() {
            String dialectName = null;
            dialectName = ruleDescr.getAttributes().get("dialect").getValue();
            if (dialectName == null && packageDescr != null) {
                for (AttributeDescr attribute: packageDescr.getAttributes()) {
                    if ("dialect".equals(attribute.getName())) {
                        dialectName = (String) attribute.getValue();
                        break;
                    }
                }
            }
            return dialectName;
        }
        
        public Dialect getDialect() {
            String dialectName = getDialectName();
            if (dialectName == null) {
                return null;
            }
            return dialectRegistry.getDialect(dialectName);
        }

        public String getSourcePathName() {
            return sourcePathName;
        }

        public String getClassName() {
            // ruleDescr is only filled in during compilation
            if (!isCompiled()) {
                throw new IllegalArgumentException("Package has not been compiled");
            }
            if (className == null) {
                String packageName = getPackageName();
                className = (packageName == null ? "" : packageName + ".") + ruleDescr.getClassName();
            }
            return className;
        }
        
        public int getConsequenceStart() {
        	return endPatternsCharacter + 5;
        }

        public int getConsequenceEnd() {
        	return getRuleEnd();
        }
        
        public int getRuleStart() {
        	return ruleDescr.getStartCharacter() + 5;
        }

        public int getRuleEnd() {
        	return ruleDescr.getEndCharacter() - 4;
        }
        
        public int getDrlLineNumber() {
            return ruleDescr.getLine();
        }

        public int getConsequenceDrlLineNumber() {
            return ruleDescr.getConsequenceLine();
        }

        public int getConsequenceJavaLineNumber() {
            if (consequenceJavaLineNumber == -1) {
                if (!isCompiled()) {
                    throw new IllegalArgumentException("Package has not been compiled");
                }
                DialectRuntimeRegistry datas = compiledPackage.getDialectRuntimeRegistry();

                LineMappings mappings = datas.getLineMappings(className);
                consequenceJavaLineNumber = mappings.getOffset();

            }
            return consequenceJavaLineNumber;
        }

        public boolean isCompiled() {
            return compiledPackage != null;
        }

        public String getPackageName() {
            return packageDescr == null ? null : packageDescr.getName();
        }

        public String getRuleName() {
            return ruleDescr.getName();
        }
        
        public RuleImpl getRule() {
        	return compiledPackage.getRule(getRuleName());
        }
        
        public String toString() {
        	return ruleDescr.toString();
        }
        
        public List<PatternInfo> getPatternInfos() {
        	if (patternInfos == null && isCompiled()) {
        		patternInfos = findPatternInfos();
        	}
        	return patternInfos;
        }
        
        private List<PatternInfo> findPatternInfos() {
        	List<PatternInfo> patternInfos = new ArrayList<PatternInfo>();
        	traversePatternTree(patternInfos, getRule().getLhs().getChildren(), ruleDescr.getLhs().getDescrs());
        	return patternInfos;
        }
        
        @SuppressWarnings("unchecked")
        private void traversePatternTree(List<PatternInfo> patternInfos, List<RuleConditionElement> ruleElements, List<BaseDescr> lhsDescrs) {
        	if (ruleElements.size() != lhsDescrs.size()) {
        		throw new RuntimeException("Cannot traverse pattern tree");
        	}
        	for (int i = 0; i < ruleElements.size(); i++) {
        		RuleConditionElement ruleElement = ruleElements.get(i);
        		BaseDescr lhsDescr = lhsDescrs.get(i);
        		
        		if (ruleElement instanceof Pattern && lhsDescr instanceof PatternDescr) {
        			patternInfos.add(new PatternInfo((PatternDescr)lhsDescr, (Pattern)ruleElement));
        			endPatternsCharacter = Math.max(endPatternsCharacter, ((PatternDescr)lhsDescr).getEndCharacter());
        		} else if (ruleElement instanceof GroupElement && lhsDescr instanceof ConditionalElementDescr) {
        			traversePatternTree(patternInfos, ((GroupElement)ruleElement).getChildren(), (List<BaseDescr>)((ConditionalElementDescr)lhsDescr).getDescrs());
        		} else {
            		throw new RuntimeException("Cannot traverse pattern tree");
        		}
        	}
        }
    }
    
    public static class PatternInfo {
        private final PatternDescr patternDescr;
        private final Pattern pattern;
        
        private PatternInfo(PatternDescr patternDescr, Pattern pattern) {
        	this.patternDescr = patternDescr;
        	this.pattern = pattern;
        }
    	
        public String getPatternTypeName() {
        	ObjectType objectType = pattern.getObjectType();
        	return objectType instanceof ClassObjectType ? ((ClassObjectType)objectType).getClassType().getName() : "";
        }
        
        public int getStart() {
        	return patternDescr.getStartCharacter();
        }
        
        public int getEnd() {
        	return patternDescr.getEndCharacter();
        }
        
        public String toString() {
        	return patternDescr.toString();
        }
    }

    public FunctionInfo[] getFunctionInfos() {
        if (functionInfos == null) {
            List<FunctionInfo> functionInfosList = new ArrayList<FunctionInfo>();
            if (packageDescr != null) {
                for (FunctionDescr functionDescr: packageDescr.getFunctions()) {
                    FunctionInfo functionInfo = new FunctionInfo(packageDescr, compiledPackage, functionDescr, sourcePathName);
                    functionInfosList.add(functionInfo);
                }
            }
            functionInfos = (FunctionInfo[]) functionInfosList.toArray(new FunctionInfo[0]);
        }
        return functionInfos;
    }

    public FunctionInfo getFunctionInfo(int drlLineNumber) {
        FunctionInfo[] functionInfos = getFunctionInfos();
        int functionLine = -1;
        FunctionInfo result = null;
        for (int i = 0; i < functionInfos.length; i++) {
            int functionDrlLineNumber = functionInfos[i].getDrlLineNumber();
            if (functionDrlLineNumber > functionLine
                    && functionDrlLineNumber <= drlLineNumber + 1) {
                functionLine = functionDrlLineNumber;
                result = functionInfos[i];
            }
        }
        return result;
    }

    public static class FunctionInfo {

        private final PackageDescr packageDescr;
        private final InternalKnowledgePackage compiledPackage;
        private final FunctionDescr functionDescr;
        private final String sourcePathName;

        // cached entries
        private transient String className;
        private transient int javaLineNumber = -1;

        public FunctionInfo(PackageDescr packageDescr, InternalKnowledgePackage compiledPackage, FunctionDescr functionDescr, String sourcePathName) {
            if (functionDescr == null) {
                throw new IllegalArgumentException("Null functionDescr");
            }
            this.packageDescr = packageDescr;
            this.compiledPackage = compiledPackage;
            this.functionDescr = functionDescr;
            this.sourcePathName = sourcePathName;
        }

        public String getSourcePathName() {
            return sourcePathName;
        }

        public String getClassName() {
            // functionDescr is only filled in during compilation
            if (!isCompiled()) {
                throw new IllegalArgumentException("Package has not been compiled");
            }
            if (className == null) {
                className = functionDescr.getClassName();
            }
            return className;
        }

        public int getDrlLineNumber() {
            return functionDescr.getLine();
        }

        public int getJavaLineNumber() {
            if (javaLineNumber == -1) {
                if (!isCompiled()) {
                    throw new IllegalArgumentException("Package has not been compiled");
                }
                javaLineNumber = compiledPackage.getDialectRuntimeRegistry().getLineMappings(className).getOffset();
            }
            return javaLineNumber;
        }

        public String getPackageName() {
            return packageDescr == null ? null : packageDescr.getName();
        }

        public String getFunctionName() {
            return functionDescr.getName();
        }

        public int getFunctionStart() {
        	return functionDescr.getStartCharacter() + 8;
        }

        public int getFunctionEnd() {
        	return functionDescr.getEndCharacter() - 1;
        }

        public boolean isCompiled() {
            return compiledPackage != null;
        }
    }

}
