/**
 * 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.tabular;

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

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.asakusafw.dmdl.directio.text.AbstractTextStreamFormatGenerator;
import com.asakusafw.dmdl.directio.text.EscapeSettings;
import com.asakusafw.dmdl.directio.text.TextFormatSettings;
import com.asakusafw.dmdl.directio.util.DirectFileInputDescriptionGenerator;
import com.asakusafw.dmdl.directio.util.DirectFileOutputDescriptionGenerator;
import com.asakusafw.dmdl.directio.util.MapValue;
import com.asakusafw.dmdl.java.emitter.EmitContext;
import com.asakusafw.dmdl.java.spi.JavaDataModelDriver;
import com.asakusafw.dmdl.semantics.ModelDeclaration;
import com.asakusafw.runtime.io.text.tabular.EscapeSequence;
import com.asakusafw.runtime.io.text.tabular.TabularTextFormat;
import com.asakusafw.utils.java.model.syntax.ModelFactory;
import com.asakusafw.utils.java.model.syntax.Name;
import com.asakusafw.utils.java.model.syntax.SimpleName;
import com.asakusafw.utils.java.model.syntax.Statement;
import com.asakusafw.utils.java.model.util.ExpressionBuilder;
import com.asakusafw.utils.java.model.util.TypeBuilder;

/**
 * Emits Direct I/O data format classes about tabular text.
 * @since 0.9.1
 */
public class TabularTextEmitter extends JavaDataModelDriver {

    static final Logger LOG = LoggerFactory.getLogger(TabularTextEmitter.class);

    @Override
    public void generateResources(EmitContext context, ModelDeclaration model) throws IOException {
        if (TabularTextTrait.find(model).isPresent() == false) {
            return;
        }
        Name supportName = generateFormat(context, model);
        generateInputDescription(context, supportName, model);
        generateOutputDescription(context, supportName, model);
    }

    private static Name generateFormat(EmitContext context, ModelDeclaration model) throws IOException {
        assert context != null;
        assert model != null;
        TabularTextTrait trait = TabularTextTrait.get(model);
        EmitContext next = new EmitContext(
                context.getSemantics(),
                context.getConfiguration(),
                model,
                PACKAGE_SEGMENT,
                "{0}TabularTextFormat"); //$NON-NLS-1$
        LOG.debug("Generating tabular text format for {}", //$NON-NLS-1$
                context.getQualifiedTypeName().toNameString());
        FormatGenerator.emit(next, model, trait);
        LOG.debug("Generated tabular text format for {}: {}", //$NON-NLS-1$
                context.getQualifiedTypeName().toNameString(),
                next.getQualifiedTypeName().toNameString());
        return next.getQualifiedTypeName();
    }

    private static void generateInputDescription(
            EmitContext context, Name formatClassName, ModelDeclaration model) throws IOException {
        EmitContext next = new EmitContext(
                context.getSemantics(),
                context.getConfiguration(),
                model,
                PACKAGE_SEGMENT,
                "Abstract{0}TabularTextInputDescription"); //$NON-NLS-1$
        DirectFileInputDescriptionGenerator.Description desc = new DirectFileInputDescriptionGenerator.Description(
                "Tabular text file input", context.getQualifiedTypeName()); //$NON-NLS-1$
        desc.setFormatClassName(formatClassName);
        DirectFileInputDescriptionGenerator.generate(next, desc);
    }

    private static void generateOutputDescription(
            EmitContext context, Name formatClassName, ModelDeclaration model) throws IOException {
        EmitContext next = new EmitContext(
                context.getSemantics(),
                context.getConfiguration(),
                model,
                PACKAGE_SEGMENT,
                "Abstract{0}TabularTextOutputDescription"); //$NON-NLS-1$
        DirectFileOutputDescriptionGenerator.Description desc = new DirectFileOutputDescriptionGenerator.Description(
                "Tabular text file output", context.getQualifiedTypeName()); //$NON-NLS-1$
        desc.setFormatClassName(formatClassName);
        DirectFileOutputDescriptionGenerator.generate(next, desc);
    }

