/**
 * Redistribution and use of this software and associated documentation
 * ("Software"), with or without modification, are permitted provided
 * that the following conditions are met:
 *
 * 1. Redistributions of source code must retain copyright
 *    statements and notices.  Redistributions must also contain a
 *    copy of this document.
 *
 * 2. Redistributions in binary form must reproduce the
 *    above copyright notice, this list of conditions and the
 *    following disclaimer in the documentation and/or other
 *    materials provided with the distribution.
 *
 * 3. The name "Exolab" must not be used to endorse or promote
 *    products derived from this Software without prior written
 *    permission of Intalio, Inc.  For written permission,
 *    please contact info@exolab.org.
 *
 * 4. Products derived from this Software may not be called "Exolab"
 *    nor may "Exolab" appear in their names without prior written
 *    permission of Intalio, Inc. Exolab is a registered
 *    trademark of Intalio, Inc.
 *
 * 5. Due credit should be given to the Exolab Project
 *    (http://www.exolab.org/).
 *
 * THIS SOFTWARE IS PROVIDED BY INTALIO, INC. AND CONTRIBUTORS
 * ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT
 * NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
 * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL
 * INTALIO, INC. OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
 * OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * Copyright 1999 (C) Intalio, Inc. All Rights Reserved.
 *
 * $Id: DatingService.java 8141 2009-03-28 11:36:50Z rjoachim $
 */
package org.exolab.castor.persist;

import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Vector;

import org.exolab.castor.jdo.engine.SQLEngine;
import org.exolab.castor.mapping.MappingException;
import org.exolab.castor.mapping.loader.Types;

/**
 * DataService is a helper class for constructing <tt>ClassMolder</tt>s and
 * pairing up ClassMolders which depends and extends the other.
 *
 * @author <a href="mailto:yip@intalio.com">Thomas Yip</a>
 */
public class DatingService {

    private ClassLoader _loader;

    private Hashtable<String, ClassMolder> _clsMolders;

    private Vector<ClassPair> _needExtendsClassMolder;

    private Vector<ClassPair> _needDependsClassMolder;

    private Vector<FieldPair> _needFieldClass;

    private Hashtable<String, Class<?>> _javaClasses;

    DatingService(final ClassLoader loader) {
        _loader = loader;
    }

    /**
     * Indicate that all ClassMolder is registered. DatingService
     * will resolve all the outstanding relation now.
     */
    void close() throws MappingException {
        ClassMolder initiateCm;
        ClassMolder targetCm;
        FieldMolder initiateFm;
        
        // resolve extends
        if (_needExtendsClassMolder != null) {
            Enumeration<ClassPair> e = _needExtendsClassMolder.elements();
            while (e.hasMoreElements()) {
                ClassPair pair = e.nextElement();
                initiateCm = pair.getValue();
                targetCm = _clsMolders.get(pair.getKey());
                if (targetCm == null) {
                    throw new MappingException(
                            "Extended element, \"" + pair.getKey() + "\"  not found!");
                }
                initiateCm.setExtends(targetCm);
                ((SQLEngine) initiateCm.getPersistence()).setExtends(
                        (SQLEngine) targetCm.getPersistence());
            }
        }

        // resolve depends
        if (_needDependsClassMolder != null) {
            Enumeration<ClassPair> e = _needDependsClassMolder.elements();
            while (e.hasMoreElements()) {
                ClassPair pair = e.nextElement();
                initiateCm = pair.getValue();
                targetCm = _clsMolders.get(pair.getKey());
                if (targetCm == null) {
                    throw new MappingException(
                            "Depended element, \"" + pair.getKey() + "\"  not found!");
                }
                initiateCm.setDepends(targetCm);
            }
        }

        // resolve depends field
        if (_needFieldClass != null) {
            Enumeration<FieldPair> e = _needFieldClass.elements();
            while (e.hasMoreElements()) {
                FieldPair pair = e.nextElement();
                initiateFm = pair.getValue();
                targetCm = _clsMolders.get(pair.getKey());
                if (targetCm == null) {
                    throw new MappingException(
                            "Field element, \"" + pair.getKey() + "\"  not found!");
                }
                initiateFm.setFieldClassMolder(targetCm);
            }
        }
    } 

