/**
 * 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.xbean.spring.generator;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.FileOutputStream;
import java.io.OutputStream;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Properties;

import org.apache.xbean.spring.context.impl.NamespaceHelper;


/**
 * @author Dain Sundstrom
 * @version $Id$
 * @since 1.0
 */
public class XmlMetadataGenerator implements GeneratorPlugin {
    private final String metaInfDir;
    private LogFacade log;
    private final File schema;

    public static final String NAMESPACE_HANDLER = "org.apache.xbean.spring.context.v2.XBeanNamespaceHandler";
	private final boolean generateSpringSchemasFile;
	private final boolean generateSpringHandlersFile;

    public XmlMetadataGenerator(String metaInfDir, File schema) {
    	this(metaInfDir, schema, true, true);
    }
    
    public XmlMetadataGenerator(String metaInfDir, File schema, boolean generateSpringSchemasFile, boolean generateSpringHandlersFile) {
        this.metaInfDir = metaInfDir;
        this.schema = schema;
		this.generateSpringSchemasFile = generateSpringSchemasFile;
		this.generateSpringHandlersFile = generateSpringHandlersFile;
    }

    public void generate(NamespaceMapping namespaceMapping) throws IOException {
        String namespace = namespaceMapping.getNamespace();
        if (namespace == null) {
            return;
        }

        File file = new File(metaInfDir, NamespaceHelper.createDiscoveryPathName(namespace));
        file.getParentFile().mkdirs();
        log.log("Generating META-INF properties file: " + file + " for namespace: " + namespace);
        PrintWriter out = new PrintWriter(new FileWriter(file));
        try {
            generatePropertiesFile(out, namespaceMapping.getElements());
        } finally {
            out.close();
        }
        
        if( generateSpringHandlersFile ) {
        	
            // Generate spring 2.0 mapping
	        file = new File(metaInfDir, "META-INF/spring.handlers");

            Properties properties = new Properties();
            if (!file.exists()) {
                log.log("Generating Spring 2.0 handler mapping: " + file + " for namespace: " + namespace);
            } else {
                log.log("Updating Spring 2.0 handler mapping: " + file + " for namespace: " + namespace);

                // read in current file
                InputStream in = new FileInputStream(file);
                try {
                    properties.load(in);
                } catch (IOException e) {
                    in.close();
                }
            }

            // add property
            properties.put(namespace,  NAMESPACE_HANDLER);

            // write properties
            OutputStream fout = new FileOutputStream(file);
            try {
                properties.store(fout, "Generated by xbean-spring");
            } finally {
                fout.close();
            }
        }

        if (schema != null && generateSpringSchemasFile ) {
            String cp = new File(metaInfDir).toURI().relativize(schema.toURI()).toString();
            file = new File(metaInfDir, "META-INF/spring.schemas");

            Properties properties = new Properties();
            if (!file.exists()) {
                log.log("Generating Spring 2.0 schema mapping: " + file + " for namespace: " + namespace);
            } else {
                log.log("Updating Spring 2.0 schema mapping: " + file + " for namespace: " + namespace);

                // read in current file
                InputStream in = new FileInputStream(file);
                try {
                    properties.load(in);
                } catch (IOException e) {
                    in.close();
                }
            }

            // add property
            String uri = namespace;
            if (!uri.endsWith("/")) {
                uri += "/";
            }
            properties.put(uri + cp,  cp);

            // write properties
            OutputStream fout = new FileOutputStream(file);
            try {
                properties.store(fout, "Generated by xbean-spring");
            } finally {
                fout.close();
            }
        }
    }

    private void generatePropertiesFile(PrintWriter out, Set elements) {
        out.println("# NOTE: this file is autogenerated by Apache XBean");
        out.println();
        out.println("# beans");

        for (Iterator iter = elements.iterator(); iter.hasNext();) {
            ElementMapping element = (ElementMapping) iter.next();
            out.println(element.getElementName() + " = " + element.getClassName());

            generatePropertiesFileContent(out, element);
            generatePropertiesFilePropertyAliases(out, element);
            generatePropertiesFileConstructors(out, element);
            out.println();
        }
    }

