/*
 * Copyright 2005 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.guvnor.server.util;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.drools.compiler.DrlParser;
import org.drools.compiler.DroolsParserException;
import org.drools.guvnor.client.common.AssetFormats;
import org.drools.lang.descr.RuleDescr;

/**
 * This class imports legacy DRL into a structure suitable for storing more
 * normalised in the repository.
 */
public class ClassicDRLImporter {

    private String        source;

    private String        packageName;

    private List<Asset>   assets = new ArrayList<Asset>();

    private StringBuilder header;

    private boolean       usesDSL;

    public ClassicDRLImporter(InputStream in) throws IOException, DroolsParserException {
        String line = "";
        StringBuilder drl = new StringBuilder();
        BufferedReader reader = new BufferedReader( new InputStreamReader( in ) );
        while ( (line = reader.readLine()) != null ) {
            drl.append( "\n" + line );
        }
        this.source = drl.toString();

        parse();
    }

    private void parse() throws DroolsParserException {
        StringTokenizer lines = new StringTokenizer( source, "\r\n" );

        header = new StringBuilder();
        Pattern functionPattern = Pattern.compile( "function\\s+.*\\s+(.*)\\(.*\\).*" );
        while ( lines.hasMoreTokens() ) {
            String line = lines.nextToken().trim();
            if ( line.startsWith( "package" ) ) {
                packageName = getPackage( line );
            } else if ( line.startsWith( "rule" ) ) {
                String ruleName = getRuleName( line );
                StringBuilder currentRule = new StringBuilder();
                laConsumeToEnd( lines, currentRule, "end", false );
                addRule( ruleName, currentRule );

            } else if ( line.startsWith( "function" ) ) {
                String functionName = getFuncName( functionPattern, line );
                StringBuilder currentFunc = new StringBuilder();

                int counter = 0;

                currentFunc.append( line + "\n" );

                counter = countBrackets( counter, line );

                if ( counter > 0 ) {
                    laConsumeBracketsToEnd( counter, lines, currentFunc );
                }
                addFunction( functionName, currentFunc );

            } else if ( line.startsWith( "/*" ) ) {

                StringBuilder comment = new StringBuilder();
                comment.append( line + "\n" );
                laConsumeToEnd( lines, comment, "*/", true );

                header.append( comment );

            } else if ( line.startsWith( "expander" ) ) {

                usesDSL = true;
            } else {

                header.append( line );
                header.append( "\n" );
            }
        }
    }

    private void addFunction(String functionName, StringBuilder currentFunc) {
        this.assets.add( new Asset( functionName, currentFunc.toString(), AssetFormats.FUNCTION ) );
    }

    private String getFuncName(Pattern functionPattern, String line) {

        Matcher m = functionPattern.matcher( line );
        m.matches();
        return m.group( 1 );
    }

    /**
     * Consumes function to the ending curly bracket.
     *
     * @param lines
     * @param currentFunc
     */
    private void laConsumeBracketsToEnd(int counter, StringTokenizer lines, StringBuilder currentFunc) {
        /*
         * Check if the first line contains matching amount of brackets.
         */
        boolean multilineIsOpen = false;
        // Start counting brackets
        while ( lines.hasMoreTokens() ) {
            String line = lines.nextToken();

            currentFunc.append( line );
            currentFunc.append( "\n" );

            if ( multilineIsOpen ) {
                int commentEnd = line.indexOf( "*/" );

                if ( commentEnd != -1 ) {
                    multilineIsOpen = false;
                    line = line.substring( commentEnd );
                }
            } else {
                multilineIsOpen = checkIfMultilineCommentStarts( line );
                line = removeComments( line );
            }

            if ( !multilineIsOpen ) {
                counter = countBrackets( counter, line );
            }

            if ( counter == 0 ) {
                break;
            }
        }
    }

    /**
     * @param line
     * @return
     */
    private boolean checkIfMultilineCommentStarts(String line) {

        int commentMultiLineStart = line.indexOf( "/*" );
        int commentMultiLineEnd = line.indexOf( "*/" );
        //        int commentSingleLine = line.indexOf( "//" );

        if ( commentMultiLineStart != -1 && commentMultiLineEnd == -1 ) {
            return true;
        }
        return false;

    }

