/*
* 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.stanbol.entityhub.indexing.core.processor;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Iterator;
import java.util.Map;

import org.apache.commons.io.IOUtils;
import org.apache.commons.io.LineIterator;
import org.apache.stanbol.commons.namespaceprefix.NamespacePrefixService;
import org.apache.stanbol.entityhub.core.mapping.FieldMappingUtils;
import org.apache.stanbol.entityhub.core.model.InMemoryValueFactory;
import org.apache.stanbol.entityhub.indexing.core.EntityProcessor;
import org.apache.stanbol.entityhub.indexing.core.config.IndexingConfig;
import org.apache.stanbol.entityhub.servicesapi.mapping.FieldMapper;
import org.apache.stanbol.entityhub.servicesapi.model.Representation;
import org.apache.stanbol.entityhub.servicesapi.model.ValueFactory;

public class FiledMapperProcessor implements EntityProcessor{
    public static final String PARAM_MAPPINGS = "mappings";
    public static final String PARAM_VALUE_FACTORY = "valueFactory";
    public static final String DEFAULT_MAPPINGS_FILE_NAME = "fieldMappings.txt";
    private FieldMapper mapper;
    private ValueFactory vf;
    private NamespacePrefixService nsPrefixService;
    /**
     * This Constructor relays on that {@link #setConfiguration(Map)} is called
     * afterwards!
     */
    public FiledMapperProcessor(){
        this(null);
    }
    /**
     * Internally used to initialise the {@link ValueFactory}
     * @param vf the value factory or <code>null</code> to use the {@link InMemoryValueFactory}.
     */
    private FiledMapperProcessor(ValueFactory vf){
        setValueFactory(vf);
    }
    public FiledMapperProcessor(FieldMapper mapper, ValueFactory vf){
        this(vf);
        if(mapper == null){
            throw new IllegalArgumentException("The parsed FieldMapper MUST NOT be NULL!");
        }
    }
    public FiledMapperProcessor(Iterator<String> mappings,ValueFactory vf){
        this(vf);
        if(mappings == null){
            throw new IllegalArgumentException("The parsed field mappings MUST NOT be NULL!");
        }
        mapper = FieldMappingUtils.createDefaultFieldMapper(mappings,nsPrefixService);
        if(mapper.getMappings().isEmpty()){
            throw new IllegalStateException("The parsed field mappings MUST contain at least a single valid mapping!");
        }
    }
    public FiledMapperProcessor(InputStream mappings, ValueFactory vf) throws IOException{
        this(vf);
        if(mappings == null){
            throw new IllegalArgumentException("The parsed field mappings MUST NOT be NULL!");
        }
        this.mapper = createMapperFormStream(mappings,nsPrefixService);
    }
    @Override
    public Representation process(Representation source) {
        if(mapper == null){
            throw new IllegalStateException("The mapper is not initialised. One must call setConfiguration to configure the FieldMapper!");
        }
        if(source == null){
            return null;
        } else {
            return mapper.applyMappings(source,
                vf.createRepresentation(source.getId()),
                vf);
        }
    }
    /**
     * used by the different constructors to init the {@link ValueFactory}
     * @param vf the value factory or <code>null</code> to use the default
     */
    private void setValueFactory(ValueFactory vf) {
        if(vf == null){
            this.vf = InMemoryValueFactory.getInstance();
        } else {
            this.vf = vf;
        }
    }
    @Override
    public void close() {
        //nothing todo
        
    }
    @Override
    public void initialise() {
        //nothing todo
    }
    @Override
    public boolean needsInitialisation() {
        return false;
    }
    @Override
    public void setConfiguration(Map<String,Object> config) {
        IndexingConfig indexingConfig = (IndexingConfig)config.get(IndexingConfig.KEY_INDEXING_CONFIG);
        nsPrefixService = indexingConfig.getNamespacePrefixService();
        Object value = config.get(PARAM_MAPPINGS);
        if(value == null || value.toString().isEmpty()){
            //use the mappings configured for the Index
            this.mapper = FieldMappingUtils.createDefaultFieldMapper(
                indexingConfig.getIndexFieldConfiguration());
        } else {
            //load (other) mappings based on the provided mappings parameter
            //final File file = new File(indexingConfig.getConfigFolder(),value.toString());
            File mappings = indexingConfig.getConfigFile(value.toString());
            if(mappings != null){
                try {
                    InputStream in = new FileInputStream(mappings);
                    this.mapper = createMapperFormStream(in,nsPrefixService);
                    IOUtils.closeQuietly(in);
                } catch (IOException e) {
                    throw new IllegalArgumentException("Unable to access FieldMapping file "+
                        value+" not found in configuration directory "+
                        indexingConfig.getConfigFolder());
                }
            } else {
                throw new IllegalArgumentException("FieldMapping file "+
                    value+" not found in configuration directory "+
                    indexingConfig.getConfigFolder());
            }
        }
        //TODO: get the valueFactory form the config (currently an InMemory is
        //create by the default constructor!
    }
    /**
     * Utility that allows to create a FieldMapper form an inputStream.
     * It uses {@link IOUtils#lineIterator(InputStream, String)} and parses it
     * to {@link FieldMappingUtils#createDefaultFieldMapper(Iterator)}
     * @param in the stream to read the mappings from
     * @throws IOException on any error while reading the data from the stream
     */
    private static FieldMapper createMapperFormStream(final InputStream in, NamespacePrefixService nps) throws IOException {
        return FieldMappingUtils.createDefaultFieldMapper(new Iterator<String>() {
            LineIterator it = IOUtils.lineIterator(in, "UTF-8");
            @Override
            public boolean hasNext() {
                return it.hasNext();
            }
            @Override
            public String next() {
                return it.nextLine();
            }
            @Override
            public void remove() {
                it.remove();
            }
        },nps);
    }

}
