/**
 * 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.nio.charset.Charset;
import java.util.Map;
import java.util.Optional;

import com.asakusafw.dmdl.directio.util.AttributeAnalyzer;
import com.asakusafw.dmdl.directio.util.ClassName;
import com.asakusafw.dmdl.directio.util.CodecNames;
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.LineSeparator;
import com.asakusafw.runtime.io.text.driver.ErrorAction;
import com.asakusafw.runtime.io.text.driver.HeaderType;

/**
 * Settings of formatted text file.
 * @since 0.9.1
 */
public class TextFormatSettings {

    private Value<Charset> charset = Value.undefined();

    private Value<HeaderType> headerType = Value.undefined();

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

    private Value<LineSeparator> lineSeparator = Value.undefined();

    private Value<Character> fieldSeparator = Value.undefined();

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

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

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

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

    /**
     * Returns the charset name.
     * @return the charset name
     */
    public Value<Charset> getCharset() {
        return charset;
    }

    /**
     * Returns the header type.
     * @return the header type
     */
    public Value<HeaderType> getHeaderType() {
        return headerType;
    }

    /**
     * Returns the compression type.
     * @return the compression type
     */
    public Value<ClassName> getCompressionType() {
        return compressionType;
    }

    /**
     * Returns the line separator.
     * @return the line separator
     */
    public Value<LineSeparator> getLineSeparator() {
        return lineSeparator;
    }

    /**
     * Returns the field separator.
     * @return the field separator
     */
    public Value<Character> getFieldSeparator() {
        return fieldSeparator;
    }

    /**
     * Returns the error action type for less input fields.
     * @return the error action type for less input fields
     */
    public Value<ErrorAction> getLessInputAction() {
        return lessInputAction;
    }

    /**
     * Returns the error action type for extra input fields.
     * @return the error action type for extra input fields
     */
    public Value<ErrorAction> getMoreInputAction() {
        return moreInputAction;
    }

    /**
     * Returns the input transformer class.
     * @return the input transformer class
     */
    public Value<ClassName> getInputTransformerClass() {
        return inputTransformerClass;
    }

    /**
     * Returns the output transformer class.
     * @return the output transformer class
     */
    public Value<ClassName> getOutputTransformerClass() {
        return outputTransformerClass;
    }

    /**
     * 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 TextFormatSettings consume(
            DmdlSemantics environment, AstAttribute attribute,
            Map<String, AstAttributeElement> elements) {
        AttributeAnalyzer analyzer = new AttributeAnalyzer(environment, attribute);
        TextFormatSettings settings = new TextFormatSettings();
        consumeCharset(settings, analyzer, elements.remove(ELEMENT_CHARSET_NAME));
        consumeHeaderType(settings, analyzer, elements.remove(ELEMENT_HEADER_TYPE));
        consumeCompressionType(settings, analyzer, elements.remove(ELEMENT_COMPRESSION_TYPE));
        consumeLineSeparator(settings, analyzer, elements.remove(ELEMENT_LINE_SEPARATOR));
        consumeFieldSeparator(settings, analyzer, elements.remove(ELEMENT_FIELD_SEPARATOR));
        consumeLessInputAction(settings, analyzer, elements.remove(ELEMENT_LESS_INPUT_ACTION));
        consumeMoreInputAction(settings, analyzer, elements.remove(ELEMENT_MORE_INPUT_ACTION));
        consumeInputTransformerClass(settings, analyzer, elements.remove(ELEMENT_INPUT_TRANSFORMER));
        consumeOutputTransformerClass(settings, analyzer, elements.remove(ELEMENT_OUTPUT_TRANSFORMER));
        return settings;
    }

    private static void consumeCharset(
            TextFormatSettings settings, AttributeAnalyzer analyzer, AstAttributeElement element) {
        if (element != null) {
            settings.charset = analyzer.toCharset(element);
        }
    }

    private static void consumeHeaderType(
            TextFormatSettings settings, AttributeAnalyzer analyzer, AstAttributeElement element) {
        if (element != null) {
            settings.headerType = analyzer.toEnumConstant(element, HeaderType.class);
        }
    }

    private static void consumeCompressionType(
            TextFormatSettings settings, AttributeAnalyzer analyzer, AstAttributeElement element) {
        if (element != null) {
            settings.compressionType = analyzer.toClassName(
                    element,
                    s -> Optional.of(CodecNames.resolveCodecName(s)));
        }
    }

    private static void consumeLineSeparator(
            TextFormatSettings settings, AttributeAnalyzer analyzer, AstAttributeElement element) {
        if (element != null) {
            settings.lineSeparator = analyzer.toEnumConstant(element, LineSeparator.class);
        }
    }

    private static void consumeFieldSeparator(
            TextFormatSettings settings, AttributeAnalyzer analyzer, AstAttributeElement element) {
        if (element != null) {
            settings.fieldSeparator = analyzer.toCharacter(element);
        }
    }

    private static void consumeLessInputAction(
            TextFormatSettings settings, AttributeAnalyzer analyzer, AstAttributeElement element) {
        if (element != null) {
            settings.lessInputAction = analyzer.toEnumConstant(element, ErrorAction.class);
        }
    }

    private static void consumeMoreInputAction(
            TextFormatSettings settings, AttributeAnalyzer analyzer, AstAttributeElement element) {
        if (element != null) {
            settings.moreInputAction = analyzer.toEnumConstant(element, ErrorAction.class);
        }
    }

    private static void consumeInputTransformerClass(
            TextFormatSettings settings, AttributeAnalyzer analyzer, AstAttributeElement element) {
        if (element != null) {
            settings.inputTransformerClass = analyzer.toClassName(element);
        }
    }

    private static void consumeOutputTransformerClass(
            TextFormatSettings settings, AttributeAnalyzer analyzer, AstAttributeElement element) {
        if (element != null) {
            settings.outputTransformerClass = analyzer.toClassName(element);
        }
    }

    /**
     * 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);
        if (fieldSeparator.isPresent()) {
            char c = fieldSeparator.getEntity();
            if (c == '\r' || c == '\n') {
                analyzer.error(fieldSeparator.getDeclaration(), Messages.getString("TextFormatSettings.diagnosticConflictLineSeparator")); //$NON-NLS-1$
            }
        }
        return analyzer.hasError() == false;
    }
}
