/*
 * #%L
 * Alfresco Repository
 * %%
 * Copyright (C) 2005 - 2016 Alfresco Software Limited
 * %%
 * This file is part of the Alfresco software. 
 * If the software was purchased under a paid Alfresco license, the terms of 
 * the paid license agreement will prevail.  Otherwise, the software is 
 * provided under the following open source license terms:
 * 
 * Alfresco is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * Alfresco 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 Alfresco. If not, see <http://www.gnu.org/licenses/>.
 * #L%
 */

package org.alfresco.repo.forms.processor.node;

import static org.alfresco.repo.forms.processor.node.FormFieldConstants.PROP_DATA_PREFIX;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.alfresco.repo.dictionary.constraint.ListOfValuesConstraint;
import org.alfresco.repo.dictionary.constraint.RegisteredConstraint;
import org.alfresco.repo.domain.node.NodePropertyValue;
import org.alfresco.repo.forms.Field;
import org.alfresco.repo.forms.FieldGroup;
import org.alfresco.repo.forms.PropertyFieldDefinition;
import org.alfresco.repo.forms.PropertyFieldDefinition.FieldConstraint;
import org.alfresco.repo.forms.processor.FieldProcessor;
import org.alfresco.service.cmr.dictionary.Constraint;
import org.alfresco.service.cmr.dictionary.ConstraintDefinition;
import org.alfresco.service.cmr.dictionary.DataTypeDefinition;
import org.alfresco.service.cmr.dictionary.DictionaryService;
import org.alfresco.service.cmr.dictionary.PropertyDefinition;
import org.alfresco.service.cmr.repository.ContentData;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.Period;
import org.alfresco.service.namespace.NamespaceService;
import org.alfresco.service.namespace.QName;
import org.alfresco.util.ISO8601DateFormat;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.util.StringUtils;

/**
 * {@link FieldProcessor} implementation that handles properties.
 * 
 * @since 3.4
 * @author Nick Smith
 */
public class PropertyFieldProcessor extends QNameFieldProcessor<PropertyDefinition>
{
    private static final Log logger = LogFactory.getLog(PropertyFieldProcessor.class);

    public PropertyFieldProcessor()
    {
        // Constructor for Spring.
    }

    public PropertyFieldProcessor(NamespaceService namespaceService, DictionaryService dictionaryService)
    {
        super(namespaceService, dictionaryService);
    }

    @Override
    protected Log getLogger() 
    {
        return logger;
    }

    @Override
    protected PropertyDefinition getTypeDefinition(QName fullName, ContentModelItemData<?> itemData, boolean isForcedField)
    {
        PropertyDefinition propDef = itemData.getPropertyDefinition(fullName);
        if (propDef == null)
        {
            if (isForcedField)
            {
                propDef = dictionaryService.getProperty(fullName);
            }
        }
        return propDef;
    }

    /**
    * {@inheritDoc}
     */
    @Override
    public Field makeField(PropertyDefinition propDef, Object value, FieldGroup group)
    {
        PropertyFieldDefinition fieldDef = makePropertyFieldDefinition(propDef, group);
        return new ContentModelField(propDef, fieldDef, value);
    }

    @Override
    protected FieldGroup getGroup(PropertyDefinition propDef)
    {
        // TODO Need to Implement this once Composite Content is implementd.
        return null;
    }

    @Override
    public Object getValue(QName name, ContentModelItemData<?> data)
    {
        Serializable value = data.getPropertyValue(name);
        if (value == null)
        {
            return getDefaultValue(name, data);
        }
        
        if (value instanceof Collection<?>)
        {
            // temporarily add repeating field data as a comma
            // separated list, this will be changed to using
            // a separate field for each value once we have full
            // UI support in place.
            Collection<?> values = (Collection<?>) value;
            
            // if the non empty collection is a List of Date objects 
            // we need to convert each date to a ISO8601 format 
            if (value instanceof List<?> && !values.isEmpty())
            {
                List<?> list = (List<?>)values;
                if (list.get(0) instanceof Date)
                {
                    List<String> isoDates = new ArrayList<String>(list.size());
                    for (Object date : list)
                    {
                        isoDates.add(ISO8601DateFormat.format((Date)date));
                    }
                    
                    // return the ISO formatted dates as a comma delimited string 
                    return StringUtils.collectionToCommaDelimitedString(isoDates);
                }
            }
            
            // return everything else using toString()
            return StringUtils.collectionToCommaDelimitedString(values);
        }
        else if (value instanceof ContentData)
        {
            // for content properties retrieve the info URL rather than the
            // the object value itself
            ContentData contentData = (ContentData)value;
            return contentData.getInfoUrl();
        }
        else if (value instanceof NodeRef)
        {
            return ((NodeRef)value).toString();
        }
        
        return value;
    }

