/*
 * Copyright (c) 2004-2017, by Alex Leigh.
 * All rights reserved.
 *
 * THIS IS UNPUBLISHED PROPRIETARY SOURCE CODE
 * The copyright notice above does not evidence any
 * actual or intended publication of such source code.
 */

package leigh.eo.schema;

import leigh.eo.EO;
import leigh.eo.EODataType;
import leigh.eo.EOLoop;
import leigh.eo.schema.dao.*;
import leigh.mecha.log.MechaLogger;
import leigh.mecha.log.MechaLoggerFactory;
import org.apache.commons.lang.StringUtils;

import java.util.Iterator;
import java.util.Locale;
import java.util.Map;

/**
 * This class provides utility functionality for working with <code>EO</code> instances.
 *
 * @author Alex Leigh
 */
public final class SchemaUtil {
    private static final MechaLogger logger = MechaLoggerFactory.getLogger(SchemaUtil.class);

    /**
     * Set all the default values from the schema into the given EO. If replace is <code>true</code> then
     * existing values in the eo will be replaced with the default value.
     * <p>
     * If the EO contains loops, the method will recurse through the loops, setting default values in all
     * the children.
     *
     * @param eo
     * @param schema
     */
    public static void setDefaults(final EO eo, final EO schema, final boolean replace) {
        final EO elementSchema = leigh.eo.schema.dao.SchemaUtil.getElement(schema, eo.getApiType());

        logger.debug("Validating against schema: {}", schema);

        if (elementSchema == null) {
            throw new IllegalArgumentException("Element not found in schema: " + eo.getApiType());
        }

        Iterator<EO> it = ElementElementDAO.getFields(elementSchema).iterator(FieldElementDAO.API_TYPE);
        while (it.hasNext()) {
            EO field = it.next();
            String defaultValue = FieldElementDAO.getDefaultValue(field);
            if (defaultValue == null) continue;

            // If we are not replacing, and we have the value, iterate
            if (!replace) {
                if (eo.contains(FieldElementDAO.getApiName(field))) continue;
            }

            // Set the default value into the target eo
            eo.setValue(FieldElementDAO.getApiName(field), defaultValue);
        }

        it = ElementElementDAO.getFields(elementSchema).iterator(LoopElementDAO.API_TYPE);
        while (it.hasNext()) {
            EO loopSchema = it.next();
            EOLoop loop = eo.getValueLoop(LoopElementDAO.getApiName(loopSchema));
            if (loop == null) continue;
            for (EO loopObject : loop) {
                setDefaults(loopObject, schema, replace);
            }
        }
    }

    /**
     * Validate the contents of the EO based on the provided schema. Any errors will be written into
     * the errors section of the EO.
     * <p>
     * The method does not recurse into EOLoops.
     *
     * @param eo
     * @param schema
     */
    public static void validate(final EO eo, final EO schema) {
        final EO elementSchema = leigh.eo.schema.dao.SchemaUtil.getRootElement(schema);

        logger.debug("Validating against schema: {}", schema);

        if (elementSchema == null) {
            throw new IllegalArgumentException("Element not found in schema: " + eo.getApiType());
        }

        for (final EO field : ElementElementDAO.getFields(elementSchema)) {
            logger.debug("Validating field: {}", field);

            logger.debug("Validators: {}", FieldElementDAO.getValidators(field));
            for (final EO validator : FieldElementDAO.getValidators(field)) {
                logger.debug("Found validator {} for {}", validator, field);
                switch (validator.getApiType()) {
                    case ValidatorNotEmptyElementDAO.API_TYPE:
                        logger.debug("Checking empty...");
                        if (eo.isEmpty(FieldElementDAO.getApiName(field))) {
                            logger.debug("VAL FAIL");
                            eo.putError(FieldElementDAO.getApiName(field), "Value must not be empty.");
                        }
                }
            }
        }
    }

    /**
     * Ensures that all strings in the object for fields which are marked as being case insensitive are in
     * upper-case format. The routine does not make any attempt to assess or correct fields which are not
     * denoted as being a string type, even if they are marked as being case-insensitive.
     * <p>
     * The method does not recurse into EOLoops.
     */
    public static void fixCase(final EO eo, final EO schema, final Locale locale) {
        logger.debug("fixCase() called against: {}", schema);

        final EO elementSchema = leigh.eo.schema.dao.SchemaUtil.getRootElement(schema);

        if (elementSchema == null) {
            throw new IllegalArgumentException("Element not found in schema: " + eo.getApiType());
        }

        for (final EO field : ElementElementDAO.getFields(elementSchema)) {
            if (!FieldElementDAO.assertType(field)) {
                logger.debug("Field was not a field");
                continue;
            }
            if (!FieldElementDAO.getIsCaseInsensitive(field)) {
                logger.debug("Field was not set as case insensitive");
                continue;
            }
            if (field.getValueEspType(FieldElementDAO.KEY_TYPE) != EODataType.string) {
                logger.debug("Field was not a string: {}", FieldElementDAO.getType(field));
                continue;
            }

            logger.debug("Asserting case for: {}", field);
            final String apiName = FieldElementDAO.getApiName(field);
            logger.debug("apiName: {}", apiName);

            final String value = eo.getValueString(apiName);
            logger.debug("value: {}", value);
            if (value == null) continue;

            if (!StringUtils.isAllUpperCase(value))
                eo.setValue(apiName, value.toUpperCase(locale));
        }
    }

    /**
     * Add the given EO, which is a schema, to the given map. The key for the map will be the name for the schema.
     *
     * @param dst
     * @param src
     */
    public static void addSchemaMap(Map<String, EO> dst, EO src) {
        dst.put(SchemaElementDAO.getApiName(src), src);
    }

}