/*
 * Copyright 2005 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.decisiontable.parser;

import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import java.util.Locale;

import org.drools.decisiontable.parser.xls.PropertiesSheetListener.CaseInsensitiveMap;
import org.drools.template.model.Condition;
import org.drools.template.model.Consequence;
import org.drools.template.model.DRLOutput;
import org.drools.template.model.Import;
import org.drools.template.model.Package;
import org.drools.template.model.Rule;
import org.drools.template.parser.DataListener;
import org.drools.template.parser.DecisionTableParseException;
import org.junit.Ignore;
import org.junit.Test;

import static org.junit.Assert.*;

/**
 *
 * Test an excel file.
 * 
 * Assumes it has a sheet called "Decision Tables" with a rule table identified
 * by a "RuleTable" cell
 */
public class RuleWorksheetParseTest {

    @Test
    public void testBasicWorkbookProperties() throws Exception {

        final InputStream stream = RuleWorksheetParseTest.class.getResourceAsStream( "/data/BasicWorkbook.xls" );

        final RuleSheetListener listener = getRuleSheetListener( stream );

        final CaseInsensitiveMap props = listener.getProperties();
        assertNotNull( props );
        assertEquals( "data", props.getSingleProperty( "RuleSet" ) );
        assertEquals( "someMisc",  props.getSingleProperty( "misc" ) );
        /*
         * System.out.println("Here are the global properties...");
         * listener.getProperties().list(System.out);
         */
    }

    @Test
    public void testComplexWorkbookProperties() throws Exception {
        final InputStream stream = RuleWorksheetParseTest.class.getResourceAsStream( "/data/ComplexWorkbook.xls" );
        final RuleSheetListener listener = getRuleSheetListener( stream );

        final CaseInsensitiveMap props = listener.getProperties();
        assertNotNull( props );
        final String ruleSetName = props.getSingleProperty( "RuleSet" );
        assertEquals( "data", ruleSetName );
    }

    @Test
    public void testWorkbookParse() throws Exception {
        final InputStream stream = RuleWorksheetParseTest.class.getResourceAsStream( "/data/BasicWorkbook.xls" );
        final RuleSheetListener listener = getRuleSheetListener( stream );

        final Package ruleset = listener.getRuleSet();
        assertNotNull( ruleset );

        final Rule firstRule = (Rule) ruleset.getRules().get( 0 );
        assertNotNull( firstRule.getSalience() );
        assertTrue( Integer.parseInt( firstRule.getSalience() ) > 0 );

        // System.out.println(ruleset.toXML());

        assertEquals( "data", ruleset.getName() );
        assertEquals( 3, ruleset.getImports().size() );
        assertEquals( 6, ruleset.getRules().size() );

        // check imports
        Import imp = (Import) ruleset.getImports().get( 0 );
        assertEquals( "blah.class1", imp.getClassName() );
        imp = (Import) ruleset.getImports().get( 1 );
        assertEquals( "blah.class2", imp.getClassName() );
        imp = (Import) ruleset.getImports().get( 2 );
        assertEquals( "lah.di.dah", imp.getClassName() );

        // check rules
        Rule rule = (Rule) ruleset.getRules().get( 0 );
        Condition cond = (Condition) rule.getConditions().get( 0 );
        assertEquals( "Foo(myObject.getColour().equals(red), myObject.size () > 12\\\")",
                cond.getSnippet() );

        Consequence cons = (Consequence) rule.getConsequences().get( 0 );
        assertNotNull( cons );
        assertEquals( "myObject.setIsValid(Y);", cons.getSnippet() );

        rule = (Rule) ruleset.getRules().get( 5 );
        cond = (Condition) rule.getConditions().get( 1 );
        assertEquals( "myObject.size () > 7", cond.getSnippet() );
        cons = (Consequence) rule.getConsequences().get( 0 );
        assertEquals( "myObject.setIsValid(10-Jul-1974)", cons.getSnippet() );

    }

    private RuleSheetListener listener;
    private int row;