    private static final class FormatGenerator extends AbstractTextStreamFormatGenerator {

        private final TabularTextTrait root;

        private final ModelFactory f;

        private FormatGenerator(EmitContext context, ModelDeclaration model, TabularTextTrait root) {
            super(context, model, root.getFormatSettings(), root.getFieldSettings());
            this.root = root;
            this.f = context.getModelFactory();
        }

        static void emit(EmitContext context, ModelDeclaration model, TabularTextTrait trait) throws IOException {
            new FormatGenerator(context, model, trait).emit(Messages.getString("TabularTextEmitter.javadocTitle")); //$NON-NLS-1$
        }

        @Override
        protected List<Statement> createGetTextFormatInternal() {
            SimpleName builder = f.newSimpleName("builder"); //$NON-NLS-1$
            List<Statement> statements = new ArrayList<>();
            statements.add(new TypeBuilder(f, context.resolve(TabularTextFormat.class))
                    .method("builder") //$NON-NLS-1$
                    .toLocalVariableDeclaration(context.resolve(TabularTextFormat.Builder.class), builder));
            buildTextFormat(statements, builder);
            statements.add(new ExpressionBuilder(f, builder)
                    .method("build") //$NON-NLS-1$
                    .toReturnStatement());
            return statements;
        }

        private void buildTextFormat(List<Statement> statements, SimpleName builder) {
            TextFormatSettings formats = root.getFormatSettings();
            EscapeSettings escapes = root.getEscapeSettings();

            formats.getCharset().ifPresent(v -> statements.add(new ExpressionBuilder(f, builder)
                    .method("withCharset", resolve(v.name())) //$NON-NLS-1$
                    .toStatement()));
            formats.getLineSeparator().ifPresent(v -> statements.add(new ExpressionBuilder(f, builder)
                    .method("withLineSeparator", resolve(v)) //$NON-NLS-1$
                    .toStatement()));
            formats.getFieldSeparator().ifPresent(v -> statements.add(new ExpressionBuilder(f, builder)
                    .method("withFieldSeparator", resolve(v)) //$NON-NLS-1$
                    .toStatement()));
            escapes.getCharacter().ifPresent(c -> {
                ExpressionBuilder escapeBuilder = new TypeBuilder(f, context.resolve(EscapeSequence.class))
                        .method("builder", resolve(c)); //$NON-NLS-1$
                escapes.getEscapeLineSeparator().ifPresent(v -> {
                    if (v) {
                        escapeBuilder.method("addLineSeparator"); //$NON-NLS-1$
                    }
                });
                for (MapValue.Entry<Character, Character> entry : escapes.getSequences()) {
                    Character key = entry.getKey();
                    Character value = entry.getValue();
                    if (value == null) {
                        escapeBuilder.method("addNullMapping", resolve(key)); //$NON-NLS-1$
                    } else {
                        escapeBuilder.method("addMapping", resolve(key), resolve(value)); //$NON-NLS-1$
                    }
                }
                statements.add(new ExpressionBuilder(f, builder)
                        .method("withEscapeSequence", escapeBuilder //$NON-NLS-1$
                                .method("build") //$NON-NLS-1$
                                .toExpression())
                        .toStatement());
            });
            formats.getInputTransformerClass().ifPresent(v -> statements.add(new ExpressionBuilder(f, builder)
                    .method("withInputTransformer", new TypeBuilder(f, resolve(v)) //$NON-NLS-1$
                            .constructorReference()
                            .toExpression())
                    .toStatement()));
            formats.getOutputTransformerClass().ifPresent(v -> statements.add(new ExpressionBuilder(f, builder)
                    .method("withOutputTransformer", new TypeBuilder(f, resolve(v)) //$NON-NLS-1$
                            .constructorReference()
                            .toExpression())
                    .toStatement()));
        }

        @Override
        protected boolean isSplittableInternal() {
            return root.getEscapeSettings().getEscapeLineSeparator().orElse(false) == false;
        }
    }
}