    private void generatePropertiesFileContent(PrintWriter out, ElementMapping element) {
        String contentProperty = element.getContentProperty();
        if (contentProperty != null) {
            out.println(element.getElementName() + ".contentProperty = " + contentProperty);
        }
        String initMethod = element.getInitMethod();
        if (initMethod != null) {
            out.println(element.getElementName() + ".initMethod = " + initMethod);
        }

        String destroyMethod = element.getDestroyMethod();
        if (destroyMethod != null) {
            out.println(element.getElementName() + ".destroyMethod = " + destroyMethod);
        }

        String factoryMethod = element.getFactoryMethod();
        if (factoryMethod != null) {
            out.println(element.getElementName() + ".factoryMethod = " + factoryMethod);
        }

        for (Iterator iter = element.getAttributes().iterator(); iter.hasNext();) {
            AttributeMapping attribute = (AttributeMapping) iter.next();
            if( attribute.getPropertyEditor() !=null ) {
                out.println(element.getElementName() + "."+attribute.getPropertyName()+ ".propertyEditor = " + attribute.getPropertyEditor());
            }
        }

        List flatProperties = element.getFlatProperties();
        for (Iterator itr = flatProperties.iterator(); itr.hasNext();) {
            out.println(element.getElementName() + "." + itr.next() + ".flat");
        }

        Map maps = element.getMapMappings();
        for (Iterator itr = maps.entrySet().iterator(); itr.hasNext();) {
            Map.Entry entry = (Map.Entry) itr.next();
            MapMapping mm = (MapMapping) entry.getValue();
            if (mm.getEntryName() != null) {
                out.println(element.getElementName() + "." + entry.getKey() + ".map.entryName = " + mm.getEntryName());
            }
            if (mm.getKeyName() != null) {
                out.println(element.getElementName() + "." + entry.getKey() + ".map.keyName = " + mm.getKeyName());
            }
            if (mm.isFlat()) {
                out.println(element.getElementName() + "." + entry.getKey() + ".map.flat = " + Boolean.toString(mm.isFlat()));
            }
            if (mm.getDupsMode() != null) {
                out.println(element.getElementName() + "." + entry.getKey() + ".map.dups = " + mm.getDupsMode());
            }
            if (mm.getDefaultKey() != null) {
                out.println(element.getElementName() + "." + entry.getKey() + ".map.defaultKey = " + mm.getDefaultKey());
            }
        }

        Map flatCollections = element.getFlatCollections();
        for (Iterator itr = flatCollections.entrySet().iterator(); itr.hasNext();) {
            Map.Entry entry = (Map.Entry) itr.next();
            String child = (String) entry.getValue();
            out.println(element.getElementName() + "." + child + ".flatCollection = " + entry.getKey());
        }
    }

    private void generatePropertiesFileConstructors(PrintWriter out, ElementMapping element) {
        List constructors = element.getConstructors();
        for (Iterator iterator = constructors.iterator(); iterator.hasNext();) {
            List args = (List) iterator.next();
            generatePropertiesFileConstructor(out, element, args);
        }
    }

    private void generatePropertiesFileConstructor(PrintWriter out, ElementMapping element, List args) {
        out.print(element.getClassName());
        if (element.getFactoryMethod() != null) {
            out.print("." + element.getFactoryMethod());
        }
        out.print("(");
        for (Iterator iterator = args.iterator(); iterator.hasNext();) {
            ParameterMapping parameterMapping = (ParameterMapping) iterator.next();
            out.print(parameterMapping.getType().getName());
            if (iterator.hasNext()) {
                out.print(",");
            }
        }
        out.print(").parameterNames =");
        for (Iterator iterator = args.iterator(); iterator.hasNext();) {
            ParameterMapping parameterMapping = (ParameterMapping) iterator.next();
            out.print(" ");
            out.print(parameterMapping.getName());
        }
        out.println();
    }

    private void generatePropertiesFilePropertyAliases(PrintWriter out, ElementMapping element) {
        for (Iterator iterator = element.getAttributes().iterator(); iterator.hasNext();) {
            AttributeMapping attributeMapping = (AttributeMapping) iterator.next();
            String propertyName = attributeMapping.getPropertyName();
            String attributeName = attributeMapping.getAttributeName();
            if (!propertyName.equals(attributeName)) {
                if (List.class.getName().equals(attributeMapping.getType().getName())) {
                    out.println(element.getElementName() + ".list." + attributeName + " = " + propertyName);
                } else {
                    out.println(element.getElementName() + ".alias." + attributeName + " = " + propertyName);
                }
            }
        }
    }

    public LogFacade getLog() {
        return log;
    }

    public void setLog(LogFacade log) {
        this.log = log;
    }
}
