/**
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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.apache.jena.query.text.assembler;

import static org.junit.Assert.assertEquals ;
import static org.junit.Assert.assertTrue ;

import java.util.Collection ;

import org.apache.jena.assembler.Assembler ;
import org.apache.jena.atlas.lib.InternalErrorException ;
import org.apache.jena.atlas.logging.LogCtl ;
import org.apache.jena.graph.Node ;
import org.apache.jena.query.text.EntityDefinition ;
import org.apache.jena.query.text.TextIndexException ;
import org.apache.jena.query.text.analyzer.ConfigurableAnalyzer ;
import org.apache.jena.query.text.analyzer.LowerCaseKeywordAnalyzer ;
import org.apache.jena.rdf.model.* ;
import org.apache.jena.sys.JenaSystem;
import org.apache.jena.vocabulary.RDF ;
import org.apache.jena.vocabulary.RDFS ;
import org.apache.lucene.analysis.core.KeywordAnalyzer ;
import org.apache.lucene.analysis.core.SimpleAnalyzer ;
import org.apache.lucene.analysis.standard.StandardAnalyzer ;
import org.junit.AfterClass ;
import org.junit.BeforeClass ;
import org.junit.Test ;

/**
 * Test assembler for EntityMap
 */
public class TestEntityMapAssembler {
    
    // Suppress warnings
    @BeforeClass public static void beforeClass() { LogCtl.setError(EntityDefinitionAssembler.class); }
    @AfterClass  public static void afterClass()  { LogCtl.setInfo(EntityDefinitionAssembler.class); }
    
    private static final String TESTBASE = "http://example.org/test/";
    private static final Resource spec1;
    private static final Resource spec2;
    private static final Resource spec3;
    private static final Resource spec4;
    private static final Resource spec5;
    private static final Resource spec6;
    private static final Resource spec7;
    private static final Resource specNoEntityField;
    private static final Resource specNoDefaultField;
    private static final Resource specNoMapProperty;
    private static final Resource specNoPrimaryFieldDef;
    
    @Test public void EntityHasPrimaryField() {
        EntityDefinitionAssembler entDefAssem = new EntityDefinitionAssembler();
        EntityDefinition entityDef = entDefAssem.open(null, spec1, null);
        assertEquals(SPEC1_DEFAULT_FIELD, entityDef.getPrimaryField());
    }
    
    @Test public void EntityHasEntityField() {
        EntityDefinitionAssembler entDefAssem = new EntityDefinitionAssembler();
        EntityDefinition entityDef = entDefAssem.open(null, spec1, null);
        assertEquals(SPEC1_ENTITY_FIELD, entityDef.getEntityField());        
    }
    
    @Test public void EntityHasMapEntries() {
        EntityDefinitionAssembler entDefAssem = new EntityDefinitionAssembler();
        EntityDefinition entityDef = entDefAssem.open(null, spec1, null);
        assertEquals(SPEC1_PREDICATE.asNode(), getOne(entityDef,SPEC1_DEFAULT_FIELD));
    }
    
    private static Object getOne(EntityDefinition entityDef, String field) {
        Collection<Node> x = entityDef.getPredicates(field) ;
        if ( x == null || x.size() == 0 )
            return null ;
        if ( x.size() != 1 )
            throw new InternalErrorException("Not unique: "+field) ;
        return x.iterator().next() ; 
    }

    @Test public void EntityHasMultipleMapEntries() {
        EntityDefinitionAssembler entDefAssem = new EntityDefinitionAssembler();
        EntityDefinition entityDef = entDefAssem.open(null, spec2, null);
        assertEquals(SPEC2_PREDICATE1.asNode(), getOne(entityDef,SPEC2_DEFAULT_FIELD));
        assertEquals(SPEC2_PREDICATE2.asNode(), getOne(entityDef, SPEC2_FIELD2));
    }
    
    @Test public void EntityHasMapEntryWithSimpleAnalyzer() {
        EntityDefinitionAssembler entDefAssem = new EntityDefinitionAssembler();
        EntityDefinition entityDef = entDefAssem.open(Assembler.general, spec3,  null);
        assertEquals(SimpleAnalyzer.class, entityDef.getAnalyzer(SPEC1_DEFAULT_FIELD).getClass());
    }
    
