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

import java.util.List;
import java.util.Map;

import org.drools.Cheese;
import org.drools.DroolsTestCase;
import org.drools.FactException;
import org.drools.Person;
import org.drools.RuleBase;
import org.drools.RuleBaseConfiguration;
import org.drools.RuleBaseFactory;
import org.drools.base.ClassObjectType;
import org.drools.common.DefaultFactHandle;
import org.drools.common.InternalFactHandle;
import org.drools.common.InternalRuleBase;
import org.drools.common.PropagationContextImpl;
import org.drools.core.util.ObjectHashSet;
import org.drools.reteoo.ReteooBuilder.IdGenerator;
import org.drools.reteoo.builder.BuildContext;
import org.drools.rule.EntryPoint;
import org.drools.spi.ObjectType;
import org.drools.spi.PropagationContext;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;

import static org.junit.Assert.*;

public class ObjectTypeNodeTest extends DroolsTestCase {
    private ReteooRuleBase ruleBase;
    private BuildContext buildContext;
    private EntryPointNode entryPoint;
    
    @Before
    public void setUp() throws Exception {
        this.ruleBase = ( ReteooRuleBase ) RuleBaseFactory.newRuleBase();
        this.buildContext = new BuildContext( ruleBase, ((ReteooRuleBase)ruleBase).getReteooBuilder().getIdGenerator() );
        this.entryPoint = new EntryPointNode( 0,
                                              this.ruleBase.getRete(),
                                              buildContext );
        this.entryPoint.attach();
    }
    
    @Test
    public void testAttach() throws Exception {
        IdGenerator idGenerator = ruleBase.getReteooBuilder().getIdGenerator();

        final Rete source = this.ruleBase.getRete();

        final ObjectType objectType = new ClassObjectType( String.class );

        int id = idGenerator.getNextId();
        final ObjectTypeNode objectTypeNode = new ObjectTypeNode( id,
                                                                  this.entryPoint,
                                                                  objectType,
                                                                  buildContext );

        assertEquals( id,
                      objectTypeNode.getId() );

        Map<ObjectType, ObjectTypeNode> map = source.getObjectTypeNodes( EntryPoint.DEFAULT );

        assertEquals( 0,
                      map.size() );

        objectTypeNode.attach();

        assertEquals( 1,
                      map.size() );

        assertSame( objectTypeNode,
                    map.get( objectType ) );
    }

    @Test
    public void testAssertObject() throws Exception {
        final PropagationContext context = new PropagationContextImpl( 0,
                                                                       PropagationContext.ASSERTION,
                                                                       null,
                                                                       null,
                                                                       null );

        ReteooRuleBase ruleBase = (ReteooRuleBase) RuleBaseFactory.newRuleBase();
        IdGenerator idGenerator = ruleBase.getReteooBuilder().getIdGenerator();

        final ReteooWorkingMemory workingMemory = ( ReteooWorkingMemory ) ruleBase.newStatefulSession();

        final Rete source = ruleBase.getRete();

        final EntryPointNode entryPoint = new EntryPointNode( 0,
                                                              source, 
                                                              buildContext );
        entryPoint.attach();

        final ObjectTypeNode objectTypeNode = new ObjectTypeNode( idGenerator.getNextId(),
                                                                  entryPoint,
                                                                  new ClassObjectType( String.class ),
                                                                  buildContext );

        final MockObjectSink sink = new MockObjectSink();
        objectTypeNode.addObjectSink( sink );

        final Object string1 = "cheese";

        final InternalFactHandle handle1 = (InternalFactHandle) workingMemory.insert( string1 );

        // should assert as ObjectType matches
        objectTypeNode.assertObject( handle1,
                                     context,
                                     workingMemory );

        // make sure just string1 was asserted 
        final List asserted = sink.getAsserted();
        assertLength( 1,
                      asserted );
        assertSame( string1,
                    workingMemory.getObject( (DefaultFactHandle) ((Object[]) asserted.get( 0 ))[0] ) );

        // check asserted object was added to memory
        final ObjectHashSet memory = (ObjectHashSet) workingMemory.getNodeMemory( objectTypeNode );
        assertEquals( 1,
                      memory.size() );
        assertTrue( memory.contains( handle1 ) );
    }
    