    /** 
     * Pair up ClassMolder and it extends class.
     * 
     * @return true if they can be paired up immediately.
     */
    boolean pairExtends(final ClassMolder me, final String extName) throws MappingException {

        if ((extName == null) || extName.equals("")) {
            throw new IllegalArgumentException("Null classname not allowed!");
        }

        ClassMolder clsMold = _clsMolders.get(extName);
        if (clsMold != null) {
            me.setExtends(clsMold);
            SQLEngine sql = ((SQLEngine) me.getPersistence());
            if (sql == null) {
                throw new MappingException(
                        "Class " + me + " extends on " + extName
                        + " which is not persistence capable!");
            }
            sql.setExtends((SQLEngine) clsMold.getPersistence());
            return true;
        }

        if (_needExtendsClassMolder == null) {
            _needExtendsClassMolder = new Vector<ClassPair>();
        }

        _needExtendsClassMolder.add(new ClassPair(extName, me));
        return false;
    }

    /** 
     * Pair up ClassMolder and it depends class.
     * @return true if they can be paired up immediately.
     */
    boolean pairDepends(final ClassMolder me, final String depName) {
        if ((depName == null) || (depName.equals(""))) {
            return true;
        }

        ClassMolder clsMold = _clsMolders.get(depName);
        if (clsMold != null) {
            me.setDepends(clsMold);
            return true;
        }

        if (_needDependsClassMolder == null) {
            _needDependsClassMolder = new Vector<ClassPair>();
        }

        _needDependsClassMolder.add(new ClassPair(depName, me));
        return false;
    }

    /**
     * Resolve the java.lang.Class of the fully qualified class name.
     */
    Class<?> resolve(final String className) throws ClassNotFoundException {
        if (_javaClasses == null) {
            _javaClasses = new Hashtable<String, Class<?>>();
        } else if (_javaClasses.contains(className)) {
            return _javaClasses.get(className);
        }
        
        Class<?> resolved = Types.typeFromName(_loader, className);
        _javaClasses.put(className, resolved);
        return resolved;
    }

    /**
     * Pair the FieldMolder with the ClassMolder of typeName.
     * 
     * @param fieldMolder the fieldMolder to be paired.
     * @param typeName Type of the field which the FieldMolder represents.
     * @exception MappingException indicates that the pairing failed.
     */
    boolean pairFieldClass(final FieldMolder fieldMolder, final String typeName)
    throws MappingException {
        
        try {
            if ((typeName == null) || typeName.equals("")) {
                return true;
            }

            if (Types.isSimpleType(resolve(typeName))) {
                return true;
            }

            if (Types.isEnumType(resolve(typeName))) {
                return true;
            }

            if (Types.isConvertibleType(resolve(typeName))) {
                return true;
            }

            ClassMolder clsMold = _clsMolders.get(typeName);
            if (clsMold != null) {
                fieldMolder.setFieldClassMolder(clsMold);
                // fieldMolder.getEnclosingClassMolder().resetResolver(fieldMolder);
                return true;
            }

            if (_needFieldClass == null) {
                _needFieldClass = new Vector<FieldPair>();
            }
            _needFieldClass.add(new FieldPair(typeName, fieldMolder));
            return false;
        } catch (ClassNotFoundException e) {
            throw new MappingException("ClassNotFound :\n" + e);
        }
    }

    /**
     * Register the name of a ClassMolder which will be pairing
     * up.
     */
    void register(final String name, final ClassMolder clsMold) {
        if (_clsMolders == null) {
            _clsMolders = new Hashtable<String, ClassMolder>();
        }
        _clsMolders.put(name, clsMold);
    }

    private final class ClassPair {
        private String _key;
        private ClassMolder _value;
        
        private ClassPair(final String key, final ClassMolder value) {
            _key = key;
            _value = value;
        }
        
        public String getKey() { return _key; }
        
        public ClassMolder getValue() { return _value; }
    }

    private final class FieldPair {
        private String _key;
        private FieldMolder _value;
        
        private FieldPair(final String key, final FieldMolder value) {
            _key = key;
            _value = value;
        }
        
        public String getKey() { return _key; }
        
        public FieldMolder getValue() { return _value; }
    }
}