    private void makeRuleSet(){
        listener = new DefaultRuleSheetListener();
        listener.startSheet( "bad_sheet" );
        row = 1;
        listener.newRow( row, 2 );
        listener.newCell( row, 1, "RuleSet",  DataListener.NON_MERGED );
        listener.newCell( row, 2, "myRuleSet", DataListener.NON_MERGED );
    }

    private void makeAttribute( String key, String val){
        row++;
        listener.newRow( row, 2 );
        listener.newCell( row, 1, key,  DataListener.NON_MERGED );
        listener.newCell( row, 2, val,  DataListener.NON_MERGED );
    }

    private void makeRuleTable(){
        listener.newRow( 10, 1 );
        listener.newCell(10, 1, "RuleTable myRuleTable",  DataListener.NON_MERGED );
    }

    private void makeRow( int row, String... values ) throws DecisionTableParseException {
        listener.newRow( row, values.length );
        for( int i = 0; i < values.length; i++ ){
            if( values[i] != null ){
                listener.newCell( row, i+1, values[i],  DataListener.NON_MERGED );
            }
        }
    }

    /**
     * Duplications of several columns are not permitted: NO-LOOP/U.
     */
    @Test
    public void testTooManyColumnsNoLoop() {
        try {
            makeRuleSet();
            makeRuleTable();
            makeRow( 11, "C", "C", "A", "U", "U" );
            listener.finishSheet();
            fail( "should have failed" );
        } catch( DecisionTableParseException e ) {
            String badCell = RuleSheetParserUtil.rc2name( 11, 5 );
            System.err.println( e.getMessage() );
            assertTrue( e.getMessage().contains( badCell ) );
        }
    }

    /**
     * Duplications of several columns are not permitted : PRIORITY/P.
     */
    @Test
    public void testTooManyColumnsPriority() {
        try {
            makeRuleSet();
            makeRuleTable();
            makeRow( 11, "C", "C", "A", "PRIORITY", "P" );
            listener.finishSheet();
            fail( "should have failed" );
        } catch( DecisionTableParseException e ) {
            String badCell = RuleSheetParserUtil.rc2name( 11, 5 );
            System.err.println( e.getMessage() );
            assertTrue( e.getMessage().contains( badCell ) );
        }
    }

    /**
     * Column headers must be valid.
     */
    @Test
    public void testBadColumnHeader() {
        try {
            makeRuleSet();
            makeRuleTable();
            makeRow( 11, "Condition", "CONDITION", "A", "SMURF", "P" );
            listener.finishSheet();
            fail( "should have failed" );
        } catch( DecisionTableParseException e ) {
            String badCell = RuleSheetParserUtil.rc2name( 11, 4 );
            System.err.println( e.getMessage() );
            assertTrue( e.getMessage().contains( badCell ) );
        }
    }

    /**
     * Must have a type for pattern below a condition, not a snippet.
     */
    @Test
    public void testMissingCondition() {
        try {
            makeRuleSet();
            makeRuleTable();
            makeRow( 11, "C",   "C", "C",   "A", "A" );
            makeRow( 12, "attr == $param", "attr == $param", "attr == $param", "action();", "action();" );
            listener.finishSheet();
            fail( "should have failed" );
        } catch( DecisionTableParseException e ) {
            String badCell = RuleSheetParserUtil.rc2name( 12, 1 );
            System.err.println( e.getMessage() );
            assertTrue( e.getMessage().contains( badCell ) );
        }
    }

    /**
     * Spurious code snippet.
     */
    @Test
    public void testSpuriousCodeSnippet() {
        try {
            makeRuleSet();
            makeRuleTable();
            makeRow( 11, "C",              "C",              "A" );
            makeRow( 12, "Foo",            "Foo" );
            makeRow( 13, "attr == $param", "attr == $param", "action();", "attr > $param" );
            makeRow( 15, "1",              "2",              "" );
            listener.finishSheet();
            fail( "should have failed" );
        } catch( DecisionTableParseException e ) {
            String badCell = RuleSheetParserUtil.rc2name( 13, 4 );
            System.err.println( e.getMessage() );
            assertTrue( e.getMessage().contains( badCell ) );
        }
    }

