/*
 * 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 com.we_lab_doc.spacecard.plugin.utils;

import groovy.lang.GString;
import groovy.lang.MissingMethodException;
import org.codehaus.groovy.grails.commons.GrailsClassUtils;
import org.codehaus.groovy.grails.orm.hibernate.exceptions.GrailsQueryException;
import org.codehaus.groovy.grails.orm.hibernate.cfg.GrailsHibernateUtil;
import org.hibernate.*;
import org.hibernate.criterion.Example;
import org.springframework.orm.hibernate3.HibernateCallback;

import java.sql.SQLException;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;
import org.codehaus.groovy.grails.orm.hibernate.metaclass.*;

/**
 * @author Shigeru GOUGI
 * @since 0.1
 *        <p/>
 *        Created: March 17, 2008
 * 
 *  original: Grails 1.0.1
 *   src/persistence/org/codehaus/groovy/grails/orm/hibernate/metaclass/FindAllPersistentMethod.java
 */
public class FindAllPersistentMethod
		extends AbstractStaticPersistentMethod {

	public FindAllPersistentMethod(SessionFactory sessionFactory,
			ClassLoader classLoader) {
		super(sessionFactory, classLoader, Pattern.compile("^findAll$"));
	}

	@SuppressWarnings("unchecked")
	protected Object doInvokeInternal(final Class clazz, String methodName, final Object[] arguments) {
		if (arguments.length == 0)
			return getHibernateTemplate().loadAll(clazz);

        final Object arg = arguments[0] instanceof GString ? arguments[0].toString() : arguments[0];

		// if the arg is an instance of the class find by example
		if (arg instanceof String) {
			final String query = ((String) arg).trim();
			//final String shortName = GrailsClassUtils.getShortName(clazz);
			return super.getHibernateTemplate().executeFind(new HibernateCallback() {
				public Object doInHibernate(Session session) throws HibernateException, SQLException {
					Query q = session.createQuery(query);

                    Object[] queryArgs = null;
					Map queryNamedArgs = null;
					int max = retrieveMaxValue(arguments);
					int offset = retrieveOffsetValue(arguments);
					if (arguments.length > 1) {
						if (arguments[1] instanceof Collection) {
							queryArgs = GrailsClassUtils.collectionToObjectArray((Collection) arguments[1]);
						} else if (arguments[1].getClass().isArray()) {
							queryArgs = (Object[]) arguments[1];
						} else if (arguments[1] instanceof Map) {
							queryNamedArgs = (Map) arguments[1];
						}
					}
                    if (queryArgs != null) {
                        for (int i = 0; i < queryArgs.length; i++) {
							if (queryArgs[i] instanceof GString) {
								q.setParameter(i, queryArgs[i].toString());
							} else {
								q.setParameter(i, queryArgs[i]);
							}
						}
					}
					if (queryNamedArgs != null) {
                        Iterator it = queryNamedArgs.entrySet().iterator();
                        while (it.hasNext()) {
							Map.Entry entry = (Map.Entry) it.next();
                            if (!(entry.getKey() instanceof String))
                                throw new GrailsQueryException("Named parameter's name must be String: "
                                        + queryNamedArgs.toString());
                            String stringKey = (String) entry.getKey();
                            // Won't try to bind these parameters since they are proceeded separately
                            if( GrailsHibernateUtil.ARGUMENT_MAX.equals(stringKey) || GrailsHibernateUtil.ARGUMENT_OFFSET.equals(stringKey) ) continue;
                            Object value = entry.getValue();
                            if (value instanceof GString) {
                                q.setParameter(stringKey, value.toString());
                            } else if (List.class.isAssignableFrom(value.getClass())) {
                                q.setParameterList(stringKey, (List) value);
                            } else if (value.getClass().isArray()) {
                                q.setParameterList(stringKey, (Object[]) value);
                            } else {
                                q.setParameter(stringKey, value);
                            }
                        }
                    }
					if (max > 0) {
						q.setMaxResults(max);
					}
					if (offset > 0) {
						q.setFirstResult(offset);
					}
					return q.list();

				}

				private int retrieveMaxValue(Object[] arguments) {
					int result = -1;
					if( arguments.length > 1) { 
						result = retrieveInt(arguments[1], GrailsHibernateUtil.ARGUMENT_MAX);
						if( arguments.length > 2 && result == -1 ) {
							result = retrieveInt(arguments[2], GrailsHibernateUtil.ARGUMENT_MAX);
						}
					}
					return result;
				}

				private int retrieveOffsetValue(Object[] arguments) {
					int result = -1;
					if( arguments.length > 1) {
						if( isMapWithValue(arguments[1], GrailsHibernateUtil.ARGUMENT_OFFSET) ) {
							result = ((Number)((Map)arguments[1]).get(GrailsHibernateUtil.ARGUMENT_OFFSET)).intValue();
						}
						if( arguments.length > 2 && result == -1 ) {
							if( isMapWithValue(arguments[2], GrailsHibernateUtil.ARGUMENT_OFFSET) ) {
								result = ((Number)((Map)arguments[2]).get(GrailsHibernateUtil.ARGUMENT_OFFSET)).intValue();
							} else if( isIntegerOrLong(arguments[1]) && isIntegerOrLong(arguments[2])) {	
								result = ((Number)arguments[2]).intValue();
							}
						}
						if( arguments.length > 3 && result == -1 ) {
							if( isIntegerOrLong(arguments[3]) ) {	
								result = ((Number)arguments[3]).intValue();
							}
						}
					}
					return result;
				}
				
				private int retrieveInt( Object param, String key ) {
					try {
						if( isMapWithValue(param, key) ) {
							return ((Number)((Map)param).get(key)).intValue();
						} else if( isIntegerOrLong(param) ) {
							return ((Number)param).intValue();
						}
					} catch (Throwable t) {
						System.err.println("### param = " + param + ", key = " + key);
						t.printStackTrace();
					}
					return -1;
				}
				
				private boolean isIntegerOrLong( Object param ) {
					return (param instanceof Integer) || (param instanceof Long);
				}
				
				private boolean isMapWithValue( Object param, String key ) {
					return (param instanceof Map) && ((Map)param).containsKey(key);
				}
			});
		} else if (clazz.isAssignableFrom(arg.getClass())) {
			return super.getHibernateTemplate().executeFind(new HibernateCallback() {

				public Object doInHibernate(Session session) throws HibernateException, SQLException {

					Example example = Example.create(arg).ignoreCase();

					Criteria crit = session.createCriteria(clazz);
					crit.add(example);

                    if(arguments.length > 1 && arguments[0] instanceof Map) {
                        GrailsHibernateUtil.populateArgumentsForCriteria(crit, (Map)arguments[1] );
                    }

                    return crit.list();
				}

			});
		}

        else if(arguments[0] instanceof Map) {
			return super.getHibernateTemplate().executeFind(new HibernateCallback() {
				public Object doInHibernate(Session session) throws HibernateException, SQLException {
					Criteria crit = session.createCriteria(clazz);
                        GrailsHibernateUtil.populateArgumentsForCriteria(crit, (Map)arguments[0] );
                    return crit.list();
				}

			});
        }

        throw new MissingMethodException(methodName, clazz, arguments);
	}

}
