/*
 * $Id$
 *
 * License Agreement.
 *
 * Rich Faces - Natural Ajax for Java Server Faces (JSF)
 *
 * Copyright (C) 2007 Exadel, Inc.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License version 2.1 as published by the Free Software Foundation.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 */
package org.richfaces.cdk.generate.freemarker;

import java.util.NoSuchElementException;
import java.util.Set;

import org.richfaces.cdk.model.BehaviorModel;
import org.richfaces.cdk.model.ClassName;
import org.richfaces.cdk.model.ComponentModel;
import org.richfaces.cdk.model.ConverterModel;
import org.richfaces.cdk.model.EventModel;
import org.richfaces.cdk.model.EventName;
import org.richfaces.cdk.model.MethodSignature;
import org.richfaces.cdk.model.ModelElementBase;
import org.richfaces.cdk.model.PropertyBase;
import org.richfaces.cdk.model.ValidatorModel;

import com.google.common.base.Predicate;
import com.google.common.collect.Collections2;
import com.google.common.collect.Iterables;
import com.google.common.collect.Sets;

import freemarker.ext.beans.BeansWrapper;
import freemarker.ext.beans.StringModel;
import freemarker.ext.util.ModelFactory;
import freemarker.template.ObjectWrapper;
import freemarker.template.TemplateModel;
import freemarker.template.TemplateModelException;

/**
 * <p class="changed_added_4_0">
 * </p>
 *
 * @author asmirnov@exadel.com
 *
 */
public class ModelElementBaseTemplateModel extends StringModel implements TemplateModel {
    static final ModelFactory FACTORY = new ModelFactory() {
        public TemplateModel create(Object object, ObjectWrapper wrapper) {
            return new ModelElementBaseTemplateModel((ModelElementBase) object, (BeansWrapper) wrapper);
        }
    };
    private final ModelElementBase model;
    private Set<EventName> eventNames;
    private final String name;

    /**
     * <p class="changed_added_4_0">
     * </p>
     *
     * @param object
     * @param wrapper
     */
    public ModelElementBaseTemplateModel(ModelElementBase object, BeansWrapper wrapper) {
        super(object, wrapper);
        model = object;
        if (model instanceof ComponentModel) {
            this.name = "Component";
        } else if (model instanceof BehaviorModel) {
            this.name = "Behavior";
        } else if (model instanceof ConverterModel) {
            this.name = "Converter";
        } else if (model instanceof ValidatorModel) {
            this.name = "Validator";
        } else {
            this.name = "UnknownModelElement";
        }
    }

    @Override
    public String getAsString() {
        return name;
    }

    @Override
    public TemplateModel get(String key) throws TemplateModelException {
        if ("requiredAttributes".equals(key)) {
            return requiredAttributes();
        } else if ("generatedAttributes".equals(key)) {
            return generatedAttributes();
        } else if ("tagAttributes".equals(key)) {
            return tagAttributes();
        } else if ("eventNames".equals(key)) {
            return eventNames();
        } else if ("defaultEvent".equals(key)) {
            return defaultEvent();
        } else if ("importClasses".equals(key)) {
            return getImportClasses();
        } else if ("implemented".equals(key)) {
            return getImplementedInterfaces();
        } else if ("tagImports".equals(key)) {
            return getTagImports();
        } else if ("hasBindingAttribute".equals(key)) {
            return hasBindingAttribute();
        }

        return super.get(key);
    }

    public TemplateModel getImportClasses() throws TemplateModelException {
        Set<ClassName> result = Sets.newTreeSet();
        String targetPackage = model.getTargetClass().getPackage();
        for (PropertyBase entry : model.getAttributes()) {
            if (entry.getGenerate()) {
                addIfNecessary(targetPackage, result, entry.getType());
            }
        }
        // Import all interfaces implemented by the generated class.
        result.addAll(model.getInterfaces());
        if (model instanceof ComponentModel) {
            ComponentModel component = (ComponentModel) model;
            for (EventModel event : component.getEvents()) {
                addIfNecessary(targetPackage, result, event.getSourceInterface());
                addIfNecessary(targetPackage, result, event.getListenerInterface());
            }
        }
        // Collection<String> list = new ArrayList<String>(result);
        return this.wrapper.wrap(result);
    }