    /**
     * Incorrect priority - not numeric
     */
    @Test
    public void testIncorrectPriority() {
        try {
            makeRuleSet();
            makeRuleTable();
            makeRow( 11, "C",              "A",         "P" );
            makeRow( 12, "Foo",            "Foo"            );
            makeRow( 13, "attr == $param", "x"              );
            makeRow( 15, "1",              "show()",   "12E" );
            listener.finishSheet();
            fail( "should have failed" );
        } catch( DecisionTableParseException e ) {
            String badCell = RuleSheetParserUtil.rc2name( 15, 3 );
            System.err.println( e.getMessage() );
            assertTrue( e.getMessage().contains( badCell ) );
        }
    }

    /**
     * Must not have snippet for attribute
     */
    @Test
    public void testSnippetForAttribute() {
        try {
            makeRuleSet();
            makeRuleTable();
            makeRow( 11, "C",              "A",         "G" );
            makeRow( 12, "Foo",            "Foo"            );
            makeRow( 13, "attr == $param", "x",       "XXX" );
            makeRow( 15, "1",              "show()",   "10" );
            listener.finishSheet();
            fail( "should have failed" );
        } catch( DecisionTableParseException e ) {
            String badCell = RuleSheetParserUtil.rc2name( 13, 3 );
            System.err.println( e.getMessage() );
            assertTrue( e.getMessage().contains( badCell ) );
        }
    }

    /**
     * Check correct rendering of string-valued attribute
     */
    @Test
    public void testRuleAttributeRendering() {
        makeRuleSet();
        makeRuleTable();
        makeRow( 11, "C",              "A",         "G"     );
        makeRow( 12, "Foo",            "Foo"                );
        makeRow( 13, "attr == $param", "x"                  );
        makeRow( 15, "1",              "show()",   "foo bar" );
        makeRow( 16, "2",              "list()",   "\"10\" group\"" );
        listener.finishSheet();
        Package p = listener.getRuleSet();
        DRLOutput dout = new DRLOutput();
        p.renderDRL(dout);
        String drl = dout.getDRL();
        // System.out.println( drl );
        assertTrue( drl.contains( "agenda-group \"foo bar\"" ) );
        assertTrue( drl.contains( "agenda-group \"10\\\" group\"" ) );
    }

    /**
     * Duplicate package level attribute
     */
    @Test
    public void testDuplicatePackageAttribute() {
        try {
            makeRuleSet();
            makeAttribute( "agenda-group", "agroup" );  // B3, C3
            makeAttribute( "agenda-group", "bgroup" );  // B3. B4
            makeRuleTable();
            makeRow( 11, "C",              "A",         "P" );
            makeRow( 12, "Foo",            "Foo"            );
            makeRow( 13, "attr == $param", "x"              );
            makeRow( 15, "1",              "show()",   "10" );
            listener.finishSheet();
            Package p = listener.getRuleSet();
            DRLOutput dout = new DRLOutput();
            p.renderDRL(dout);
            String drl = dout.getDRL();
            // System.out.println( drl );
            fail( "should have failed" );
        } catch( DecisionTableParseException e ) {
            System.err.println( e.getMessage() );
            assertTrue( e.getMessage().contains( "C3, C4" ) );
        }
    }

    /**
     * Check correct rendering of package level attributes
     */
    @Test
    public void testPackageAttributeRendering() {
        makeRuleSet();
        makeAttribute( "NO-LOOP", "true" );
        makeAttribute( "agenda-group", "agroup" );
        makeRuleTable();
        makeRow( 11, "C",              "A",         "P"     );
        makeRow( 12, "foo:Foo",        "foo"                );
        makeRow( 13, "attr == $param", "x($param)"          );
        makeRow( 15, "1",              "1",         "100"   );
        listener.finishSheet();
        Package p = listener.getRuleSet();
        DRLOutput dout = new DRLOutput();
        p.renderDRL(dout);
        String drl = dout.getDRL();
        // System.out.println( drl );
        assertTrue( drl.contains( "no-loop true" ) );
        assertTrue( drl.contains( "agenda-group \"agroup\"" ) );
    }

