/*
 * Copyright 2013 Red Hat, Inc. and/or its affiliates.
 *
 * Licensed under the Eclipse Public License version 1.0, available at
 * http://www.eclipse.org/legal/epl-v10.html
 */
package org.jboss.forge.addon.ui.util;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import org.jboss.forge.addon.convert.CompositeConverter;
import org.jboss.forge.addon.convert.Converter;
import org.jboss.forge.addon.convert.ConverterFactory;
import org.jboss.forge.addon.facets.Facet;
import org.jboss.forge.addon.ui.facets.HintsFacet;
import org.jboss.forge.addon.ui.hints.InputType;
import org.jboss.forge.addon.ui.input.HasCompleter;
import org.jboss.forge.addon.ui.input.InputComponent;
import org.jboss.forge.addon.ui.input.ManyValued;
import org.jboss.forge.addon.ui.input.SelectComponent;
import org.jboss.forge.addon.ui.input.SingleValued;
import org.jboss.forge.addon.ui.input.UICompleter;
import org.jboss.forge.furnace.util.Sets;
import org.jboss.forge.furnace.util.Strings;
/**
 * Utilities for {@link InputComponent} objects
 * 
 * @author <a href="mailto:ggastald@redhat.com">George Gastaldi</a>
 * 
 */
@SuppressWarnings({ "rawtypes", "unchecked" })
public final class InputComponents
{
   public static final char DEFAULT_SHORT_NAME = ' ';
   private static final String COLON = ":";
   /**
    * @return the {@link InputType} object associated to this {@link InputComponent}
    */
   public static String getInputType(InputComponent<?, ?> input)
   {
      String result = InputType.DEFAULT;
      for (Facet f : input.getFacets())
      {
         if (HintsFacet.class.isInstance(f))
         {
            result = ((HintsFacet) f).getInputType();
            break;
         }
      }
      // FIXME: The following code does NOT work when called from Eclipse. Could it be a bug in CLAC ?
      // if (input.hasFacet(HintsFacet.class))
      // {
      // HintsFacet facet = input.getFacet(HintsFacet.class);
      // result = facet.getInputType();
      // }
      return result;
   }
   /**
    * Returns the value stored in this {@link InputComponent}. <code>null</code> if the component is null
    */
   public static Object getValueFor(InputComponent<?, ?> component)
   {
      return (component == null) ? null : component.getValue();
   }
   /**
    * Sets the value in the provided {@link InputComponent}, making any necessary conversions
    * 
    * @param component
    * @param value
    */
   public static void setValueFor(final ConverterFactory converterFactory, final InputComponent<?, ?> component,
            final Object value)
   {
      if (component instanceof SingleValued)
      {
         setSingleInputValue(converterFactory, component, value, false);
      }
      else if (component instanceof ManyValued)
      {
         setManyInputValue(converterFactory, component, value, false);
      }
   }
   /**
    * Sets the default value in the provided {@link InputComponent}, making any necessary conversions
    * 
    * @param component
    * @param value
    */
   public static void setDefaultValueFor(final ConverterFactory converterFactory,
            final InputComponent<?, Object> component,
            final Object value)
   {
      if (component instanceof SingleValued)
      {
         setSingleInputValue(converterFactory, component, value, true);
      }
      else if (component instanceof ManyValued)
      {
         setManyInputValue(converterFactory, component, value, true);
      }
   }
   private static void setSingleInputValue(final ConverterFactory converterFactory,
            final InputComponent<?, ?> input, final Object value, boolean defaultValue)
   {
      final Object convertedType;
      if (value != null)
      {
         convertedType = convertToUIInputValue(converterFactory, input, value);
      }
      else
      {
         convertedType = null;
      }
      if (defaultValue)
      {
         ((SingleValued) input).setDefaultValue(convertedType);
      }
      else
      {
         ((SingleValued) input).setValue(convertedType);
      }
   }
   private static void setManyInputValue(final ConverterFactory converterFactory,
            final InputComponent<?, ?> input, Object value, boolean defaultValue)
   {
      final Iterable<Object> convertedValues;
      if (value != null)
      {
         List<Object> convertedValuesList = new ArrayList<>();
         if (value instanceof Iterable && !input.getValueType().isInstance(value))
         {
            for (Object itValue : (Iterable) value)
            {
               Object singleValue = convertToUIInputValue(converterFactory, input, itValue);
               if (singleValue != null)
               {
                  convertedValuesList.add(singleValue);
               }
            }
         }
         else
         {
            Object singleValue = convertToUIInputValue(converterFactory, input, value);
            if (singleValue != null)
            {
               convertedValuesList.add(singleValue);
            }
         }
         convertedValues = convertedValuesList;
      }
      else
      {
         convertedValues = null;
      }
      if (defaultValue)
      {
         ((ManyValued) input).setDefaultValue(convertedValues);
      }
      else
      {
         ((ManyValued) input).setValue(convertedValues);
      }
   }
   /**
    * Returns the converted value that matches the input.
    */
   public static Object convertToUIInputValue(final ConverterFactory converterFactory,
            final InputComponent<?, ?> input, final Object value)
   {
      final Object result;
      Class<Object> sourceType = (Class<Object>) value.getClass();
      Class<Object> targetType = (Class<Object>) input.getValueType();
      if (!targetType.isAssignableFrom(sourceType))
      {
         if (input instanceof SelectComponent)
         {
            SelectComponent<?, Object> selectComponent = (SelectComponent<?, Object>) input;
            Iterable<Object> valueChoices = selectComponent.getValueChoices();
            final Converter<Object, ?> selectConverter;
            if (String.class.isAssignableFrom(sourceType))
            {
               selectConverter = getItemLabelConverter(converterFactory, selectComponent);
            }
            else
            {
               selectConverter = converterFactory.getConverter(targetType, sourceType);
            }
            Object chosenObj = null;
            if (valueChoices != null)
            {
               for (Object valueChoice : valueChoices)
               {
                  Object convertedObj = selectConverter.convert(valueChoice);
                  if (convertedObj.equals(value))
                  {
                     chosenObj = valueChoice;
                     break;
                  }
               }
            }
            result = chosenObj;
         }
         else
         {
            Converter<String, Object> valueConverter = (Converter<String, Object>) input.getValueConverter();
            if (valueConverter != null)
            {
               if (value instanceof String)
               {
                  result = valueConverter.convert((String) value);
               }
               else
               {
                  Converter<Object, String> stringConverter = converterFactory.getConverter(sourceType, String.class);
                  CompositeConverter compositeConverter = new CompositeConverter(stringConverter, valueConverter);
                  result = compositeConverter.convert(value);
               }
            }
            else
            {
               Converter<Object, Object> converter = converterFactory.getConverter(sourceType, targetType);
               result = converter.convert(value);
            }
         }
      }
      else
      {
         Converter<String, Object> valueConverter = (Converter<String, Object>) input.getValueConverter();
         if (valueConverter != null && value instanceof String)
         {
            result = valueConverter.convert((String) value);
         }
         else
         {
            // FORGE-2493: By setting the system property 'org.jboss.forge.ui.select_one_lenient_value' to true will
            // allow UISelectOne to set values outside of its value choices. (pre-2.20.0.Final behavior)
            if (input instanceof SelectComponent && !Boolean.getBoolean("org.jboss.forge.ui.select_one_lenient_value"))
            {
               SelectComponent<?, Object> selectComponent = (SelectComponent<?, Object>) input;
               Set<Object> valueChoices = Sets.toSet(selectComponent.getValueChoices());
               // Check if the value is contained in the valueChoices set
               if (valueChoices != null && valueChoices.contains(value))
               {
                  result = value;
               }
               else
               {
                  // equals()/hashCode may not have been implemented. Trying to compare from the String representation
                  Object chosenObj = null;
                  if (valueChoices != null)
                  {
                     Converter<Object, String> selectConverter = getItemLabelConverter(converterFactory,
                              selectComponent);


Answer:                      String valueLabel = selectConverter.convert(value);