/*
 * Copyright 2004-2005 the original author or authors.
 * 
 * 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 org.codehaus.groovy.grails.web.metaclass;

import groovy.lang.GroovySystem;
import groovy.lang.MetaClass;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.codehaus.groovy.grails.commons.GrailsDomainClass;
import org.codehaus.groovy.grails.commons.GrailsDomainClassProperty;
import org.codehaus.groovy.grails.commons.metaclass.DynamicConstructor;
import org.codehaus.groovy.grails.exceptions.GrailsDomainException;
import org.codehaus.groovy.grails.web.binding.DataBindingUtils;
import org.springframework.context.ApplicationContext;

import javax.servlet.http.HttpServletRequest;
import java.util.Iterator;
import java.util.Map;

/**
 * A dynamic property that uses a Map of OGNL expressions to sets properties on the target object
 * 
 * @author Graeme Rocher
 * @since 0.3
 * 
 * Created 23/07/06
 */

public class DataBindingDynamicConstructor implements DynamicConstructor {
	private static final Log LOG = LogFactory.getLog( DataBindingDynamicConstructor.class );
    private ApplicationContext applicationContext;

    public DataBindingDynamicConstructor() {
    }

    public DataBindingDynamicConstructor(ApplicationContext ctx) {
        this.applicationContext = ctx;
    }

    public boolean isArgumentsMatch(Object[] args) {
        if(args.length == 0) return true;
        if(args.length > 1 ) return false;        
        if(args.length == 1) {
            if(HttpServletRequest.class.isAssignableFrom(args[0].getClass())) return true;
            if(Map.class.isAssignableFrom(args[0].getClass())) return true;
        }
		return false;
	}

	public Object invoke(Class clazz, Object[] args) {
		Object map = args.length > 0 ? args[0] : null;
        Object instance;
        if(applicationContext!=null && applicationContext.containsBean(clazz.getName())) {
            instance = applicationContext.getBean(clazz.getName());
        }
        else {

            try {
                instance = clazz.newInstance();
            } catch (InstantiationException e1) {
                throw new GrailsDomainException("Error instantiated class [" + clazz + "]: " + e1.getMessage(),e1);
            } catch (IllegalAccessException e1) {
                throw new GrailsDomainException("Illegal access instantiated class [" + clazz + "]: " + e1.getMessage(),e1);
            }
        }


        if (map !=null) {
            DataBindingUtils.bindObjectToInstance(instance, map);
            GrailsDomainClass domainClass = null;
            String domainClassBeanName = clazz.getName() + "DomainClass";
            if(applicationContext != null && applicationContext.containsBean(domainClassBeanName)) {
                domainClass = (GrailsDomainClass)applicationContext.getBean(domainClassBeanName);
            }
            if(map instanceof Map && domainClass != null) {
                Map theMap = (Map) map;
                for (Iterator i = theMap.keySet().iterator(); i.hasNext();) {
                    Object key = i.next();
                    String propertyName = key.toString();
                    if(propertyName.indexOf('.') >-1) {
                        propertyName = propertyName.substring(0,propertyName.indexOf('.'));
                    }
                    if(domainClass.hasPersistentProperty(propertyName)) {
                        GrailsDomainClassProperty prop = domainClass.getPropertyByName(propertyName);
                        if(prop != null && prop.isOneToOne() && prop.isBidirectional()) {
                            Object val = theMap.get(key);
                            GrailsDomainClassProperty otherSide = prop.getOtherSide();
                            if(val != null && otherSide != null) {
                                MetaClass mc = GroovySystem.getMetaClassRegistry().getMetaClass(val.getClass());
                                try {
                                    mc.setProperty(val,otherSide.getName(), instance);
                                } catch (Exception e) {
                                    // ignore
                                }
                            }
                        }                        
                    }

                }
            }
        }

        return instance;
	}


}