    private Object getDefaultValue(QName name, ContentModelItemData<?> data)
    {
        PropertyDefinition propDef = data.getPropertyDefinition(name);
        if (propDef != null)
        {
            QName typeQName = propDef.getDataType().getName();
            String strDefaultValue = propDef.getDefaultValue();
            if (NodePropertyValue.isDataTypeSupported(typeQName))
            {
                // convert to the appropriate type
                NodePropertyValue pv = new NodePropertyValue(typeQName, strDefaultValue);
                return pv.getValue(typeQName);
            }
            return strDefaultValue;
        }
        return null;
    }

    private PropertyFieldDefinition makePropertyFieldDefinition(final PropertyDefinition propDef, FieldGroup group)
    {
        String name = getPrefixedName(propDef);
        QName dataType = propDef.getDataType().getName();
        PropertyFieldDefinition fieldDef = new PropertyFieldDefinition(name, dataType.getLocalName());
        
        populateFieldDefinition(propDef, fieldDef, group, PROP_DATA_PREFIX);

        fieldDef.setDefaultValue(propDef.getDefaultValue());
        fieldDef.setMandatory(propDef.isMandatory());
        fieldDef.setRepeating(propDef.isMultiValued());

        fieldDef.setIndexTokenisationMode(propDef.getIndexTokenisationMode());

        // any property from the system model (sys prefix) should be protected
        // the model doesn't
        // currently enforce this so make sure they are not editable
        if (NamespaceService.SYSTEM_MODEL_1_0_URI.equals(propDef.getName().getNamespaceURI()))
        {
            fieldDef.setProtectedField(true);
        }

        // If the property data type is d:period we need to setup a data
        // type parameters object to represent the options and rules
        if (dataType.equals(DataTypeDefinition.PERIOD))
        {
            PeriodDataTypeParameters periodOptions = getPeriodOptions();
            fieldDef.setDataTypeParameters(periodOptions);
        }

        // setup constraints for the property
        List<FieldConstraint> fieldConstraints = makeFieldConstraints(propDef);
        fieldDef.setConstraints(fieldConstraints);
        return fieldDef;
    }

    private List<FieldConstraint> makeFieldConstraints(PropertyDefinition propDef)
    {
        List<FieldConstraint> fieldConstraints = null;
        List<ConstraintDefinition> constraints = propDef.getConstraints();
        if (constraints != null && constraints.size() > 0)
        {
            fieldConstraints = new ArrayList<FieldConstraint>(constraints.size());
            for (ConstraintDefinition constraintDef : constraints)
            {
                Constraint constraint = constraintDef.getConstraint();
                String type = constraint.getType();
                Map<String, Object> params = constraint.getParameters();
                
                
                //ListOfValuesConstraints have special handling for localising their allowedValues.
                //If the constraint that we are currently handling is a registered constraint then
                //we need to examine the underlying constraint to see if it is a LIST constraint
                if (RegisteredConstraint.class.isAssignableFrom(constraint.getClass()))
                {
                    constraint = ((RegisteredConstraint)constraint).getRegisteredConstraint();
                }
                
                if (ListOfValuesConstraint.class.isAssignableFrom(constraint.getClass()))
                {
                    ListOfValuesConstraint lovConstraint = (ListOfValuesConstraint) constraint;
                    List<String> allowedValues = lovConstraint.getAllowedValues();
                    List<String> localisedValues = new ArrayList<String>(allowedValues.size());
                    
                    // Look up each localised display-label in turn.
                    for (String value : allowedValues)
                    {
                        String displayLabel = lovConstraint.getDisplayLabel(value, dictionaryService);
                        // Change the allowedValue entry to the format the FormsService expects for localised strings: "value|label"
                        // If there is no localisation defined for any value, then this will give us "value|value".
                        localisedValues.add(value + "|" + displayLabel);
                    }
                    
                    // Now replace the allowedValues param with our localised version.
                    params.put(ListOfValuesConstraint.ALLOWED_VALUES_PARAM, localisedValues);
                }
                FieldConstraint fieldConstraint = new FieldConstraint(type, params);
                fieldConstraints.add(fieldConstraint);
            }
        }
        return fieldConstraints;
    }
    
    private PeriodDataTypeParameters getPeriodOptions()
    {
        PeriodDataTypeParameters periodOptions = new PeriodDataTypeParameters();
        Set<String> providers = Period.getProviderNames();
        for (String provider : providers)
        {
            periodOptions.addPeriodProvider(Period.getProvider(provider));
        }
        return periodOptions;
    }
    
    @Override
    protected String getRegistryKey() 
    {
        return FormFieldConstants.PROP;
    }
}