/**
 * Copyright 2011-2021 Asakusa Framework Team.
 *
 * 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 com.asakusafw.dmdl.directio.text;

import static com.asakusafw.dmdl.directio.text.TextFormatConstants.*;

import java.time.ZoneId;
import java.util.Map;

import com.asakusafw.dmdl.directio.util.AttributeAnalyzer;
import com.asakusafw.dmdl.directio.util.ClassName;
import com.asakusafw.dmdl.directio.util.DatePattern;
import com.asakusafw.dmdl.directio.util.DecimalPattern;
import com.asakusafw.dmdl.directio.util.Value;
import com.asakusafw.dmdl.model.AstAttribute;
import com.asakusafw.dmdl.model.AstAttributeElement;
import com.asakusafw.dmdl.semantics.DmdlSemantics;
import com.asakusafw.runtime.io.text.csv.QuoteStyle;
import com.asakusafw.runtime.io.text.driver.ErrorAction;
import com.asakusafw.runtime.io.text.value.DecimalOptionFieldAdapter;

/**
 * Settings of text fields.
 * @since 0.9.1
 */
public class TextFieldSettings {

    private Value<ClassName> adapterClass = Value.undefined();

    private Value<String> nullFormat = Value.undefined();

    private Value<String> trueFormat = Value.undefined();

    private Value<String> falseFormat = Value.undefined();

    private Value<DecimalPattern> numberFormat = Value.undefined();

    private Value<DatePattern> dateFormat = Value.undefined();

    private Value<DatePattern> dateTimeFormat = Value.undefined();

    private Value<ZoneId> timeZone = Value.undefined();

    private Value<DecimalOptionFieldAdapter.OutputStyle> decimalOutputStyle = Value.undefined();

    private Value<Boolean> trimInputWhitespaces = Value.undefined();

    private Value<Boolean> skipEmptyInput = Value.undefined();

    private Value<ErrorAction> malformedInputAction = Value.undefined();

    private Value<ErrorAction> unmappableOutputAction = Value.undefined();

    private Value<QuoteStyle> quoteStyle = Value.undefined();

    /**
     * Returns the field adapter class.
     * @return the adapter
     */
    public Value<ClassName> getAdapterClass() {
        return adapterClass;
    }

    /**
     * Returns the string representation of {@code null}.
     * @return the string representation of {@code null}
     */
    public Value<String> getNullFormat() {
        return nullFormat;
    }

    /**
     * Returns the string representation of {@code true}.
     * @return the string representation of {@code true}
     */
    public Value<String> getTrueFormat() {
        return trueFormat;
    }

    /**
     * Returns the string representation of {@code false}.
     * @return the string representation of {@code false}
     */
    public Value<String> getFalseFormat() {
        return falseFormat;
    }

    /**
     * Returns the string representation of numbers.
     * @return the string representation of numbers
     */
    public Value<DecimalPattern> getNumberFormat() {
        return numberFormat;
    }

    /**
     * Returns the string representation of {@code DATE}.
     * @return the string representation of {@code DATE}
     */
    public Value<DatePattern> getDateFormat() {
        return dateFormat;
    }

    /**
     * Returns the string representation of {@code DATETIME}.
     * @return the string representation of {@code DATETIME}
     */
    public Value<DatePattern> getDateTimeFormat() {
        return dateTimeFormat;
    }

    /**
     * Returns the time zone.
     * @return the time zone
     */
    public Value<ZoneId> getTimeZone() {
        return timeZone;
    }

    /**
     * Returns the decimal output style.
     * @return the decimal output style
     */
    public Value<DecimalOptionFieldAdapter.OutputStyle> getDecimalOutputStyle() {
        return decimalOutputStyle;
    }

    /**
     * Returns whether or not trims input field leading/trailing white-space characters.
     * @return {@code true} if trims input field leading/trailing white-space characters, otherwise {@code false}
     */
    public Value<Boolean> getTrimInputWhitespaces() {
        return trimInputWhitespaces;
    }

    /**
     * Returns whether or not skips empty input field.
     * @return {@code true} if skips empty input field, otherwise {@code false}
     */
    public Value<Boolean> getSkipEmptyInput() {
        return skipEmptyInput;
    }

    /**
     * Returns the error action of malformed inputs.
     * @return the error action of malformed inputs
     */
    public Value<ErrorAction> getMalformedInputAction() {
        return malformedInputAction;
    }

    /**
     * Returns the error action of unmappable outputs.
     * @return the error action of unmappable outputs
     */
    public Value<ErrorAction> getUnmappableOutputAction() {
        return unmappableOutputAction;
    }

    /**
     * Returns the quote style.
     * @return the quote style
     */
    public Value<QuoteStyle> getQuoteStyle() {
        return quoteStyle;
    }

