package org.apache.struts2.oval.interceptor;

import com.opensymphony.xwork2.inject.Inject;
import com.opensymphony.xwork2.util.FileManager;
import com.opensymphony.xwork2.util.logging.Logger;
import com.opensymphony.xwork2.util.logging.LoggerFactory;
import net.sf.oval.configuration.Configurer;
import net.sf.oval.configuration.annotation.AnnotationsConfigurer;
import net.sf.oval.configuration.annotation.JPAAnnotationsConfigurer;
import net.sf.oval.configuration.xml.XMLConfigurer;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;


public class DefaultOValValidationManager implements OValValidationManager {
    private static final Logger LOG = LoggerFactory.getLogger(DefaultOValValidationManager.class);

    protected static final String VALIDATION_CONFIG_SUFFIX = "-validation.xml";
    protected final Map<String, List<Configurer>> validatorCache = new HashMap<String, List<Configurer>>();
    protected final Map<String, Configurer> validatorFileCache = new HashMap<String, Configurer>();

    protected boolean validateJPAAnnotations;

    public synchronized List<Configurer> getConfigurers(Class clazz, String context, boolean validateJPAAnnotations) {
        this.validateJPAAnnotations =validateJPAAnnotations;
        final String validatorKey = buildValidatorKey(clazz, context);

        if (validatorCache.containsKey(validatorKey)) {
            if (FileManager.isReloadingConfigs()) {
                List<Configurer> configurers = buildXMLConfigurers(clazz, context, true, null);

                //add an annotation configurer
                addAditionalConfigurers(configurers);
                validatorCache.put(validatorKey, configurers);
            }
        } else {
            List<Configurer> configurers = buildXMLConfigurers(clazz, context, false, null);

            //add an annotation configurer
            addAditionalConfigurers(configurers);
            validatorCache.put(validatorKey, configurers);
        }

        // get the set of validator configs
        return validatorCache.get(validatorKey);
    }

    private void addAditionalConfigurers(List<Configurer> configurers) {
        AnnotationsConfigurer annotationsConfigurer = new AnnotationsConfigurer();
        configurers.add(annotationsConfigurer);

        if (validateJPAAnnotations) {
            if (LOG.isDebugEnabled())
                LOG.debug("Adding support for JPA annotations validations in OVal");
            configurers.add(new JPAAnnotationsConfigurer());
        }
    }

    protected static String buildValidatorKey(Class clazz, String context) {
        StringBuilder sb = new StringBuilder(clazz.getName());
        sb.append("/");
        sb.append(context);
        return sb.toString();
    }

    private List<Configurer> buildXMLConfigurers(Class clazz, String context, boolean checkFile, Set<String> checked) {
        List<Configurer> configurers = new ArrayList<Configurer>();

        if (checked == null) {
            checked = new TreeSet<String>();
        } else if (checked.contains(clazz.getName())) {
            return configurers;
        }

        if (clazz.isInterface()) {
            for (Class anInterface : clazz.getInterfaces()) {
                configurers.addAll(buildXMLConfigurers(anInterface, context, checkFile, checked));
            }
        } else {
            if (!clazz.equals(Object.class)) {
                configurers.addAll(buildXMLConfigurers(clazz.getSuperclass(), context, checkFile, checked));
            }
        }

        // look for validators for implemented interfaces
        for (Class anInterface1 : clazz.getInterfaces()) {
            if (checked.contains(anInterface1.getName())) {
                continue;
            }

            addIfNotNull(configurers, buildClassValidatorConfigs(anInterface1, checkFile));

            if (context != null) {
                addIfNotNull(configurers, buildAliasValidatorConfigs(anInterface1, context, checkFile));
            }

            checked.add(anInterface1.getName());
        }

        addIfNotNull(configurers, buildClassValidatorConfigs(clazz, checkFile));

        if (context != null) {
            addIfNotNull(configurers, buildAliasValidatorConfigs(clazz, context, checkFile));
        }

        checked.add(clazz.getName());

        return configurers;
    }

    protected void addIfNotNull(List<Configurer> configurers, Configurer configurer) {
        if (configurer != null)
            configurers.add(configurer);
    }


    protected XMLConfigurer buildAliasValidatorConfigs(Class aClass, String context, boolean checkFile) {
        String fileName = aClass.getName().replace('.', '/') + "-" + context + VALIDATION_CONFIG_SUFFIX;

        return loadFile(fileName, aClass, checkFile);
    }

    protected XMLConfigurer buildClassValidatorConfigs(Class aClass, boolean checkFile) {
        String fileName = aClass.getName().replace('.', '/') + VALIDATION_CONFIG_SUFFIX;

        return loadFile(fileName, aClass, checkFile);
    }

    protected XMLConfigurer loadFile(String fileName, Class clazz, boolean checkFile) {
        if ((checkFile && FileManager.fileNeedsReloading(fileName, clazz)) || !validatorFileCache.containsKey(fileName)) {
            java.io.InputStream is = null;

            try {
                is = FileManager.loadFile(fileName, clazz);

                if (is != null) {
                    if (LOG.isDebugEnabled())
                        LOG.debug("Loading validation xml file [#0]", fileName);
                    XMLConfigurer configurer = new XMLConfigurer();
                    configurer.fromXML(is);
                    validatorFileCache.put(fileName, configurer);
                    return configurer;
                }
            } finally {
                if (is != null) {
                    try {
                        is.close();
                    } catch (java.io.IOException e) {
                        LOG.error("Unable to close input stream for [#0] ", e, fileName);
                    }
                }
            }
        } else {
            return (XMLConfigurer) validatorFileCache.get(fileName);
        }

        return null;
    }
}