    @Test public void EntityHasMapEntryWithStandardAnalyzerAndStopWords() {
        EntityDefinitionAssembler entDefAssem = new EntityDefinitionAssembler();
        EntityDefinition entityDef = entDefAssem.open(Assembler.general, spec4,  null);
        assertEquals(StandardAnalyzer.class, entityDef.getAnalyzer(SPEC1_DEFAULT_FIELD).getClass());
    }
    
    @Test public void EntityHasMapEntryWithKeywordAnalyzer() {
        EntityDefinitionAssembler entDefAssem = new EntityDefinitionAssembler();
        EntityDefinition entityDef = entDefAssem.open(Assembler.general, spec5,  null);
        assertEquals(KeywordAnalyzer.class, entityDef.getAnalyzer(SPEC1_DEFAULT_FIELD).getClass());
    }    
    
    @Test public void EntityHasMapEntryWithLowerCaseKeywordAnalyzer() {
        EntityDefinitionAssembler entDefAssem = new EntityDefinitionAssembler();
        EntityDefinition entityDef = entDefAssem.open(Assembler.general, spec6,  null);
        assertEquals(LowerCaseKeywordAnalyzer.class, entityDef.getAnalyzer(SPEC1_DEFAULT_FIELD).getClass());
    }    
    
    @Test public void EntityHasMapEntryWithConfigurableAnalyzer() {
        EntityDefinitionAssembler entDefAssem = new EntityDefinitionAssembler();
        EntityDefinition entityDef = entDefAssem.open(Assembler.general, spec7,  null);
        assertEquals(ConfigurableAnalyzer.class, entityDef.getAnalyzer(SPEC1_DEFAULT_FIELD).getClass());
    }    
    
    @Test(expected=TextIndexException.class) public void errorOnNoEntityField() {
        EntityDefinitionAssembler entDefAssem = new EntityDefinitionAssembler();
        entDefAssem.open(null, specNoEntityField, null);
    }
    
    @Test(expected=TextIndexException.class) public void errorOnNoDefaultField() {
        EntityDefinitionAssembler entDefAssem = new EntityDefinitionAssembler();
        entDefAssem.open(null, specNoDefaultField, null);
    }
    
    @Test(expected=TextIndexException.class) public void errorOnNoMapProperty() {
        EntityDefinitionAssembler entDefAssem = new EntityDefinitionAssembler();
        entDefAssem.open(null, specNoMapProperty, null);
    }
    
    @Test(expected=TextIndexException.class) public void errorOnNoPrimaryFieldDef() {
        EntityDefinitionAssembler entDefAssem = new EntityDefinitionAssembler();
        try {
            entDefAssem.open(null, specNoPrimaryFieldDef, null);
        } catch (TextIndexException e) {
            assertTrue(e.getMessage().contains(SPEC1_DEFAULT_FIELD));
            throw e ;
        }    
        
    }
    
    private static final String SPEC1_ENTITY_FIELD = "spec1EntityField";
    private static final String SPEC1_DEFAULT_FIELD = "spec1DefaultField";
    private static final Property SPEC1_PREDICATE = RDFS.label;
    
