/*
 * 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.jackrabbit.vault.fs.io;

import javax.jcr.Session;

import java.io.File;
import java.io.IOException;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;

import org.apache.jackrabbit.vault.fs.api.IdConflictPolicy;
import org.apache.jackrabbit.vault.fs.api.ImportMode;
import org.apache.jackrabbit.vault.fs.api.PathMapping;
import org.apache.jackrabbit.vault.fs.api.ProgressTrackerListener;
import org.apache.jackrabbit.vault.fs.api.WorkspaceFilter;
import org.apache.jackrabbit.vault.packaging.DependencyHandling;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Option that control the package import.
 */
public class ImportOptions {
    private static final Logger LOG = LoggerFactory.getLogger(ImportOptions.class);

    private Boolean strict;

    private ProgressTrackerListener listener;

    private boolean nonRecursive = false;

    private boolean dryRun;

    private int autoSaveThreshold = -1;

    private AccessControlHandling acHandling = null;

    private AccessControlHandling cugHandling = null;

    private ImportMode importMode;

    private Pattern cndPattern = Pattern.compile("^/(apps|libs)/([^/]+/){1,2}nodetypes/.+\\.cnd$");

    private WorkspaceFilter filter = null;

    private ClassLoader hookClassLoader;

    private PathMapping pathMapping = null;

    private DependencyHandling dependencyHandling = null;

    private IdConflictPolicy idConflictPolicy = null;

    private Boolean overwritePrimaryTypesOfFolders = null;

    /**
     * Default constructor.
     */
    public ImportOptions() {
        // default constructor.
    }

    /**
     * @deprecated use {@link #copy()} instead.
     * @param base base options
     */
    @Deprecated
    public ImportOptions(ImportOptions base) {
        if (base != null) {
            strict = base.strict;
            listener = base.listener;
            nonRecursive = base.nonRecursive;
            dryRun = base.dryRun;
            autoSaveThreshold = base.autoSaveThreshold;
            acHandling = base.acHandling;
            cugHandling = base.cugHandling;
            importMode = base.importMode;
            cndPattern = base.cndPattern;
            filter = base.filter;
            hookClassLoader = base.hookClassLoader;
            pathMapping = base.pathMapping;
            dependencyHandling = base.dependencyHandling;
            idConflictPolicy = base.idConflictPolicy;
            overwritePrimaryTypesOfFolders = base.overwritePrimaryTypesOfFolders;
        }
    }

    /**
     * Creates a copy of this import options.
     * @return a copy of this.
     */
    public ImportOptions copy() {
        ImportOptions ret = new ImportOptions();
        ret.strict = strict;
        ret.listener = listener;
        ret.nonRecursive = nonRecursive;
        ret.dryRun = dryRun;
        ret.autoSaveThreshold = autoSaveThreshold;
        ret.acHandling = acHandling;
        ret.cugHandling = cugHandling;
        ret.importMode = importMode;
        ret.cndPattern = cndPattern;
        ret.filter = filter;
        ret.hookClassLoader = hookClassLoader;
        ret.pathMapping = pathMapping;
        ret.dependencyHandling = dependencyHandling;
        ret.idConflictPolicy = idConflictPolicy;
        ret.overwritePrimaryTypesOfFolders = overwritePrimaryTypesOfFolders;
        return ret;
    }

    public void setOverwritePrimaryTypesOfFolders(boolean overwritePrimaryTypesOfFolders) {
        this.overwritePrimaryTypesOfFolders = overwritePrimaryTypesOfFolders;
    }

    public boolean overwritePrimaryTypesOfFolders() {
        if (overwritePrimaryTypesOfFolders == null) {
            return true;
        } else {
            return overwritePrimaryTypesOfFolders;
        }
    }

    public boolean overwritePrimaryTypesOfFolders(boolean overwritePrimaryTypesOfFoldersByDefault) {
        if (overwritePrimaryTypesOfFolders == null) {
            return overwritePrimaryTypesOfFoldersByDefault;
        } else {
            return overwritePrimaryTypesOfFolders;
        }
    }

    public boolean isStrict(boolean isStrictByDefault) {
        if (strict == null) {
            return isStrictByDefault;
        } else {
            return strict;
        }
    }

    /**
     * Returns the 'strict' flag.
     * @return the 'strict' flag or {@code null} in case this is not set
     * @deprecated Use {@link #isStrict(boolean)} instead.
     */
    @Deprecated
    public boolean isStrict() {
        if (strict == null) {
            return false;
        } else {
            return strict;
        }
    }