    @Test
    public void testAssertObjectSequentialMode() {
        final PropagationContext context = new PropagationContextImpl( 0,
                                                                       PropagationContext.ASSERTION,
                                                                       null,
                                                                       null,
                                                                       null );

        RuleBaseConfiguration conf = new RuleBaseConfiguration();
        conf.setSequential( true );
        final ReteooRuleBase ruleBase = (ReteooRuleBase) RuleBaseFactory.newRuleBase( conf );
        buildContext = new BuildContext( ruleBase, ((ReteooRuleBase)ruleBase).getReteooBuilder().getIdGenerator() );
        buildContext.setObjectTypeNodeMemoryEnabled( false );
        
        final ReteooWorkingMemory workingMemory = new ReteooWorkingMemory( 1,
                                                                           ruleBase );

        final Rete source = ruleBase.getRete();

        final EntryPointNode entryPoint = new EntryPointNode( 0,
                                                              source, 
                                                              buildContext );
        entryPoint.attach();

        final ObjectTypeNode objectTypeNode = new ObjectTypeNode( 1,
                                                                  entryPoint,
                                                                  new ClassObjectType( String.class ),
                                                                  buildContext );

        final MockObjectSink sink = new MockObjectSink();
        objectTypeNode.addObjectSink( sink );

        final Object string1 = "cheese";

        final InternalFactHandle handle1 = (InternalFactHandle) workingMemory.insert( string1 );

        // should assert as ObjectType matches
        objectTypeNode.assertObject( handle1,
                                     context,
                                     workingMemory );

        // make sure just string1 was asserted 
        final List asserted = sink.getAsserted();
        assertLength( 1,
                      asserted );
        assertSame( string1,
                    workingMemory.getObject( (DefaultFactHandle) ((Object[]) asserted.get( 0 ))[0] ) );

        // it's sequential, so check the asserted object was not added to the node memory
        final ObjectHashSet memory = (ObjectHashSet) workingMemory.getNodeMemory( objectTypeNode );
        assertEquals( 0,
                      memory.size() );
    }

    @Test
    public void testMemory() {
        ReteooRuleBase ruleBase = (ReteooRuleBase) RuleBaseFactory.newRuleBase();
        IdGenerator idGenerator = ruleBase.getReteooBuilder().getIdGenerator();
        
        final ReteooWorkingMemory workingMemory = ( ReteooWorkingMemory ) ruleBase.newStatefulSession();

        final ObjectTypeNode objectTypeNode = new ObjectTypeNode( idGenerator.getNextId(),
                                                                  this.entryPoint,
                                                                  new ClassObjectType( String.class ),
                                                                  buildContext );

        final ObjectHashSet memory = (ObjectHashSet) workingMemory.getNodeMemory( objectTypeNode );

        assertNotNull( memory );
    }

    @Test
    public void testIsAssignableFrom() {
        ReteooRuleBase ruleBase = (ReteooRuleBase) RuleBaseFactory.newRuleBase();
        IdGenerator idGenerator = ruleBase.getReteooBuilder().getIdGenerator();
        final Rete source = new Rete((InternalRuleBase) ruleBase);

        ObjectTypeNode objectTypeNode = new ObjectTypeNode( idGenerator.getNextId(),
                                                            this.entryPoint,
                                                            new ClassObjectType( String.class ),
                                                            buildContext );

        assertFalse( objectTypeNode.isAssignableFrom( new ClassObjectType( new Object().getClass() ) ) );
        assertFalse( objectTypeNode.isAssignableFrom( new ClassObjectType( new Integer( 5 ).getClass() ) ) );
        assertTrue( objectTypeNode.isAssignableFrom( new ClassObjectType( "string".getClass() ) ) );

        objectTypeNode = new ObjectTypeNode( idGenerator.getNextId(),
                                             this.entryPoint,
                                             new ClassObjectType( Object.class ),
                                             buildContext );

        assertTrue( objectTypeNode.isAssignableFrom( new ClassObjectType( new Object().getClass() ) ) );
        assertTrue( objectTypeNode.isAssignableFrom( new ClassObjectType( new Integer( 5 ).getClass() ) ) );
        assertTrue( objectTypeNode.isAssignableFrom( new ClassObjectType( "string".getClass() ) ) );

    }

