/**
 * 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.camel.dataformat.univocity;

import java.io.Writer;
import java.util.LinkedHashMap;

import com.univocity.parsers.fixed.FixedWidthFieldLengths;
import com.univocity.parsers.fixed.FixedWidthFormat;
import com.univocity.parsers.fixed.FixedWidthParser;
import com.univocity.parsers.fixed.FixedWidthParserSettings;
import com.univocity.parsers.fixed.FixedWidthWriter;
import com.univocity.parsers.fixed.FixedWidthWriterSettings;

/**
 * This class is the data format that uses the fixed-width uniVocity parser.
 */
public class UniVocityFixedWidthDataFormat extends AbstractUniVocityDataFormat<FixedWidthFormat, FixedWidthWriterSettings,
        FixedWidthWriter, FixedWidthParserSettings, FixedWidthParser, UniVocityFixedWidthDataFormat> {
    protected int[] fieldLengths;
    protected Boolean skipTrailingCharsUntilNewline;
    protected Boolean recordEndsOnNewline;
    protected Character padding;

    /**
     * Gets the field lengths.
     * It's used to construct uniVocity {@link com.univocity.parsers.fixed.FixedWidthFieldLengths} instance.
     *
     * @return the field lengths
     */
    public int[] getFieldLengths() {
        return fieldLengths;
    }

    /**
     * Sets the field lengths
     * It's used to construct uniVocity {@link com.univocity.parsers.fixed.FixedWidthFieldLengths} instance.
     *
     * @param fieldLengths the field length
     * @return current data format instance, fluent API
     */
    public UniVocityFixedWidthDataFormat setFieldLengths(int[] fieldLengths) {
        this.fieldLengths = fieldLengths;
        return this;
    }

    /**
     * Gets whether or not trailing characters until new line must be ignored.
     *
     * @return whether or not trailing characters until new line must be ignored
     * @see com.univocity.parsers.fixed.FixedWidthParserSettings#getSkipTrailingCharsUntilNewline()
     */
    public Boolean getSkipTrailingCharsUntilNewline() {
        return skipTrailingCharsUntilNewline;
    }

    /**
     * Sets whether or not trailing characters until new line must be ignored.
     *
     * @param skipTrailingCharsUntilNewline whether or not trailing characters until new line must be ignored
     * @return current data format instance, fluent API
     * @see com.univocity.parsers.fixed.FixedWidthParserSettings#setSkipTrailingCharsUntilNewline(boolean)
     */
    public UniVocityFixedWidthDataFormat setSkipTrailingCharsUntilNewline(Boolean skipTrailingCharsUntilNewline) {
        this.skipTrailingCharsUntilNewline = skipTrailingCharsUntilNewline;
        return this;
    }

    /**
     * Gets whether or not the record ends on new line.
     *
     * @return whether or not the record ends on new line
     * @see com.univocity.parsers.fixed.FixedWidthParserSettings#getRecordEndsOnNewline()
     */
    public Boolean getRecordEndsOnNewline() {
        return recordEndsOnNewline;
    }

    /**
     * Sets whether or not the record ends on new line
     *
     * @param recordEndsOnNewline whether or not the record ends on new line
     * @return current data format instance, fluent API
     * @see com.univocity.parsers.fixed.FixedWidthParserSettings#setRecordEndsOnNewline(boolean)
     */
    public UniVocityFixedWidthDataFormat setRecordEndsOnNewline(Boolean recordEndsOnNewline) {
        this.recordEndsOnNewline = recordEndsOnNewline;
        return this;
    }

    /**
     * Gets the padding symbol.
     * If {@code null} then the default format value is used.
     *
     * @return the padding symbol
     * @see com.univocity.parsers.fixed.FixedWidthFormat#getPadding()
     */
    public Character getPadding() {
        return padding;
    }

    /**
     * Sets the padding symbol.
     * If {@code null} then the default format value is used.
     *
     * @param padding the padding symbol
     * @return current data format instance, fluent API
     * @see com.univocity.parsers.fixed.FixedWidthFormat#setPadding(char)
     */
    public UniVocityFixedWidthDataFormat setPadding(Character padding) {
        this.padding = padding;
        return this;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected FixedWidthWriterSettings createWriterSettings() {
        return new FixedWidthWriterSettings(createFixedWidthFieldLengths());
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected FixedWidthWriter createWriter(Writer writer, FixedWidthWriterSettings settings) {
        return new FixedWidthWriter(writer, settings);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected FixedWidthParserSettings createParserSettings() {
        return new FixedWidthParserSettings(createFixedWidthFieldLengths());
    }

    @Override
    protected void configureParserSettings(FixedWidthParserSettings settings) {
        super.configureParserSettings(settings);

        if (skipTrailingCharsUntilNewline != null) {
            settings.setSkipTrailingCharsUntilNewline(skipTrailingCharsUntilNewline);
        }
        if (recordEndsOnNewline != null) {
            settings.setRecordEndsOnNewline(true);
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected FixedWidthParser createParser(FixedWidthParserSettings settings) {
        return new FixedWidthParser(settings);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected void configureFormat(FixedWidthFormat format) {
        super.configureFormat(format);

        if (padding != null) {
            format.setPadding(padding);
        }
    }

    /**
     * Creates the {@link com.univocity.parsers.fixed.FixedWidthFieldLengths} instance based on the headers and field
     * lengths.
     *
     * @return new {@code FixedWidthFieldLengths} based on the header and field lengthsl
     */
    private FixedWidthFieldLengths createFixedWidthFieldLengths() {
        // Ensure that the field lengths have been defined.
        if (fieldLengths == null) {
            throw new IllegalArgumentException("The fieldLengths must have been defined in order to use the fixed-width format.");
        }

        // If there's no header then we only use their length
        if (headers == null) {
            return new FixedWidthFieldLengths(fieldLengths);
        }

        // Use both headers and field lengths (same size and no duplicate headers)
        if (fieldLengths.length != headers.length) {
            throw new IllegalArgumentException("The headers and fieldLengths must have the same number of element in order to use the fixed-width format.");
        }
        LinkedHashMap<String, Integer> fields = new LinkedHashMap<String, Integer>();
        for (int i = 0; i < headers.length; i++) {
            fields.put(headers[i], fieldLengths[i]);
        }
        if (fields.size() != headers.length) {
            throw new IllegalArgumentException("The headers cannot have duplicates in order to use the fixed-width format.");
        }
        return new FixedWidthFieldLengths(fields);
    }

    @Override
    public String getDataFormatName() {
        return "univocity-fixed";
    }
}