    /**
     * Sets the 'strict' flag.
     * @param strict the flag
     */
    public void setStrict(boolean strict) {
        this.strict = strict;
    }

    /**
     * Returns the progress tracker listener.
     * @return the progress tracker listener.
     */
    public ProgressTrackerListener getListener() {
        return listener;
    }

    /**
     * Sets the progress tracker listener that receives messages during package installation.
     * @param listener The listener
     */
    public void setListener(ProgressTrackerListener listener) {
        this.listener = listener;
    }

    /**
     * Returns the patch parent path
     * @return the patch parent path
     * @deprecated No longer evaluated as patch files are no longer supported for security reasons.
     */
    @Deprecated(since = "4.2.0", forRemoval = true)
    public String getPatchParentPath() {
        return null;
    }

    /**
     * Sets the parent path of the patch node.
     * @param patchParentPath the path
     * @deprecated No longer evaluated as patch files are no longer supported for security reasons.
     */
    @Deprecated(since = "4.2.0", forRemoval = true)
    public void setPatchParentPath(String patchParentPath) {
        // noop
        LOG.warn(
                "Patch files are no longer supported for security reasons. Ignoring patch parent path {}",
                patchParentPath);
    }

    /**
     * Returns the patch directory
     * @return the patch directory
     * @deprecated No longer evaluated as patch files are no longer supported for security reasons.
     */
    @Deprecated(since = "4.2.0", forRemoval = true)
    public File getPatchDirectory() {
        return null;
    }

    /**
     * Sets the patch directory. The nt:file nodes that are placed below the {@link #getPatchParentPath()} will be
     * copied into this directory during extraction.
     * @param patchDirectory The directory
     * @throws IOException if an i/o error occurrs during obtaining the canonical file of this directory.
     * @deprecated No longer evaluated as patch files are no longer supported for security reasons.
     */
    @Deprecated(since = "4.2.0", forRemoval = true)
    public void setPatchDirectory(File patchDirectory) throws IOException {
        LOG.warn(
                "Patch files are no longer supported for security reasons. Ignoring patch directory {}",
                patchDirectory);
        // noop
    }

    /**
     * Returns the 'patch-keep-in-repo' flag.
     * @return the 'patch-keep-in-repo' flag.
     * @deprecated No longer evaluated as patch files are no longer supported for security reasons.
     */
    @Deprecated(since = "4.2.0", forRemoval = true)
    public boolean isPatchKeepInRepo() {
        return false;
    }

    /**
     * Sets the flag if patches should be kept in the repository after there were copied to the disk.
     * @param patchKeepInRepo the flag
     * @deprecated No longer evaluated as patch files are no longer supported for security reasons.
     */
    @Deprecated(since = "4.2.0", forRemoval = true)
    public void setPatchKeepInRepo(boolean patchKeepInRepo) {
        // noop
        LOG.warn(
                "Patch files are no longer supported for security reasons. Ignoring setPatchKeepInRepo({})",
                patchKeepInRepo);
    }

    /**
     * Returns the default access control handling.
     * @return the default access control handling.
     */
    public AccessControlHandling getAccessControlHandling() {
        return acHandling;
    }

    /**
     * Sets the access control handling.
     * @param acHandling the ACL handling.
     */
    public void setAccessControlHandling(AccessControlHandling acHandling) {
        this.acHandling = acHandling;
    }

    /**
     * Returns closed user group handling.
     * @return CUG handling value. <code>null</code> value indicates that CUG
     * handling is controlled by acHandling value which maintains backwards compatibility.
     */
    public AccessControlHandling getCugHandling() {
        return cugHandling;
    }

    /**
     * Sets closed user group handling. For backwards compatibility, when cugHandling is set to
     * null <code>null</code> then acHandling is used is used to control handling of CUG nodes.
     * @param cugHandling the CUG handling.
     */
    public void setCugHandling(AccessControlHandling cugHandling) {
        this.cugHandling = cugHandling;
    }

    /**
     * Defines the package installation should recursively install sub packages. Note that if this flag is enabled,
     * the {@link org.apache.jackrabbit.vault.packaging.SubPackageHandling} configuration has no effect, as sub packages
     * are not evaluated at all.
     *
     * @return {@code true} if package installation should not install sub packages.
     */
    public boolean isNonRecursive() {
        return nonRecursive;
    }

