/*
 *  Copyright 2001-2013 Stephen Colebourne
 *
 *  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.joda.beans.ui.form;

import java.lang.annotation.Annotation;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Objects;

import javax.validation.constraints.DecimalMax;
import javax.validation.constraints.DecimalMin;
import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;

import org.joda.beans.MetaBean;
import org.joda.beans.MetaProperty;
import org.joda.beans.PropertyDefinition;

/**
 * Factory used to create {@code MetaUIForm} instances.
 */
public class MetaUIFactory {

    /**
     * Creates an instance.
     */
    public MetaUIFactory() {
    }

    //-------------------------------------------------------------------------
    /**
     * Creates a form for a meta-bean.
     * 
     * @param metaBean  the meta-bean, not null
     * @return the form, not null
     */
    public MetaUIForm createForm(final MetaBean metaBean) {
        Objects.requireNonNull(metaBean, "metaBean");
        MetaUIForm form = new MetaUIForm(metaBean);
        for (MetaProperty<?> mp : metaBean.metaPropertyIterable()) {
            buildComponent(form, mp);
        }
        return form;
    }

    /**
     * Builds the component for the property.
     * 
     * @param form  the form to add to, not null
     * @param mp  the meta-property, not null
     */
    protected void buildComponent(MetaUIForm form, MetaProperty<?> mp) {
        MetaUIComponent comp = new MetaUIComponent(mp);
        buildMandatory(comp);
        buildMinMax(comp);
        buildSize(comp);
        buildValues(comp);
        form.getComponents().add(comp);
    }

    /**
     * Builds the mandatory flag for the component.
     * 
     * @param comp  the component, not null
     */
    protected void buildMandatory(MetaUIComponent comp) {
        if (comp.getMetaProperty().propertyType().isPrimitive()) {
            comp.setMandatory(true);
        }
        PropertyDefinition propAnno = findAnnotation(comp, PropertyDefinition.class);
        if (propAnno != null) {
            if (propAnno.validate().equals("notNull") || propAnno.validate().equals("notEmpty")) {
                comp.setMandatory(true);
            }
        }
        NotNull nnAnno = findAnnotation(comp, NotNull.class);
        if (nnAnno != null) {
            comp.setMandatory(true);
        }
    }

    /**
     * Builds the min/max values for the component.
     * 
     * @param comp  the component, not null
     */
    protected void buildMinMax(MetaUIComponent comp) {
        DecimalMax dmaxAnno = findAnnotation(comp, DecimalMax.class);
        DecimalMin dminAnno = findAnnotation(comp, DecimalMin.class);
        Max maxAnno = findAnnotation(comp, Max.class);
        Min minAnno = findAnnotation(comp, Min.class);
        if (dminAnno != null) {
            comp.setMinValue(new BigDecimal(dminAnno.value()));
        } else if (minAnno != null) {
            comp.setMinValue(minAnno.value());
        }
        if (dmaxAnno != null) {
            comp.setMaxValue(new BigDecimal(dmaxAnno.value()));
        } else if (maxAnno != null) {
            comp.setMaxValue(maxAnno.value());
        }
    }

    /**
     * Builds the min/max size for the component.
     * 
     * @param comp  the component, not null
     */
    protected void buildSize(MetaUIComponent comp) {
        Size sizeAnno = findAnnotation(comp, Size.class);
        if (sizeAnno != null) {
            comp.setMinSize(sizeAnno.min());
            comp.setMaxSize(sizeAnno.max());
        }
    }

    /**
     * Builds the selectable values for the component.
     * 
     * @param comp  the component, not null
     */
    protected void buildValues(MetaUIComponent comp) {
        Class<?> type = comp.getMetaProperty().propertyType();
        if (Enum.class.isAssignableFrom(type)) {
            Object[] values = type.getEnumConstants();
            comp.setLimitedValues(true);
            comp.setSelectableValues(new ArrayList<>(Arrays.asList(values)));
        }
    }

    //-------------------------------------------------------------------------
    /**
     * Finds an annotation on the property, returning null if not found.
     * 
     * @param <A>  the annotation type
     * @param metaComponent  the meta-component to examine, not null
     * @param annotationType  the annotation type, not null
     * @return the annotation, null if not found
     */
    protected <A extends Annotation> A findAnnotation(MetaUIComponent metaComponent, Class<A> annotationType) {
        try {
            return metaComponent.getMetaProperty().annotation(annotationType);
        } catch (RuntimeException ex) {
            return null;
        }
    }

}

@@ -1229,20 +1229,71 @@
 tances.%0A
+ * %3Cp%3E%0A * This class is designed to be subclassed.%0A
  */%0A
-
 public c
@@ -1759,26 +1759,31 @@
  form = 
-new 
+createEmpty
 Meta
-UI
 Form(met
@@ -1927,32 +1927,294 @@
 rn form;%0A    %7D%0A%0A
+    /**%0A     * Creates an empty form.%0A     * %0A     * @param metaBean  the meta-bean, not null%0A     * @return the empty form, not null%0A     */%0A    protected MetaUIForm createEmptyMetaForm(final MetaBean metaBean) %7B%0A        return new MetaUIForm(metaBean);%0A    %7D%0A%0A
     /**%0A     * B
@@ -2466,26 +2466,31 @@
  comp = 
-new 
+createEmpty
 Meta
-UI
 Componen
@@ -2597,32 +2597,32 @@
 ldValues(comp);%0A
-
         form.get
@@ -2644,32 +2644,336 @@
 d(comp);%0A    %7D%0A%0A
+    /**%0A     * Creates an empty component.%0A     * %0A     * @param metaProperty  the meta-property, not null%0A     * @return the empty component, not null%0A     */%0A    protected MetaUIComponent createEmptyMetaComponent(MetaProperty%3C?%3E metaProperty) %7B%0A        return new MetaUIComponent(metaProperty);%0A    %7D%0A%0A
     /**%0A     * B

Allow MetaUIFactory to be subclassed