    /**
     * Must have a code snippet in an action.
     */
    @Test
    public void testMissingCodeSnippetAction() {
        try {
            makeRuleSet();
            makeRuleTable();
            makeRow( 11, "C",              "A"          );
            makeRow( 12, "foo: Foo",       "Bar()"          );
            makeRow( 13, "attr == $param"  );
            makeRow( 15, "1",              "1"          );
            makeRow( 16, "2",              "2"          );
            listener.finishSheet();
            Package p = listener.getRuleSet();
            DRLOutput dout = new DRLOutput();
            p.renderDRL(dout);
            String drl = dout.getDRL();
            System.out.println( drl );
            fail( "should have failed" );
        } catch( DecisionTableParseException e ) {
            String badCell = RuleSheetParserUtil.rc2name( 13, 2 );
            System.err.println( e.getMessage() );
            assertTrue( e.getMessage().contains( badCell ) );
        }
    }

    @Test
    public void testMetadata() {
        makeRuleSet();
        makeRuleTable();
        makeRow( 11, "C",              "A",              "@",              "@"  );
        makeRow( 12, "foo: Foo",       "foo"          );
        makeRow( 13, "attr == $param", "goaway($param)", "Author($param)", "Version($1-$2)" );
        makeRow( 15, "1",              "1",              "J.W.Goethe",     "3,14"       );
        makeRow( 16, "2",              "2",              "",               ""       );
        listener.finishSheet();
        Package p = listener.getRuleSet();
        DRLOutput dout = new DRLOutput();
        p.renderDRL(dout);
        String drl = dout.getDRL();

        assertTrue( drl.contains( "@Author(J.W.Goethe)" ) );
        assertTrue( drl.contains( "@Version(3-14)" ) );
        assertFalse( drl.contains( "@Author()" ) );
        assertFalse( drl.contains( "@Version(-)" ) );
    }

    @Test
    public void testQuoteEscapingEnabled() throws Exception {
        final InputStream stream = RuleWorksheetParseTest.class.getResourceAsStream( "/data/QuoteEscapeEnabledWorkbook.xls" );
        final RuleSheetListener listener = getRuleSheetListener( stream );

        final Package ruleset = listener.getRuleSet();
        assertNotNull( ruleset );
        DRLOutput dout = new DRLOutput();
        ruleset.renderDRL(dout);
        String drl = dout.getDRL();
        System.out.println(drl);
        
        // check rules
        Rule rule = ruleset.getRules().get( 0 );
        Condition cond = rule.getConditions().get( 0 );
        assertEquals( "Foo(myObject.getColour().equals(red), myObject.size () > 12\\\")",
                cond.getSnippet() );
    }

    @Test
    public void testQuoteEscapingDisabled() throws Exception {
        final InputStream stream = RuleWorksheetParseTest.class.getResourceAsStream( "/data/QuoteEscapeDisabledWorkbook.xls" );
        final RuleSheetListener listener = getRuleSheetListener( stream );

        final Package ruleset = listener.getRuleSet();
        assertNotNull( ruleset );
        DRLOutput dout = new DRLOutput();
        ruleset.renderDRL(dout);
        String drl = dout.getDRL();
        System.out.println(drl);
        
        // check rules
        Rule rule = (Rule) ruleset.getRules().get( 0 );
        Condition cond = (Condition) rule.getConditions().get( 0 );
        assertEquals( "Foo(myObject.getColour().equals(red), myObject.size () > \"12\")",
                cond.getSnippet() );
        rule = ruleset.getRules().get( 1 );
        cond = rule.getConditions().get( 0 );
        assertEquals( "Foo(myObject.getColour().equals(blue), myObject.size () > 12\")",
                cond.getSnippet() );
    }