    /**
     * Sets the flag to ignore sub packages.
     * @param nonRecursive {@code true} to set non recursive
     * @see #isNonRecursive()
     */
    public void setNonRecursive(boolean nonRecursive) {
        this.nonRecursive = nonRecursive;
    }

    /**
     * Returns the CND pattern
     * @return the CND pattern
     */
    public Pattern getCndPattern() {
        return cndPattern;
    }

    /**
     * Sets the CND file pattern.
     * @param cndPattern the cnd pattern
     * @throws PatternSyntaxException If the pattern is not valid
     */
    public void setCndPattern(String cndPattern) throws PatternSyntaxException {
        this.cndPattern = Pattern.compile(cndPattern);
    }

    /**
     * Returns the dry run flag.
     * @return the dry run flag.
     * @since 2.2.14
     */
    public boolean isDryRun() {
        return dryRun;
    }

    /**
     * Sets the dry run flag.
     * @param dryRun the dry run flag.
     * @since 2.2.14
     */
    public void setDryRun(boolean dryRun) {
        this.dryRun = dryRun;
    }

    /**
     * Sets the auto-save threshold. See {@link AutoSave}
     * @param autoSaveThreshold the threshold in number of nodes.
     * @since 2.2.16
     */
    public void setAutoSaveThreshold(int autoSaveThreshold) {
        this.autoSaveThreshold = autoSaveThreshold;
    }

    /**
     * Returns the auto-save threshold.
     * If {@link Integer#MAX_VALUE} both {@link Session#save()} and {@link Session#refresh(boolean)} must not be
     * executed during {@link Importer#run(Archive, Session, String)}.
     * @return the auto-save threshold.
     * @since 2.2.16
     */
    public int getAutoSaveThreshold() {
        return autoSaveThreshold;
    }

    /**
     * Returns the import mode.
     * @return the import mode.
     * @since 2.3
     */
    public ImportMode getImportMode() {
        return importMode;
    }

    /**
     * Sets the default import mode.
     * @param importMode The import mode.
     * @since 2.3
     */
    public void setImportMode(ImportMode importMode) {
        this.importMode = importMode;
    }

    /**
     * Returns the default workspace filter.
     * @return the default workspace filter.
     * @since 2.3.20
     */
    public WorkspaceFilter getFilter() {
        return filter;
    }

    /**
     * Sets the default workspace filter.
     * @param filter the filter
     * @since 2.3.20
     */
    public void setFilter(WorkspaceFilter filter) {
        this.filter = filter;
    }

    /**
     * Returns the hook class loader.
     * @return the hook class loader.
     * @since 2.3.22
     */
    public ClassLoader getHookClassLoader() {
        return hookClassLoader;
    }

    /**
     * Sets the hook class loader.
     * @param hookClassLoader the class loader
     * @since 2.3.22
     */
    public void setHookClassLoader(ClassLoader hookClassLoader) {
        this.hookClassLoader = hookClassLoader;
    }

    /**
     * Defines a path mapping that is applied to the incoming package paths and filter when installing the package.
     *
     * @since 3.1.14
     * @return {@code null} if no path mapping is defined.
     */
    public PathMapping getPathMapping() {
        return pathMapping;
    }

    /**
     * Sets the path mapping
     * @param pathMapping The path mapping
     * @see #getPathMapping()
     * @since 3.1.14
     */
    public void setPathMapping(PathMapping pathMapping) {
        this.pathMapping = pathMapping;
    }

    /**
     * Defines how package dependencies affect package installation and un-installation.
     * @return the dependency handling.
     */
    public DependencyHandling getDependencyHandling() {
        return dependencyHandling;
    }

    /**
     * Sets the dependency handling.
     * @param dependencyHandling the dependency handling.
     * @see #getDependencyHandling()
     * @since 3.1.32
     */
    public void setDependencyHandling(DependencyHandling dependencyHandling) {
        this.dependencyHandling = dependencyHandling;
    }

    /**
     *
     * @return the id conflict policy if one is set, otherwise {@link IdConflictPolicy#FAIL}
     * @since 3.5.1
     */
    public @NotNull IdConflictPolicy getIdConflictPolicy() {
        return idConflictPolicy != null ? idConflictPolicy : IdConflictPolicy.FAIL;
    }

    /**
     * @return {@code true} if {@link #setIdConflictPolicy(IdConflictPolicy)} has been called otherwise {@code false}
     * @since 3.6.0
     */
    public boolean hasIdConflictPolicyBeenSet() {
        return idConflictPolicy != null;
    }