    private void addIfNecessary(String pkg, Set<ClassName> classNames, ClassName toadd) {
        if (null != toadd && !isPredefined(toadd) && !pkg.equals(toadd.getPackage())) {
            classNames.add(toadd);
        }
    }

    public TemplateModel getTagImports() throws TemplateModelException {
        Set<ClassName> result = Sets.newTreeSet();

        for (PropertyBase entry : model.getAttributes()) {
            if (!(entry.isHidden() || entry.isReadOnly() || null == entry.getSignature())) {
                MethodSignature methodSignature = entry.getSignature();
                if (!isPredefined(methodSignature.getReturnType())) {
                    result.add(methodSignature.getReturnType());
                }
                for (ClassName className : methodSignature.getParameters()) {
                    if (!isPredefined(className)) {
                        result.add(className);
                    }
                }
            }
        }
        return this.wrapper.wrap(result);
    }

    public TemplateModel hasBindingAttribute() throws TemplateModelException {
        return wrapper.wrap(Iterables.any(model.getAttributes(), new Predicate<PropertyBase>() {
            @Override
            public boolean apply(PropertyBase input) {
                return input.isBindingAttribute();
            }
        }));
    }

    public TemplateModel getImplementedInterfaces() throws TemplateModelException {
        Set<ClassName> result = Sets.newTreeSet();
        // Import all interfaces implemented by the generated class.
        result.addAll(model.getInterfaces());
        if (model instanceof ComponentModel) {
            ComponentModel component = (ComponentModel) model;
            if (getEventNames().size() > 0) {
                result.add(ClassName.parseName("javax.faces.component.behavior.ClientBehaviorHolder"));
            }
            for (EventModel event : component.getEvents()) {
                ClassName sourceInterface = event.getSourceInterface();
                if (null != sourceInterface) {
                    result.add(sourceInterface);
                }
            }
        }
        // Collection<String> list = new ArrayList<String>(result);
        return this.wrapper.wrap(result);
    }

    public boolean isPredefined(PropertyBase property) {
        return isPredefined(property.getType());
    }

    public boolean isPredefined(ClassName type) {
        return type.isPrimitive() || "java.lang".equals(type.getPackage());
    }

    private TemplateModel eventNames() throws TemplateModelException {
        return wrapper.wrap(getEventNames());
    }

    private TemplateModel defaultEvent() throws TemplateModelException {
        Set<EventName> names = getEventNames();

        try {
            EventName defaultEvent = Iterables.find(names, new Predicate<EventName>() {
                @Override
                public boolean apply(EventName event) {
                    return event.isDefaultEvent();
                }
            });

            return wrapper.wrap(defaultEvent);
        } catch (NoSuchElementException e) {
            return wrapper.wrap(null);
        }
    }

    private Set<EventName> getEventNames() {
        if (null == eventNames) {
            eventNames = Sets.newHashSet();

            for (PropertyBase property : model.getAttributes()) {
                eventNames.addAll(property.getEventNames());
            }
        }

        return eventNames;
    }

    private TemplateModel requiredAttributes() throws TemplateModelException {
        return wrapper.wrap(Collections2.filter(model.getAttributes(), new Predicate<PropertyBase>() {
            @Override
            public boolean apply(PropertyBase input) {
                return input.isRequired();
            }
        }));
    }

    private TemplateModel generatedAttributes() throws TemplateModelException {
        return wrapper.wrap(Collections2.filter(model.getAttributes(), new Predicate<PropertyBase>() {
            @Override
            public boolean apply(PropertyBase input) {
                return input.getGenerate();
            }
        }));
    }

    private TemplateModel tagAttributes() throws TemplateModelException {
        return wrapper.wrap(Collections2.filter(model.getAttributes(), new Predicate<PropertyBase>() {
            @Override
            public boolean apply(PropertyBase input) {
                return !(input.isHidden() || input.isReadOnly());
            }
        }));
    }
}