    private int countBrackets(int counter, String line) {
        char[] chars = line.toCharArray();
        for ( int i = 0; i < chars.length; i++ ) {
            if ( chars[i] == '{' ) {
                counter++;
            } else if ( chars[i] == '}' ) {
                counter--;
            }
        }
        return counter;
    }

    private String removeComments(String line) {

        int commentMultiLineStart = line.indexOf( "/*" );
        int commentMultiLineEnd = line.indexOf( "*/" );
        int commentSingleLine = line.indexOf( "//" );

        // Single line comment is first
        // Case: some code // /* */
        // Another case: some code // No comments
        if ( commentSingleLine != -1 && commentMultiLineStart > commentSingleLine ) {
            return line.substring( 0, commentSingleLine );
        }

        // There is only a start for the multiline comment.
        // Case: some code here /* commented out
        if ( commentMultiLineStart != -1 && commentMultiLineEnd == -1 ) {
            return line.substring( 0, commentMultiLineStart );
        }

        // Two ends are on the same line
        // some code /* comment */ some code
        if ( commentMultiLineStart != -1 && commentMultiLineEnd != -1 ) {

            line = line.substring( 0, commentMultiLineStart ) + line.substring( commentMultiLineEnd + "*/".length());
            return line;
        }

        return line;
    }

    private void laConsumeToEnd(StringTokenizer lines, StringBuilder currentRule, String end, boolean addLastLine) {
        String line;
        while ( lines.hasMoreTokens() ) {
            line = lines.nextToken();
            if ( line.trim().startsWith( end ) ) {
                if ( addLastLine ) {
                    currentRule.append( line + "\n" );
                }
                break;
            }
            currentRule.append( line );
            currentRule.append( "\n" );
        }
    }

    private void addRule(String ruleName, StringBuilder currentRule) {
        ruleName = ruleName.replace( '\'', ' ' );
        if ( this.isDSLEnabled() ) {
            this.assets.add( new Asset( ruleName, currentRule.toString(), AssetFormats.DSL_TEMPLATE_RULE ) );
        } else {
            this.assets.add( new Asset( ruleName, currentRule.toString(), AssetFormats.DRL ) );
        }
    }

    /** Get the rule name from a declaration line */
    public static String getRuleName(String line) throws DroolsParserException {
        DrlParser parser = new DrlParser();
        line = line + "\n when\n then \n end";
        RuleDescr rule = (RuleDescr) parser.parse( line ).getRules().get( 0 );
        return rule.getName();
    }

    private String getPackage(String line) throws DroolsParserException {
        DrlParser parser = new DrlParser();
        return parser.parse( line ).getName();

    }

    public List<Asset> getAssets() {
        return this.assets;
    }

    public String getPackageName() {
        return this.packageName;
    }

    public String getPackageHeader() {
        return this.header.toString();
    }

    public boolean isDSLEnabled() {
        return this.usesDSL;
    }

    /**
     * Holds a rule to import. The content does not include the "end".
     */
    public static class Asset {

        public Asset(String name, String content, String format) {
            this.name = name;
            this.content = content;
            this.format = format;
        }

        public String format;
        public String name;
        public String content;
    }

    /**
     * This merges the toMerge new schtuff into the existing. Line by line, simple stuff.
     */
    public static String mergeLines(String existing, String toMerge) {

        if ( toMerge == null || toMerge.equals( "" ) ) {
            return existing;
        }
        if ( existing == null || existing.equals( "" ) ) {
            return toMerge;
        }
        Set existingLines = new HashSet<String>( Arrays.asList( existing.split( "\n" ) ) );
        String[] newLines = toMerge.split( "\n" );
        for ( int i = 0; i < newLines.length; i++ ) {
            String newLine = newLines[i].trim();

            if ( !newLine.equals( "" ) && !existingLines.contains( newLines[i].trim() ) ) {
                existing = existing + "\n" + newLines[i];
            }
        }
        return existing;

    }

}