    /**
     * Sets the id conflict policy (in case of unresolveable conflicts).
     * @param idConflictPolicy the conflict policy
     * @since 3.5.2
     */
    public void setIdConflictPolicy(@NotNull IdConflictPolicy idConflictPolicy) {
        this.idConflictPolicy = idConflictPolicy;
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + ((acHandling == null) ? 0 : acHandling.hashCode());
        result = prime * result + autoSaveThreshold;
        result = prime * result + ((cndPattern == null) ? 0 : cndPattern.hashCode());
        result = prime * result + ((cugHandling == null) ? 0 : cugHandling.hashCode());
        result = prime * result + ((dependencyHandling == null) ? 0 : dependencyHandling.hashCode());
        result = prime * result + (dryRun ? 1231 : 1237);
        result = prime * result + ((filter == null) ? 0 : filter.hashCode());
        result = prime * result + ((hookClassLoader == null) ? 0 : hookClassLoader.hashCode());
        result = prime * result + ((importMode == null) ? 0 : importMode.hashCode());
        result = prime * result + ((listener == null) ? 0 : listener.hashCode());
        result = prime * result + (nonRecursive ? 1231 : 1237);
        result = prime * result + ((pathMapping == null) ? 0 : pathMapping.hashCode());
        result = prime * result + ((idConflictPolicy == null) ? 0 : idConflictPolicy.hashCode());
        result = prime * result + (strict ? 1231 : 1237);
        result = prime * result + (overwritePrimaryTypesOfFolders ? 1231 : 1237);

        return result;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) return true;
        if (obj == null) return false;
        if (getClass() != obj.getClass()) return false;
        ImportOptions other = (ImportOptions) obj;
        if (acHandling != other.acHandling) return false;
        if (autoSaveThreshold != other.autoSaveThreshold) return false;
        if (cndPattern == null) {
            if (other.cndPattern != null) return false;
        } else if (!cndPattern.pattern().equals(other.cndPattern.pattern())) return false;
        if (cugHandling != other.cugHandling) return false;
        if (dependencyHandling != other.dependencyHandling) return false;
        if (dryRun != other.dryRun) return false;
        if (filter == null) {
            if (other.filter != null) return false;
        } else if (!filter.equals(other.filter)) return false;
        if (hookClassLoader == null) {
            if (other.hookClassLoader != null) return false;
        } else if (!hookClassLoader.equals(other.hookClassLoader)) return false;
        if (importMode != other.importMode) return false;
        if (listener == null) {
            if (other.listener != null) return false;
        } else if (!listener.equals(other.listener)) return false;
        if (nonRecursive != other.nonRecursive) return false;
        if (pathMapping == null) {
            if (other.pathMapping != null) return false;
        } else if (!pathMapping.equals(other.pathMapping)) return false;
        if (strict != other.strict) return false;
        if (overwritePrimaryTypesOfFolders != other.overwritePrimaryTypesOfFolders) return false;
        if (idConflictPolicy == null) {
            if (other.idConflictPolicy != null) return false;
        } else if (!idConflictPolicy.equals(other.idConflictPolicy)) return false;
        return true;
    }

    @Override
    public String toString() {
        return "ImportOptions [strict=" + strict + ", " + (listener != null ? "listener=" + listener + ", " : "")
                + ", nonRecursive=" + nonRecursive + ", dryRun=" + dryRun + ", autoSave=" + autoSaveThreshold + ", "
                + (acHandling != null ? "acHandling=" + acHandling + ", " : "")
                + (cugHandling != null ? "cugHandling=" + cugHandling + ", " : "")
                + (importMode != null ? "importMode=" + importMode + ", " : "")
                + (cndPattern != null ? "cndPattern=" + cndPattern + ", " : "")
                + (filter != null ? "filter=" + filter + ", " : "")
                + (hookClassLoader != null ? "hookClassLoader=" + hookClassLoader + ", " : "")
                + (pathMapping != null ? "pathMapping=" + pathMapping + ", " : "")
                + (dependencyHandling != null ? "dependencyHandling=" + dependencyHandling + ", " : "")
                + "overwritePrimaryTypesOfFolders=" + overwritePrimaryTypesOfFolders + ", "
                + "idConflictPolicy=" + (idConflictPolicy != null ? idConflictPolicy : IdConflictPolicy.FAIL) + "]";
    }
}
