/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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.apache.syncope.core.provisioning.java.data;

import org.apache.syncope.core.provisioning.api.data.ConfigurationDataBinder;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
import org.apache.commons.jexl3.JexlContext;
import org.apache.commons.jexl3.MapContext;
import org.apache.syncope.common.lib.SyncopeClientException;
import org.apache.syncope.common.lib.to.AttrTO;
import org.apache.syncope.common.lib.types.ClientExceptionType;
import org.apache.syncope.core.provisioning.api.jexl.JexlUtils;
import org.apache.syncope.core.persistence.api.attrvalue.validation.InvalidPlainAttrValueException;
import org.apache.syncope.core.persistence.api.dao.ConfDAO;
import org.apache.syncope.core.persistence.api.dao.NotFoundException;
import org.apache.syncope.core.persistence.api.entity.PlainAttrUniqueValue;
import org.apache.syncope.core.persistence.api.entity.PlainAttrValue;
import org.apache.syncope.core.persistence.api.entity.PlainSchema;
import org.apache.syncope.core.persistence.api.entity.conf.CPlainAttr;
import org.apache.syncope.core.persistence.api.entity.conf.CPlainAttrUniqueValue;
import org.apache.syncope.core.persistence.api.entity.conf.CPlainAttrValue;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class ConfigurationDataBinderImpl extends AbstractAnyDataBinder implements ConfigurationDataBinder {

    @Autowired
    private ConfDAO confDAO;

    @Override
    public List<AttrTO> getConfTO() {
        return confDAO.get().getPlainAttrs().stream().map(attr -> getAttrTO(attr)).collect(Collectors.toList());
    }

    @Override
    public AttrTO getAttrTO(final CPlainAttr attr) {
        return new AttrTO.Builder().
                schema(attr.getSchema().getKey()).
                values(attr.getValuesAsStrings()).
                build();
    }

    private void fillAttr(final List<String> values,
            final PlainSchema schema, final CPlainAttr attr, final SyncopeClientException invalidValues) {

        // if schema is multivalue, all values are considered for addition;
        // otherwise only the fist one - if provided - is considered
        List<String> valuesProvided = schema.isMultivalue()
                ? values
                : (values.isEmpty()
                ? Collections.<String>emptyList()
                : Collections.singletonList(values.iterator().next()));

        if (valuesProvided.isEmpty()) {
            JexlContext jexlContext = new MapContext();
            JexlUtils.addPlainAttrsToContext(confDAO.get().getPlainAttrs(), jexlContext);

            if (!schema.isReadonly()
                    && Boolean.parseBoolean(JexlUtils.evaluate(schema.getMandatoryCondition(), jexlContext))) {

                LOG.error("Mandatory schema " + schema.getKey() + " not provided with values");

                SyncopeClientException reqValMissing = SyncopeClientException.build(
                        ClientExceptionType.RequiredValuesMissing);
                reqValMissing.getElements().add(schema.getKey());
                throw reqValMissing;
            }
        }

        valuesProvided.forEach(value -> {
            if (value == null || value.isEmpty()) {
                LOG.debug("Null value for {}, ignoring", schema.getKey());
            } else {
                try {
                    PlainAttrValue attrValue;
                    if (schema.isUniqueConstraint()) {
                        attrValue = entityFactory.newEntity(CPlainAttrUniqueValue.class);
                        ((PlainAttrUniqueValue) attrValue).setSchema(schema);
                    } else {
                        attrValue = entityFactory.newEntity(CPlainAttrValue.class);
                    }

                    attr.add(value, attrValue);
                } catch (InvalidPlainAttrValueException e) {
                    LOG.warn("Invalid value for attribute " + schema.getKey() + ": " + value, e);

                    invalidValues.getElements().add(schema.getKey() + ": " + value + " - " + e.getMessage());
                }
            }
        });
    }

    @Override
    public CPlainAttr getAttr(final AttrTO attrTO) {
        PlainSchema schema = getPlainSchema(attrTO.getSchema());
        if (schema == null) {
            throw new NotFoundException("Conf schema " + attrTO.getSchema());
        } else {
            SyncopeClientException invalidValues = SyncopeClientException.build(ClientExceptionType.InvalidValues);

            CPlainAttr attr = entityFactory.newEntity(CPlainAttr.class);
            attr.setSchema(schema);
            fillAttr(attrTO.getValues(), schema, attr, invalidValues);

            if (!invalidValues.isEmpty()) {
                throw invalidValues;
            }
            return attr;
        }
    }
}