    /**
     * Consumes attribute elements about escape settings, and returns corresponding {@link EscapeSettings}.
     * @param environment the current environment
     * @param attribute the attribute
     * @param elements the element map to be consumed
     * @return corresponded {@link EscapeSettings}.
     */
    public static TextFieldSettings consume(
            DmdlSemantics environment, AstAttribute attribute,
            Map<String, AstAttributeElement> elements) {
        AttributeAnalyzer analyzer = new AttributeAnalyzer(environment, attribute);
        TextFieldSettings settings = new TextFieldSettings();
        consumeAdapterClass(settings, analyzer, elements.remove(ELEMENT_FIELD_ADAPTER));
        consumeNullFormat(settings, analyzer, elements.remove(ELEMENT_NULL_FORMAT));
        consumeTrueFormat(settings, analyzer, elements.remove(ELEMENT_TRUE_FORMAT));
        consumeFalseFormat(settings, analyzer, elements.remove(ELEMENT_FALSE_FORMAT));
        consumeNumberFormat(settings, analyzer, elements.remove(ELEMENT_NUMBER_FORMAT));
        consumeDateFormat(settings, analyzer, elements.remove(ELEMENT_DATE_FORMAT));
        consumeDateTimeFormat(settings, analyzer, elements.remove(ELEMENT_DATETIME_FORMAT));
        consumeTimeZone(settings, analyzer, elements.remove(ELEMENT_TIME_ZONE));
        consumeDecimalOutputStyle(settings, analyzer, elements.remove(ELEMENT_DECIMAL_OUTPUT_STYLE));
        consumeTrimInputWhitespaces(settings, analyzer, elements.remove(ELEMENT_TRIM_INPUT_WHITESPACES));
        consumeSkipEmptyInput(settings, analyzer, elements.remove(ELEMENT_SKIP_EMPTY_INPUT));
        consumeMalformedInputAction(settings, analyzer, elements.remove(ELEMENT_MALFORMED_INPUT_ACTION));
        consumeUnmappableOutputAction(settings, analyzer, elements.remove(ELEMENT_UNMAPPABLE_OUTPUT_ACTION));
        consumeQuoteStyle(settings, analyzer, elements.remove(ELEMENT_QUOTE_STYLE));
        return settings;
    }

    private static void consumeAdapterClass(
            TextFieldSettings settings, AttributeAnalyzer analyzer, AstAttributeElement element) {
        if (element != null) {
            settings.adapterClass = analyzer.toClassName(element);
        }
    }

    private static void consumeNullFormat(
            TextFieldSettings settings, AttributeAnalyzer analyzer, AstAttributeElement element) {
        if (element != null) {
            settings.nullFormat = analyzer.toStringWithNull(element);
        }
    }

    private static void consumeTrueFormat(
            TextFieldSettings settings, AttributeAnalyzer analyzer, AstAttributeElement element) {
        if (element != null) {
            settings.trueFormat = analyzer.toString(element);
        }
    }

    private static void consumeFalseFormat(
            TextFieldSettings settings, AttributeAnalyzer analyzer, AstAttributeElement element) {
        if (element != null) {
            settings.falseFormat = analyzer.toString(element);
        }
    }

    private static void consumeNumberFormat(
            TextFieldSettings settings, AttributeAnalyzer analyzer, AstAttributeElement element) {
        if (element != null) {
            settings.numberFormat = analyzer.toDecimalPatternWithNull(element);
        }
    }

    private static void consumeDateFormat(
            TextFieldSettings settings, AttributeAnalyzer analyzer, AstAttributeElement element) {
        if (element != null) {
            settings.dateFormat = analyzer.toDatePattern(element);
        }
    }

    private static void consumeDateTimeFormat(
            TextFieldSettings settings, AttributeAnalyzer analyzer, AstAttributeElement element) {
        if (element != null) {
            settings.dateTimeFormat = analyzer.toDatePattern(element);
        }
    }

    private static void consumeTimeZone(
            TextFieldSettings settings, AttributeAnalyzer analyzer, AstAttributeElement element) {
        if (element != null) {
            settings.timeZone = analyzer.toZoneIdWithNull(element);
        }
    }

    private static void consumeDecimalOutputStyle(
            TextFieldSettings settings, AttributeAnalyzer analyzer, AstAttributeElement element) {
        if (element != null) {
            settings.decimalOutputStyle = analyzer.toEnumConstant(element, DecimalOptionFieldAdapter.OutputStyle.class);
        }
    }

    private static void consumeTrimInputWhitespaces(
            TextFieldSettings settings, AttributeAnalyzer analyzer, AstAttributeElement element) {
        if (element != null) {
            settings.trimInputWhitespaces = analyzer.toBoolean(element);
        }
    }

    private static void consumeSkipEmptyInput(
            TextFieldSettings settings, AttributeAnalyzer analyzer, AstAttributeElement element) {
        if (element != null) {
            settings.skipEmptyInput = analyzer.toBoolean(element);
        }
    }

    private static void consumeMalformedInputAction(
            TextFieldSettings settings, AttributeAnalyzer analyzer, AstAttributeElement element) {
        if (element != null) {
            settings.malformedInputAction = analyzer.toEnumConstant(element, ErrorAction.class);
        }
    }

    private static void consumeUnmappableOutputAction(
            TextFieldSettings settings, AttributeAnalyzer analyzer, AstAttributeElement element) {
        if (element != null) {
            settings.unmappableOutputAction = analyzer.toEnumConstant(element, ErrorAction.class);
        }
    }

    private static void consumeQuoteStyle(
            TextFieldSettings settings, AttributeAnalyzer analyzer, AstAttributeElement element) {
        if (element != null) {
            settings.quoteStyle = analyzer.toEnumConstant(element, QuoteStyle.class);
        }
    }

    /**
     * Verifies this settings.
     * @param environment the current environment
     * @param attribute the original attribute
     * @return {@code true} if the settings seems valid, otherwise {@code false}
     */
    public boolean verify(DmdlSemantics environment, AstAttribute attribute) {
        AttributeAnalyzer analyzer = new AttributeAnalyzer(environment, attribute);
        checkNotConflict(analyzer, trueFormat, nullFormat);
        checkNotConflict(analyzer, falseFormat, nullFormat);
        checkNotConflict(analyzer, falseFormat, trueFormat);
        return analyzer.hasError() == false;
    }

    private static void checkNotConflict(AttributeAnalyzer analyzer, Value<?> a, Value<?> b) {
        if (a.isPresent() && b.isPresent() && a.equals(b)) {
            analyzer.error(a.getDeclaration(),
                    Messages.getString("TextFieldSettings.diagnosticConflictFormat"), //$NON-NLS-1$
                    b.getDeclaration().name);
        }
    }
}
