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

import org.apache.any23.configuration.Configuration;
import org.apache.any23.configuration.DefaultConfiguration;

import java.util.HashMap;
import java.util.Map;

/**
 * This class models the parameters to be used to perform an extraction. See org.apache.any23.Any23 for more details.
 * 
 * @author Michele Mostarda (mostarda@fbk.eu)
 */
public class ExtractionParameters {

    private final Configuration configuration;

    private final ValidationMode extractionMode;

    private final Map<String, Boolean> extractionFlags;

    private final Map<String, String> extractionProperties;

    public static final String METADATA_DOMAIN_PER_ENTITY_FLAG = "any23.extraction.metadata.domain.per.entity";

    public static final String METADATA_NESTING_FLAG = "any23.extraction.metadata.nesting";

    public static final String METADATA_TIMESIZE_FLAG = "any23.extraction.metadata.timesize";

    public static final String EXTRACTION_CONTEXT_IRI_PROPERTY = "any23.extraction.context.iri";

    /**
     * Constructor.
     *
     * @param configuration
     *            underlying configuration.
     * @param extractionMode
     *            specifies the required extraction mode.
     * @param extractionFlags
     *            map of specific flags used for extraction. If not specified they will be retrieved by the default
     *            {@link org.apache.any23.configuration.Configuration}.
     * @param extractionProperties
     *            map of specific properties used for extraction. If not specified they will ne retrieved by the default
     *            {@link org.apache.any23.configuration.Configuration}.
     */
    public ExtractionParameters(Configuration configuration, ValidationMode extractionMode,
            Map<String, Boolean> extractionFlags, Map<String, String> extractionProperties) {
        if (configuration == null) {
            throw new NullPointerException("Configuration cannot be null.");
        }
        if (extractionMode == null) {
            throw new NullPointerException("Extraction mode cannot be null.");
        }
        this.configuration = configuration;
        this.extractionMode = extractionMode;
        this.extractionFlags = extractionFlags == null ? new HashMap<>() : new HashMap<>(extractionFlags);
        this.extractionProperties = extractionProperties == null ? new HashMap<>()
                : new HashMap<>(extractionProperties);
    }

    /**
     * Constructor.
     *
     * @param configuration
     *            underlying configuration.
     * @param extractionMode
     *            specifies the required extraction mode.
     */
    public ExtractionParameters(Configuration configuration, ValidationMode extractionMode) {
        this(configuration, extractionMode, null, null);
    }

    /**
     * Constructor, allows to set explicitly the value for flag SingleDocumentExtraction#METADATA_NESTING_FLAG.
     *
     * @param configuration
     *            the underlying configuration.
     * @param extractionMode
     *            specifies the required extraction mode.
     * @param nesting
     *            if <code>true</code> nesting triples will be expressed.
     */
    public ExtractionParameters(Configuration configuration, ValidationMode extractionMode, final boolean nesting) {
        this(configuration, extractionMode, new HashMap<String, Boolean>() {
            /**
             * 
             */
            private static final long serialVersionUID = 1L;
            {
                put(ExtractionParameters.METADATA_NESTING_FLAG, nesting);
            }
        }, null);
    }

    /**
     * @param c
     *            the underlying configuration.
     * 
     * @return the default extraction parameters.
     */
    public static final ExtractionParameters newDefault(Configuration c) {
        return new ExtractionParameters(c, ValidationMode.NONE);
    }

    /**
     * Creates the default extraction parameters with {@link org.apache.any23.configuration.DefaultConfiguration}.
     *
     * @return the default extraction parameters.
     */
    public static final ExtractionParameters newDefault() {
        return new ExtractionParameters(DefaultConfiguration.singleton(), ValidationMode.NONE);
    }

    /**
     * Declares the supported validation actions.
     */
    public enum ValidationMode {
        NONE, VALIDATE, VALIDATE_AND_FIX
    }

    /**
     * @return <code>true</code> if validation is active.
     */
    public boolean isValidate() {
        return extractionMode == ValidationMode.VALIDATE || extractionMode == ValidationMode.VALIDATE_AND_FIX;
    }

    /**
     * @return <code>true</code> if fix is active.
     */
    public boolean isFix() {
        return extractionMode == ValidationMode.VALIDATE_AND_FIX;
    }

    /**
     * Returns the value of the specified extraction flag, if the flag is undefined it will be retrieved by the default
     * {@link org.apache.any23.configuration.Configuration}.
     *
     * @param flagName
     *            name of flag.
     * 
     * @return flag value.
     */
    public boolean getFlag(String flagName) {
        final Boolean value = extractionFlags.get(flagName);
        if (value == null) {
            return configuration.getFlagProperty(flagName);
        }
        return value;
    }

    /**
     * Sets the value for an extraction flag.
     *
     * @param flagName
     *            flag name.
     * @param value
     *            new flag value.
     * 
     * @return the previous flag value.
     */
    public Boolean setFlag(String flagName, boolean value) {
        checkPropertyExists(flagName);
        validateValue("flag name", flagName);
        return extractionFlags.put(flagName, value);
    }

    /**
     * Returns the value of the specified extraction property, if the property is undefined it will be retrieved by the
     * default {@link org.apache.any23.configuration.Configuration}.
     *
     * @param propertyName
     *            the property name.
     * 
     * @return the property value.
     * 
     * @throws IllegalArgumentException
     *             if the property name is not defined in configuration.
     */
    public String getProperty(String propertyName) {
        final String propertyValue = extractionProperties.get(propertyName);
        if (propertyValue == null) {
            return configuration.getPropertyOrFail(propertyName);
        }
        return propertyValue;
    }

    /**
     * Sets the value for an extraction property.
     *
     * @param propertyName
     *            the property name.
     * @param propertyValue
     *            the property value.
     * 
     * @return the previous property value.
     */
    public String setProperty(String propertyName, String propertyValue) {
        checkPropertyExists(propertyName);
        validateValue("property name", propertyName);
        validateValue("property value", propertyValue);
        return extractionProperties.put(propertyName, propertyValue);
    }

    @Override
    public boolean equals(Object obj) {
        if (obj == null) {
            return false;
        }
        if (obj == this) {
            return true;
        }
        if (obj instanceof ExtractionParameters) {
            ExtractionParameters other = (ExtractionParameters) obj;
            return extractionMode == other.extractionMode && extractionFlags.equals(other.extractionFlags)
                    && extractionProperties.equals(other.extractionProperties);
        }
        return false;
    }

    @Override
    public int hashCode() {
        return extractionMode.hashCode() * 2 * extractionFlags.hashCode() * 3 * extractionProperties.hashCode() * 5;
    }

    private void checkPropertyExists(String propertyName) {
        if (!configuration.defineProperty(propertyName)) {
            throw new IllegalArgumentException(
                    String.format(java.util.Locale.ROOT, "Property '%s' is unknown and cannot be set.", propertyName));
        }
    }

    private void validateValue(String desc, String value) {
        if (value == null || value.trim().length() == 0)
            throw new IllegalArgumentException(String.format(java.util.Locale.ROOT, "Invalid %s: '%s'", desc, value));
    }
}