    private static final String SPEC2_ENTITY_FIELD = "spec2EntityField";
    private static final String SPEC2_DEFAULT_FIELD = "spec2DefaultField";
    private static final String SPEC2_FIELD2 = "spec2Field2";
    private static final Property SPEC2_PREDICATE1 = RDFS.label;
    private static final Property SPEC2_PREDICATE2 = RDFS.comment;
    static {
        JenaSystem.init();
        TextAssembler.init();
        Model model = ModelFactory.createDefaultModel();
        
        // create a simple entity map specification
        
        spec1 = model.createResource(TESTBASE + "spec1")
                     .addProperty(TextVocab.pEntityField, SPEC1_ENTITY_FIELD)
                     .addProperty(TextVocab.pDefaultField, SPEC1_DEFAULT_FIELD)
                     .addProperty(TextVocab.pMap,
                                  model.createList(
                                          new RDFNode[] {
                                                model.createResource()
                                                     .addProperty(TextVocab.pField, SPEC1_DEFAULT_FIELD)
                                                     .addProperty(TextVocab.pPredicate, SPEC1_PREDICATE)
                                          }))
                     ;
        
        // create an entity map specification with multiple map entries
        
        spec2 = model.createResource(TESTBASE + "spec2")
                     .addProperty(TextVocab.pEntityField, SPEC2_ENTITY_FIELD)
                     .addProperty(TextVocab.pDefaultField, SPEC2_DEFAULT_FIELD)
                     .addProperty(TextVocab.pMap,
                                  model.createList(
                                          new RDFNode[] {
                                                model.createResource()
                                                     .addProperty(TextVocab.pField, SPEC2_DEFAULT_FIELD)
                                                     .addProperty(TextVocab.pPredicate, SPEC2_PREDICATE1),
                                                model.createResource()
                                                     .addProperty(TextVocab.pField, SPEC2_FIELD2)
                                                     .addProperty(TextVocab.pPredicate, SPEC2_PREDICATE2),
                                                    
                                          }))
                     ;
        
        // create a simple entity map specification using a keyword analyzer
        
                spec3 = model.createResource(TESTBASE + "spec3")
                             .addProperty(TextVocab.pEntityField, SPEC1_ENTITY_FIELD)
                             .addProperty(TextVocab.pDefaultField, SPEC1_DEFAULT_FIELD)
                             .addProperty(TextVocab.pMap,
                                          model.createList(
                                                  new RDFNode[] {
                                                        model.createResource()
                                                             .addProperty(TextVocab.pField, SPEC1_DEFAULT_FIELD)
                                                             .addProperty(TextVocab.pPredicate, SPEC1_PREDICATE)
                                                             .addProperty(TextVocab.pAnalyzer, 
                                                                          model.createResource()
                                                                               .addProperty(RDF.type, TextVocab.simpleAnalyzer))
                                                  }))
                             ;
                // create a simple entity map specification using a standard analyzer with stop words
                
                spec4 = model.createResource(TESTBASE + "spec4")
                             .addProperty(TextVocab.pEntityField, SPEC1_ENTITY_FIELD)
                             .addProperty(TextVocab.pDefaultField, SPEC1_DEFAULT_FIELD)
                             .addProperty(TextVocab.pMap,
                                          model.createList(
                                                  new RDFNode[] {
                                                        model.createResource()
                                                             .addProperty(TextVocab.pField, SPEC1_DEFAULT_FIELD)
                                                             .addProperty(TextVocab.pPredicate, SPEC1_PREDICATE)
                                                             .addProperty(TextVocab.pAnalyzer, 
                                                                          model.createResource()
                                                                               .addProperty(RDF.type, TextVocab.standardAnalyzer))
                                                                               .addProperty(TextVocab.pStopWords, toList(model, "and the but"))
                                                  }));        
        // create a simple entity map specification using a keyword analyzer
        
                spec5 = model.createResource(TESTBASE + "spec5")
                             .addProperty(TextVocab.pEntityField, SPEC1_ENTITY_FIELD)
                             .addProperty(TextVocab.pDefaultField, SPEC1_DEFAULT_FIELD)
                             .addProperty(TextVocab.pMap,
                                          model.createList(
                                                  new RDFNode[] {
                                                        model.createResource()
                                                             .addProperty(TextVocab.pField, SPEC1_DEFAULT_FIELD)
                                                             .addProperty(TextVocab.pPredicate, SPEC1_PREDICATE)
                                                             .addProperty(TextVocab.pAnalyzer, 
                                                                          model.createResource()
                                                                               .addProperty(RDF.type, TextVocab.keywordAnalyzer))
                                                  }));
                
        // create a simple entity map specification using a lowercase keyword analyzer
        
                spec6 = model.createResource(TESTBASE + "spec6")
                             .addProperty(TextVocab.pEntityField, SPEC1_ENTITY_FIELD)
                             .addProperty(TextVocab.pDefaultField, SPEC1_DEFAULT_FIELD)
                             .addProperty(TextVocab.pMap,
                                          model.createList(
                                                  new RDFNode[] {
                                                        model.createResource()
                                                             .addProperty(TextVocab.pField, SPEC1_DEFAULT_FIELD)
                                                             .addProperty(TextVocab.pPredicate, SPEC1_PREDICATE)
                                                             .addProperty(TextVocab.pAnalyzer, 
                                                                          model.createResource()
                                                                               .addProperty(RDF.type, TextVocab.lowerCaseKeywordAnalyzer))
                                                  }));
                

        // create an entity map specification using a configurable analyzer
        
                spec7 = model.createResource(TESTBASE + "spec7")
                             .addProperty(TextVocab.pEntityField, SPEC1_ENTITY_FIELD)
                             .addProperty(TextVocab.pDefaultField, SPEC1_DEFAULT_FIELD)
                             .addProperty(TextVocab.pMap,
                                          model.createList(
                                                  new RDFNode[] {
                                                        model.createResource()
                                                             .addProperty(TextVocab.pField, SPEC1_DEFAULT_FIELD)
                                                             .addProperty(TextVocab.pPredicate, SPEC1_PREDICATE)
                                                             .addProperty(TextVocab.pAnalyzer, 
                                                                          model.createResource()
                                                                               .addProperty(RDF.type, TextVocab.configurableAnalyzer)
                                                                               .addProperty(TextVocab.pTokenizer, TextVocab.standardTokenizer))
                                                  }));
                
        // bad assembler spec
                
        specNoEntityField = 
                model.createResource(TESTBASE + "specNoEntityField")
                     .addProperty(TextVocab.pDefaultField, SPEC1_DEFAULT_FIELD)
                     .addProperty(TextVocab.pMap,
                                  model.createList(
                                          new RDFNode[] {
                                                model.createResource()
                                                     .addProperty(TextVocab.pField, SPEC1_DEFAULT_FIELD)
                                                     .addProperty(TextVocab.pPredicate, SPEC1_PREDICATE)
                                          }))
                     ;
        
        // bad assembler spec
        
        specNoDefaultField = 
                model.createResource(TESTBASE + "specNoDefaultField")
                     .addProperty(TextVocab.pDefaultField, SPEC1_DEFAULT_FIELD)
                     .addProperty(TextVocab.pMap,
                                  model.createList(
                                          new RDFNode[] {
                                                model.createResource()
                                                     .addProperty(TextVocab.pField, SPEC1_DEFAULT_FIELD)
                                                     .addProperty(TextVocab.pPredicate, SPEC1_PREDICATE)
                                          }))
                     ;
        // bad assembler spec
        specNoMapProperty = 
                model.createResource(TESTBASE + "specNoMapProperty")
                     .addProperty(TextVocab.pEntityField, SPEC1_ENTITY_FIELD)
                     .addProperty(TextVocab.pDefaultField, SPEC1_DEFAULT_FIELD)
                     ;
        
        // bad assembler spec        
        specNoPrimaryFieldDef =
                model.createResource(TESTBASE + "noPrimaryFieldDef")
                     .addProperty(TextVocab.pEntityField, SPEC1_ENTITY_FIELD)
                     .addProperty(TextVocab.pDefaultField, SPEC1_DEFAULT_FIELD)
                     .addProperty(TextVocab.pMap,
                                  model.createList(
                                          new RDFNode[] {
                                                model.createResource()
                                                     .addProperty(TextVocab.pField, SPEC1_ENTITY_FIELD)
                                                     .addProperty(TextVocab.pPredicate, SPEC1_PREDICATE)
                                          }))
                     ;
    }
    
    private static Resource toList(Model model, String string) {
        String[] members = string.split("\\s");
        Resource current = RDF.nil;
        for (int i = members.length-1; i>=0; i--) {
            Resource previous = current;
            current = model.createResource();
            current.addProperty(RDF.rest, previous);
            current.addProperty(RDF.first, members[i]);            
        }
        return current;    
    }
}
