/*
 * 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.debug.core;

import java.util.HashMap;
import java.util.Map;

import org.drools.eclipse.DRLInfo;
import org.drools.eclipse.DRLInfo.FunctionInfo;
import org.drools.eclipse.DRLInfo.RuleInfo;
import org.drools.eclipse.DroolsEclipsePlugin;
import org.eclipse.core.resources.IMarker;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.jdt.internal.debug.core.breakpoints.JavaLineBreakpoint;

public class DroolsLineBreakpoint extends JavaLineBreakpoint {

    private static final String DIALECT = "org.drools.eclipse.debug.core.breakpointDialect";

    /**
     * Default constructor is required for the breakpoint manager
     * to re-create persisted breakpoints. After instantiating a breakpoint,
     * the <code>setMarker(...)</code> method is called to restore
     * this breakpoint's attributes.
     */
    public DroolsLineBreakpoint() {
        super();
    }

    /**
     * Constructs a line breakpoint on the given resource at the given
     * line number.
     *
     * @param resource file on which to set the breakpoint
     * @param lineNumber line number of the breakpoint
     * @throws CoreException if unable to create the breakpoint
     */
    public DroolsLineBreakpoint(IResource resource, int lineNumber)
            throws CoreException {
        super(resource, "", -1, -1, -1, 0, true, createAttributesMap(lineNumber), IDroolsDebugConstants.DROOLS_MARKER_TYPE);
        setJavaBreakpointProperties();
    }

    private static Map<String, Object> createAttributesMap(int lineNumber) {
        Map<String, Object> map = new HashMap<String, Object>();
        map.put( IDroolsDebugConstants.DRL_LINE_NUMBER, new Integer( lineNumber ) );
        return map;
    }

    public int getDRLLineNumber() {
        return getMarker().getAttribute( IDroolsDebugConstants.DRL_LINE_NUMBER, -1 );
    }

    public String getModelIdentifier() {
        return IDroolsDebugConstants.ID_DROOLS_DEBUG_MODEL;
    }

    public String getDialectName() {
        return getMarker().getAttribute( DIALECT, "Unknown");
    }

    public Map<String, Integer> getFileRuleMappings() {
        String packedInfo = getMarker().getAttribute( IDroolsDebugConstants.DRL_RULES, "");
        return unpackRuleMapping( packedInfo );
    }

    public void setJavaBreakpointProperties() throws CoreException {
        IMarker marker = getMarker();
        int drlLineNumber = getDRLLineNumber();
        if ( marker.exists() ) {
            try {
                DRLInfo drlInfo = DroolsEclipsePlugin.getDefault().parseResource( marker.getResource(), true );

                RuleInfo[] ruleInfos = drlInfo.getRuleInfos();

                StringBuffer rb = new StringBuffer();
                for (int i=0;i<ruleInfos.length; i++) {
                    int line = ruleInfos[i].getConsequenceDrlLineNumber();
                    String ruleid = ruleInfos[i].getClassName()+":"+line;
                    rb.append(ruleid);
                    if (i<ruleInfos.length-1) {
                        rb.append(";");
                    }
                }

                marker.setAttribute( IDroolsDebugConstants.DRL_RULES, rb.toString());

                marker.setAttribute( TYPE_NAME, getRuleClassName( drlInfo, marker.getResource().toString(), drlLineNumber ) );
                int ruleLineNumber = getRuleLineNumber( drlInfo, marker.getResource().toString(), drlLineNumber );
                marker.setAttribute( IMarker.LINE_NUMBER, ruleLineNumber );
                marker.setAttribute( DIALECT, getDialect( drlInfo, drlLineNumber ) );

            } catch ( Throwable t ) {
                throw new CoreException( new Status( IStatus.ERROR, DroolsEclipsePlugin.getUniqueIdentifier(), 0,
                                                     "Cannot determine ruleInfo " + marker.getResource() + " " + drlLineNumber, t ) );
            }
        }
    }

    private String getDialect(DRLInfo info, int drlLineNumber) {
        if ( info != null ) {
            return info.getRuleInfo( drlLineNumber ).getDialectName();
        }
        return null;
    }

    private String getRuleClassName(DRLInfo drlInfo, String resource, int lineNumber) throws CoreException {
        if ( drlInfo != null ) {
            RuleInfo ruleInfo = drlInfo.getRuleInfo( lineNumber );
            if ( ruleInfo != null ) {
                return ruleInfo.getClassName();
            }
            FunctionInfo functionInfo = drlInfo.getFunctionInfo( lineNumber );
            if ( functionInfo != null ) {
                return functionInfo.getClassName();
            }
        }
        throw new CoreException( new Status( IStatus.ERROR, DroolsEclipsePlugin.getUniqueIdentifier(), 0,
                                             "Cannot determine ruleClassName for " + resource + " " + lineNumber, null ) );
    }

    private int getRuleLineNumber(DRLInfo drlInfo, String resource, int lineNumber) throws CoreException {
        if ( drlInfo != null ) {
            RuleInfo ruleInfo = drlInfo.getRuleInfo( lineNumber );
            if ( ruleInfo != null ) {
                if ( ruleInfo.getConsequenceDrlLineNumber() < lineNumber ) {

                    int line = ruleInfo.getConsequenceJavaLineNumber()
                                            + (lineNumber - ruleInfo.getConsequenceDrlLineNumber());
                    if (ruleInfo.getDialectName() != null && ruleInfo.getDialectName().equals( "mvel" )) {
                        return line;
                    }
                    return line+1;
                }
            }
            FunctionInfo functionInfo = drlInfo.getFunctionInfo( lineNumber );
            if ( functionInfo != null ) {
                return functionInfo.getJavaLineNumber()
                    + (lineNumber - functionInfo.getDrlLineNumber());
            }
        }
        throw new CoreException( new Status( IStatus.ERROR, DroolsEclipsePlugin.getUniqueIdentifier(), 0,
                                             "Cannot determine ruleLineNumber for " + resource + " " + lineNumber, null ) );
    }

    public String getRuleName() {
        IMarker marker = getMarker();
        if ( marker.exists() ) {
                try {
                    return (String) marker.getAttribute( TYPE_NAME);
                } catch ( CoreException e ) {
                    DroolsEclipsePlugin.log( e );
                }
        }
        return null;
    }

    private final static Map<String, Integer> unpackRuleMapping(String input) {
        Map<String, Integer> map = new HashMap<String, Integer>();
        String[] rules = input.split( "\\;");
        for (int i=0; i<rules.length; i++) {
            if (rules[i].length()>0) {
                String[] inf = rules[i].split( "\\:" );
                map.put( inf[0], Integer.valueOf( inf[1] ) );
            }
        }
        return map;
    }


}