    @Test
    public void testRetractObject() throws Exception {
        ReteooRuleBase ruleBase = (ReteooRuleBase) RuleBaseFactory.newRuleBase();
        IdGenerator idGenerator = ruleBase.getReteooBuilder().getIdGenerator();
        final PropagationContext context = new PropagationContextImpl( 0,
                                                                       PropagationContext.ASSERTION,
                                                                       null,
                                                                       null,
                                                                       null );

        final ReteooWorkingMemory workingMemory = (ReteooWorkingMemory)ruleBase.newStatefulSession();

        final Rete source = new Rete((InternalRuleBase) ruleBase);

        final ObjectTypeNode objectTypeNode = new ObjectTypeNode( idGenerator.getNextId(),
                                                                  this.entryPoint,
                                                                  new ClassObjectType( String.class ),
                                                                  buildContext );

        final MockObjectSink sink = new MockObjectSink();
        objectTypeNode.addObjectSink( sink );

        final Object string1 = "cheese";

        final DefaultFactHandle handle1 = new DefaultFactHandle( 1,
                                                                 string1 );

        // should assert as ObjectType matches
        objectTypeNode.assertObject( handle1,
                                     context,
                                     workingMemory );
        // check asserted object was added to memory
        final ObjectHashSet memory = (ObjectHashSet) workingMemory.getNodeMemory( objectTypeNode );
        assertEquals( 1,
                      memory.size() );

        // should retract as ObjectType matches
        objectTypeNode.retractObject( handle1,
                                      context,
                                      workingMemory );
        // check asserted object was removed from memory
        assertEquals( 0,
                      memory.size() );

        // make sure its just the handle1 for string1 that was propagated
        final List retracted = sink.getRetracted();
        assertLength( 1,
                      retracted );
        assertSame( handle1,
                    ((Object[]) retracted.get( 0 ))[0] );
    }

    @Test
    public void testUpdateSink() throws FactException {
        // Tests that when new child is added only the last added child is
        // updated
        // When the attachingNewNode flag is set
        final PropagationContext context = new PropagationContextImpl( 0,
                                                                       PropagationContext.ASSERTION,
                                                                       null,
                                                                       null,
                                                                       null );
        final RuleBase ruleBase = RuleBaseFactory.newRuleBase();
        final ReteooWorkingMemory workingMemory = new ReteooWorkingMemory( 1,
                                                                           (ReteooRuleBase) ruleBase );

        final Rete source = new Rete( (InternalRuleBase)ruleBase);

        final ObjectTypeNode objectTypeNode = new ObjectTypeNode( 1,
                                                                  this.entryPoint,
                                                                  new ClassObjectType( String.class ),
                                                                  buildContext );

        final MockObjectSink sink1 = new MockObjectSink();
        objectTypeNode.addObjectSink( sink1 );

        final Object string1 = "cheese";

        final Object string2 = "bread";

        final DefaultFactHandle handle1 = new DefaultFactHandle( 1,
                                                                 string1 );
        final DefaultFactHandle handle2 = new DefaultFactHandle( 2,
                                                                 string2 );

        objectTypeNode.assertObject( handle1,
                                     context,
                                     workingMemory );

        objectTypeNode.assertObject( handle2,
                                     context,
                                     workingMemory );

        assertEquals( 2,
                      sink1.getAsserted().size() );

        final MockObjectSink sink2 = new MockObjectSink();
        objectTypeNode.addObjectSink( sink2 );

        assertEquals( 0,
                      sink2.getAsserted().size() );

        objectTypeNode.updateSink( sink2,
                                   new PropagationContextImpl(),
                                   workingMemory );

        assertEquals( 2,
                      sink2.getAsserted().size() );

        final Object string3 = "water";

        final DefaultFactHandle handle3 = new DefaultFactHandle( 3,
                                                                 string3 );

        objectTypeNode.assertObject( handle3,
                                     context,
                                     workingMemory );

        assertEquals( 3,
                      sink1.getAsserted().size() );

        assertEquals( 3,
                      sink2.getAsserted().size() );

    }

