/*
 * Copyright  2002-2004 The Apache Software Foundation
 *
 *  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 com.izforge.izpack.util.file.types.selectors;

import java.io.File;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.Map;

import com.izforge.izpack.api.data.InstallData;
import com.izforge.izpack.util.file.FileNameMapper;
import com.izforge.izpack.util.file.IdentityMapper;
import com.izforge.izpack.util.file.types.Mapper;

/**
 * Selector that filters files based on whether they appear in another
 * directory tree. It can contain a mapper element, so isn't available
 * as an ExtendSelector (since those parameters can't hold other
 * elements).
 */
public class PresentSelector extends BaseSelector
{

    private File targetdir = null;
    private Mapper mapperElement = null;
    private FileNameMapper map = null;
    private boolean destmustexist = true;

    /**
     * Creates a new <code>PresentSelector</code> instance.
     */
    public PresentSelector()
    {
    }

    /**
     * @return a string describing this object
     */
    public String toString()
    {
        StringBuffer buf = new StringBuffer("{presentselector targetdir: ");
        if (targetdir == null)
        {
            buf.append("NOT YET SET");
        }
        else
        {
            buf.append(targetdir.getName());
        }
        buf.append(" present: ");
        if (destmustexist)
        {
            buf.append("both");
        }
        else
        {
            buf.append("srconly");
        }
        if (map != null)
        {
            buf.append(map.toString());
        }
        else if (mapperElement != null)
        {
            buf.append(mapperElement.toString());
        }
        buf.append("}");
        return buf.toString();
    }

    /**
     * The name of the file or directory which is checked for matching
     * files.
     *
     * @param targetdir the directory to scan looking for matching files.
     */
    public void setTargetdir(File targetdir)
    {
        this.targetdir = targetdir;
    }

    /**
     * Defines the FileNameMapper to use (nested mapper element).
     *
     * @return a mapper to be configured
     * @throws Exception if more that one mapper defined
     */
    public Mapper createMapper() throws Exception
    {
        if (mapperElement != null)
        {
            throw new Exception("Cannot define more than one mapper");
        }
        mapperElement = new Mapper();
        return mapperElement;
    }


    /**
     * This sets whether to select a file if its dest file is present.
     * It could be a <code>negate</code> boolean, but by doing things
     * this way, we get some documentation on how the system works.
     * A user looking at the documentation should clearly understand
     * that the ONLY files whose presence is being tested are those
     * that already exist in the source directory, hence the lack of
     * a <code>destonly</code> option.
     *
     * @param fp An attribute set to either <code>srconly</code or
     *           <code>both</code>.
     */
    public void setPresent(FilePresence fp)
    {
        if (fp.equals(FilePresence.SRCONLY))
        {
            destmustexist = false;
        }
    }

    /**
     * Checks to make sure all settings are kosher. In this case, it
     * means that the targetdir attribute has been set and we have a mapper.
     */
    public void verifySettings() throws Exception
    {
        if (targetdir == null)
        {
            setError("The targetdir attribute is required.");
        }
        if (mapperElement == null)
        {
            map = new IdentityMapper();
        }
        else
        {
            map = mapperElement.getImplementation();
        }
        if (map == null)
        {
            setError("Could not set <mapper> element.");
        }
    }

    /**
     * The heart of the matter. This is where the selector gets to decide
     * on the inclusion of a file in a particular fileset.
     *
     * @param basedir  the base directory the scan is being done from
     * @param filename is the name of the file to check
     * @param file     is a java.io.File object the selector can use
     * @return whether the file should be selected or not
     */
    public boolean isSelected(InstallData idata, File basedir, String filename, File file) throws Exception
    {

        // throw BuildException on error
        validate();

        // Determine file whose existence is to be checked
        String[] destfiles = map.mapFileName(filename);
        // If filename does not match the To attribute of the mapper
        // then filter it out of the files we are considering
        if (destfiles == null)
        {
            return false;
        }
        // Sanity check
        if (destfiles.length != 1 || destfiles[0] == null)
        {
            throw new Exception("Invalid destination file results for "
                    + targetdir + " with filename " + filename);
        }
        String destname = destfiles[0];
        File destfile = new File(targetdir, destname);
        return destfile.exists() == destmustexist;
    }

    public enum FilePresence
    {
        SRCONLY("srconly"), BOTH("both");

        private static Map<String, FilePresence> lookup;

        private String attribute;

        FilePresence(String attribute)
        {
            this.attribute = attribute;
        }

        static
        {
            lookup = new HashMap<String, FilePresence>();
            for (FilePresence mapperType : EnumSet.allOf(FilePresence.class))
            {
                lookup.put(mapperType.getAttribute(), mapperType);
            }
        }

        public String getAttribute()
        {
            return attribute;
        }

        public static FilePresence getFromAttribute(String attribute)
        {
            if (attribute != null && lookup.containsKey(attribute))
            {
                return lookup.get(attribute);
            }
            return null;
        }
    }
}