    @Test
    public void testSalienceRange() throws Exception {
        // DROOLS-1225
        final InputStream stream = RuleWorksheetParseTest.class.getResourceAsStream( "/data/SalienceRangeWorkbook.xls" );
        final RuleSheetListener listener = getRuleSheetListener( stream );

        final Package ruleset = listener.getRuleSet();
        assertNotNull( ruleset );
        DRLOutput dout = new DRLOutput();
        ruleset.renderDRL(dout);
        String drl = dout.getDRL();
        System.out.println(drl);

        // check rules
        List<Rule> rules = ruleset.getRules();
        assertEquals( "10000",
                      rules.get(0).getSalience() );
        assertEquals( "9999",
                      rules.get(1).getSalience() );
    }

    @Test
    public void testSalienceOutOfRange() throws Exception {
        // DROOLS-1225
        final InputStream stream = RuleWorksheetParseTest.class.getResourceAsStream( "/data/SalienceOutOfRangeWorkbook.xls" );
        try {
            final RuleSheetListener listener = getRuleSheetListener( stream );
            fail( "should have failed" );
        } catch (DecisionTableParseException e) { }
    }

    /**
     * See if it can cope with odd shaped rule table, including missing
     * conditions. Also is not "sequential".
     */
    @Test
    public void testComplexWorksheetMissingConditionsInLocaleEnUs() throws Exception {
        Locale originalDefaultLocale = Locale.getDefault();
        Locale.setDefault(Locale.US);
        doComplexWorksheetMissingConditions();
        Locale.setDefault(originalDefaultLocale);
    }

    @Test @Ignore // TODO JBRULES-2880 TIRELLI: Ignore test while we decide what to do in order to solve i18n issues
    public void testComplexWorksheetMissingConditionsInLocaleFrFr() throws Exception {
        Locale originalDefaultLocale = Locale.getDefault();
        Locale.setDefault(Locale.FRANCE);
        doComplexWorksheetMissingConditions();
        Locale.setDefault(originalDefaultLocale);
    }

    private void doComplexWorksheetMissingConditions() throws IOException {
        final InputStream stream = RuleWorksheetParseTest.class.getResourceAsStream( "/data/ComplexWorkbook.xls" );
        final RuleSheetListener listener = getRuleSheetListener( stream );

        final Package ruleset = listener.getRuleSet();
        assertEquals( 6, ruleset.getRules().size() );
        assertEquals( 0, ruleset.getImports().size() );

        Rule rule = (Rule) ruleset.getRules().get( 0 );
        assertEquals( 3, rule.getConditions().size() );
        assertEquals( 2, rule.getConsequences().size() );
        final Consequence cons = (Consequence) rule.getConsequences().get( 1 );
        assertEquals( "myObject.setIsValid(1, 2)", cons.getSnippet() );
        final Condition con = (Condition) rule.getConditions().get( 2 );
        assertEquals( "myObject.size() < $3.00", con.getSnippet() );

        rule = (Rule) ruleset.getRules().get( 4 );

        // this should have less conditions
        assertEquals( 1, rule.getConditions().size() );

        rule = (Rule) ruleset.getRules().get( 5 );
        assertEquals( 2, rule.getConditions().size() );
        assertEquals( 1, rule.getConsequences().size() );
    }

    @Test
    public void testNumericDisabled() throws Exception {
        // DROOLS-1378
        final InputStream stream = RuleWorksheetParseTest.class.getResourceAsStream( "/data/NumericDisabled.xls" );
        final RuleSheetListener listener = getRuleSheetListener( stream );

        final Package ruleset = listener.getRuleSet();
        assertNotNull( ruleset );
        DRLOutput dout = new DRLOutput();
        ruleset.renderDRL( dout );
        String drl = dout.getDRL();
        System.out.println( drl );

        // check rules
        Rule rule = (Rule) ruleset.getRules().get( 0 );
        Condition cond = (Condition) rule.getConditions().get( 0 );
        assertEquals( "Cheese(price == 6600)", cond.getSnippet() );
    }

    /**
     * Utility method showing how to get a rule sheet listener from a stream.
     */
    public static RuleSheetListener getRuleSheetListener(final InputStream stream) throws IOException {
        return RulesheetUtil.getRuleSheetListener( stream );
    }
}