    @Test
    public void testAssertObjectWithShadowEnabled() throws Exception {

        final ReteooRuleBase ruleBase = (ReteooRuleBase) RuleBaseFactory.newRuleBase();
        final ReteooWorkingMemory workingMemory = new ReteooWorkingMemory( 1,
                                                                           ruleBase );

        final Rete source = ruleBase.getRete();

        final EntryPointNode entryPoint = new EntryPointNode( 0,
                                                              source, 
                                                              buildContext );
        entryPoint.attach();

        final ObjectTypeNode objectTypeNode = new ObjectTypeNode( 1,
                                                                  this.entryPoint,
                                                                  new ClassObjectType( Cheese.class  ),
                                                                  buildContext );

        final MockObjectSink sink = new MockObjectSink();
        objectTypeNode.addObjectSink( sink );
        entryPoint.addObjectSink( objectTypeNode );

        final Object cheese = new Cheese( "muzzarela",
                                          5 );

        final InternalFactHandle handle1 = (InternalFactHandle) workingMemory.insert( cheese );

        // make sure just string1 was asserted 
        final List asserted = sink.getAsserted();
        assertLength( 1,
                      asserted );
        assertEquals( cheese,
                      ((InternalFactHandle) ((Object[]) asserted.get( 0 ))[0]).getObject() );

        // check asserted object was added to memory
        final ObjectHashSet memory = (ObjectHashSet) workingMemory.getNodeMemory( objectTypeNode );
        assertEquals( 1,
                      memory.size() );
        assertTrue( memory.contains( handle1 ) );
    }

    @Test
    public void testAssertObjectWithShadowEnabledNoDefaultConstr() throws Exception {
        final PropagationContext context = new PropagationContextImpl( 0,
                                                                       PropagationContext.ASSERTION,
                                                                       null,
                                                                       null,
                                                                       null );

        ReteooRuleBase ruleBase = (ReteooRuleBase) RuleBaseFactory.newRuleBase();
        IdGenerator idGenerator = ruleBase.getReteooBuilder().getIdGenerator();
        final ReteooWorkingMemory workingMemory = ( ReteooWorkingMemory ) ruleBase.newStatefulSession();

        final Rete source = ruleBase.getRete();
        
        final EntryPointNode entryPoint = new EntryPointNode( 0,
                                                              source, 
                                                              buildContext );
        entryPoint.attach();

        final ObjectTypeNode objectTypeNode = new ObjectTypeNode( idGenerator.getNextId(),
                                                                  entryPoint,
                                                                  new ClassObjectType( Person.class ),
                                                                  buildContext );

        final MockObjectSink sink = new MockObjectSink();
        objectTypeNode.addObjectSink( sink );

        final Object person = new Person( "bob",
                                          25 );

        final InternalFactHandle handle1 = (InternalFactHandle) workingMemory.insert( person );

        // should assert as ObjectType matches
        objectTypeNode.assertObject( handle1,
                                     context,
                                     workingMemory );

        // make sure just string1 was asserted 
        final List asserted = sink.getAsserted();
        assertLength( 1,
                      asserted );
        assertEquals( ((InternalFactHandle) ((Object[]) asserted.get( 0 ))[0]).getObject(),
                      person );

        // check asserted object was added to memory
        final ObjectHashSet memory = (ObjectHashSet) workingMemory.getNodeMemory( objectTypeNode );
        assertEquals( 1,
                      memory.size() );
        assertTrue( memory.contains( handle1 ) );
    }

}
