/*
 * Copyright 2016 Matti Tahvonen.
 *
 * 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.vaadin.viritinv7.fields;

import com.vaadin.v7.data.Property;
import com.vaadin.ui.Component;
import com.vaadin.v7.ui.CustomField;
import org.apache.commons.lang3.StringUtils;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Collection;

/**
 * A simple TextField based component to edit collections of objects, which can
 * be converted to String presentation and back.
 * <p>
 * Both split and joinSeparator can be specified and one can provide custom
 * strategies to convert from String presentation to element types.
 */
public class CommaSeparatedCollectionField extends CustomField<Collection> {

    private static final long serialVersionUID = 2443075282417590322L;

    public interface FromStringInstantiator<T> {

        T instance(String stringPresentation);
    }

    MTextField textField = new MTextField();
    private Collection collection;
    private String splitSeparatorRegexp = "\\s*,\\s*";
    private String joinSeparator = ", ";
    private Class<?> elementType;
    private FromStringInstantiator instantiator;

    public CommaSeparatedCollectionField() {
        textField.addValueChangeListener(new ValueChangeListener() {
            private static final long serialVersionUID = -382717228031608542L;
            @Override
            public void valueChange(Property.ValueChangeEvent event) {
                if (textField.isUserValueChange()) {

                    String[] parts = textField.getValue().split(splitSeparatorRegexp);
                    // TODO null check for collection and create it if not set (e.g. null in property)
                    collection.clear();

                    for (final String part : parts) {
                        if (instantiator != null) {
                            collection.add(instantiator.instance(part));
                        } else if (elementType != null) {

                            try {

                                Method declaredMethod = elementType.getDeclaredMethod("valueOf", String.class);
                                collection.add(declaredMethod.invoke(null, part));
                            } catch (NoSuchMethodException ex) {
                                try {
                                    collection.add(elementType.getConstructor(String.class).newInstance(part));
                                } catch (IllegalAccessException | IllegalArgumentException | InstantiationException | NoSuchMethodException | SecurityException | InvocationTargetException ex1) {
                                throw new RuntimeException("The string " + part + " could not be converted to " + elementType.getSimpleName(), ex1);
                                }
                            } catch (IllegalAccessException | IllegalArgumentException | SecurityException | InvocationTargetException ex) {
                                throw new RuntimeException("The string " + part + " could not be converted to " + elementType.getSimpleName(), ex);
                            }
                        } else {
                            // assume String values in the collection
                            collection.add(part);
                        }
                    }
                }
                fireValueChange(true);
            }
        });
    }

    @Override
    protected Component initContent() {
        return textField;
    }

    @Override
    public Class<? extends Collection> getType() {
        return Collection.class;
    }

    @Override
    protected void setInternalValue(Collection newValue) {
        this.collection = newValue;

        String toPresentation = StringUtils.join(collection, joinSeparator);
        textField.setValue(toPresentation);
        super.setInternalValue(newValue);
    }

    public Class<?> getElementType() {
        return elementType;
    }

    /**
     * Sets the type of element in the collection. The class needs to have
     * either static valueOf(String) method or a constructor with String
     * parameter. Those will be used to instantiate objects from user input.
     * Alternatively you can specify an explicit instantiator.
     * <p>
     * If the element type in collection is String, no elmentType or
     * instantiator is needed.
     *
     * @param elementType the type of objects in the collection.
     */
    public void setElementType(Class<?> elementType) {
        this.elementType = elementType;
    }

    public void setInstantiator(FromStringInstantiator instantiator) {
        this.instantiator = instantiator;
    }

    public FromStringInstantiator getInstantiator() {
        return instantiator;
    }

    public String getJoinSeparator() {
        return joinSeparator;
    }

    /**
     * @param joinSeparator the separator string to be used when joining objects
     * to string presentation for editing. The default value is ", "
     */
    public void setJoinSeparator(String joinSeparator) {
        this.joinSeparator = joinSeparator;
    }

    public String getSplitSeparatorRegexp() {
        return splitSeparatorRegexp;
    }

    /**
     * Sets the split separator that is used to slice the input string into a
     * parts. The value is a regular expression so you can strip away extra
     * spaces. The default value is "\\s*,\\s*".
     *
     * @param splitSeparatorRegexp the split separator
     */
    public void setSplitSeparatorRegexp(String splitSeparatorRegexp) {
        this.splitSeparatorRegexp = splitSeparatorRegexp;
    }

}
