/****************************************************************
 * 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.james.mime4j.stream;

import org.apache.james.mime4j.MimeException;

/**
 * Properties used to configure the behavior of MIME stream parsers.
 */
public final class MimeConfig {

    public static final MimeConfig PERMISSIVE = MimeConfig.custom()
        .setMaxContentLen(100 * 1024 * 1024)
        .setMaxHeaderCount(-1)
        .setMaxHeaderLen(-1)
        .setMaxLineLen(-1)
        .build();
    public static final MimeConfig DEFAULT = new Builder().build();
    public static final MimeConfig STRICT = new Builder()
        .setStrictParsing(true)
        .setMalformedHeaderStartsBody(false)
        .build();

    private final boolean strictParsing;
    private final int maxLineLen;
    private final int maxHeaderCount;
    private final int maxHeaderLen;
    private final long maxContentLen;
    private final boolean countLineNumbers;
    private final String headlessParsing;
    private final boolean malformedHeaderStartsBody;

    MimeConfig(
            boolean strictParsing,
            int maxLineLen,
            int maxHeaderCount,
            int maxHeaderLen,
            long maxContentLen,
            boolean countLineNumbers,
            String headlessParsing,
            boolean malformedHeaderStartsBody) {
        this.strictParsing = strictParsing;
        this.countLineNumbers = countLineNumbers;
        this.malformedHeaderStartsBody = malformedHeaderStartsBody;
        this.maxLineLen = maxLineLen;
        this.maxHeaderCount = maxHeaderCount;
        this.maxHeaderLen = maxHeaderLen;
        this.maxContentLen = maxContentLen;
        this.headlessParsing = headlessParsing;
    }

    /**
     * @see Builder#setMalformedHeaderStartsBody(boolean)
     *
     * @return true if malformed header should "end" the headers and be part of
     *         the body
     */
    public boolean isMalformedHeaderStartsBody() {
        return malformedHeaderStartsBody;
    }

    /**
     * Returns the value of the strict parsing mode
     *
     * @see Builder#setStrictParsing(boolean)
     *
     * @return value of the strict parsing mode
     */
    public boolean isStrictParsing() {
        return this.strictParsing;
    }

    /**
     * Returns the maximum line length limit
     *
     * @see Builder#setMaxLineLen(int)
     *
     * @return value of the the maximum line length limit
     */
    public int getMaxLineLen() {
        return this.maxLineLen;
    }

    /**
     * Returns the maximum header limit
     *
     * @see Builder#setMaxHeaderCount(int)
     *
     * @return value of the the maximum header limit
     */
    public int getMaxHeaderCount() {
        return this.maxHeaderCount;
    }

    /**
     * Returns the maximum header length limit
     *
     * @see Builder#setMaxHeaderLen(int)
     *
     * @return value of the maximum header length limit
     */
    public int getMaxHeaderLen() {
        return maxHeaderLen;
    }

    /**
     * Returns the maximum content length limit
     *
     * @see Builder#setMaxContentLen(long)
     *
     * @return value of the the maximum content length limit
     */
    public long getMaxContentLen() {
        return maxContentLen;
    }

    /**
     * Returns the value of the line number counting mode.
     *
     * @return value of the line number counting mode.
     */
    public boolean isCountLineNumbers() {
        return countLineNumbers;
    }

    /**
     * Returns the value of the default content type. When not null, indicates
     * that the parsing should be headless.
     *
     * @return default content type when parsing headless, null otherwise
     * @see org.apache.james.mime4j.parser.MimeStreamParser#parse(java.io.InputStream)
     */
    public String getHeadlessParsing() {
        return headlessParsing;
    }

    @Override
    public String toString() {
        StringBuilder b = new StringBuilder();
        b.append("[strictParsing=").append(strictParsing)
                .append(", maxLineLen=").append(maxLineLen)
                .append(", maxHeaderCount=").append(maxHeaderCount)
                .append(", maxHeaderLen=").append(maxHeaderLen)
                .append(", maxContentLen=").append(maxContentLen)
                .append(", countLineNumbers=").append(countLineNumbers)
                .append(", headlessParsing=").append(headlessParsing)
                .append(", malformedHeaderStartsBody=").append(malformedHeaderStartsBody)
                .append("]");
        return b.toString();
    }

    public static MimeConfig.Builder custom() {
        return new Builder();
    }

    public static MimeConfig.Builder copy(final MimeConfig config) {
        if (config == null) {
            throw new IllegalArgumentException("Config may not be null");
        }
        return new Builder()
            .setStrictParsing(config.isStrictParsing())
            .setMaxLineLen(config.getMaxLineLen())
            .setMaxHeaderCount(config.getMaxHeaderCount())
            .setMaxHeaderLen(config.getMaxHeaderLen())
            .setMaxContentLen(config.getMaxContentLen())
            .setCountLineNumbers(config.isCountLineNumbers())
            .setHeadlessParsing(config.getHeadlessParsing())
            .setMalformedHeaderStartsBody(config.isMalformedHeaderStartsBody());
    }

    public static class Builder {

        private boolean strictParsing;
        private int maxLineLen;
        private int maxHeaderCount;
        private int maxHeaderLen;
        private long maxContentLen;
        private boolean countLineNumbers;
        private String headlessParsing;
        private boolean malformedHeaderStartsBody;

        public Builder() {
            this.strictParsing = false;
            this.countLineNumbers = false;
            this.malformedHeaderStartsBody = false;
            this.maxLineLen = 1000;
            this.maxHeaderCount = 1000;
            this.maxHeaderLen = 10000;
            this.maxContentLen = -1;
            this.headlessParsing = null;
        }

        /**
         * Define the behaviour for dealing with malformed headers while in lenient
         * mode
         *
         * @param malformedHeaderStartsBody
         *            <code>true</code> to make the parser interpret a malformed
         *            header as end of the headers and as part of the body (as if
         *            the CRLF separator was missing). <code>false</code> to simply
         *            ignore malformed headers and continue parsing headers from the
         *            following line.
         */
        public Builder setMalformedHeaderStartsBody(boolean malformedHeaderStartsBody) {
            this.malformedHeaderStartsBody = malformedHeaderStartsBody;
            return this;
        }

        /**
         * Defines whether minor violations of the MIME specification should be
         * tolerated or should result in a {@link MimeException}. If this parameter
         * is set to <code>true</code>, a strict interpretation of the MIME
         * specification will be enforced, If this parameter is set to
         * <code>false</code> minor violations will result in a warning in the log.
         * <p>
         * Default value: <code>false</code>
         *
         * @param strictParsing
         *            value of the strict parsing mode
         */
        public Builder setStrictParsing(boolean strictParsing) {
            this.strictParsing = strictParsing;
            return this;
        }

        /**
         * Sets the maximum line length limit. Parsing of a MIME entity will be
         * terminated with a {@link MimeException} if a line is encountered that
         * exceeds the maximum length limit. If this parameter is set to a non
         * positive value the line length check will be disabled.
         * <p>
         * Default value: <code>1000</code>
         *
         * @param maxLineLen
         *            maximum line length limit
         */
        public Builder setMaxLineLen(int maxLineLen) {
            this.maxLineLen = maxLineLen;
            return this;
        }

        /**
         * Sets the maximum header limit. Parsing of a MIME entity will be
         * terminated with a {@link MimeException} if the number of headers exceeds
         * the maximum limit. If this parameter is set to a non positive value the
         * header limit check will be disabled.
         * <p>
         * Default value: <code>1000</code>
         *
         * @param maxHeaderCount
         *            maximum header limit
         */
        public Builder setMaxHeaderCount(int maxHeaderCount) {
            this.maxHeaderCount = maxHeaderCount;
            return this;
        }

        /**
         * Sets the maximum header length limit. Parsing of a MIME entity will be
         * terminated with a {@link MimeException} if the total length of a header
         * exceeds this limit. If this parameter is set to a non positive value the
         * header length check will be disabled.
         * <p>
         * A message header may be folded across multiple lines. This configuration
         * parameter is used to limit the total length of a header, i.e. the sum of
         * the length of all lines the header spans across (including line
         * terminators).
         * <p>
         * Default value: <code>10000</code>
         *
         * @param maxHeaderLen
         *            maximum header length limit
         */
        public Builder setMaxHeaderLen(int maxHeaderLen) {
            this.maxHeaderLen = maxHeaderLen;
            return this;
        }

        /**
         * Sets the maximum content length limit. Parsing of a MIME entity will be
         * terminated with a {@link MimeException} if a content body exceeds the
         * maximum length limit. If this parameter is set to a non positive value
         * the content length check will be disabled.
         * <p>
         * Default value: <code>-1</code>
         *
         * @param maxContentLen
         *            maximum content length limit
         */
        public Builder setMaxContentLen(long maxContentLen) {
            this.maxContentLen = maxContentLen;
            return this;
        }

        /**
         * Defines whether the parser should count line numbers. If enabled line
         * numbers are included in the debug output.
         * <p>
         * Default value: <code>false</code>
         *
         * @param countLineNumbers
         *            value of the line number counting mode.
         */
        public Builder setCountLineNumbers(boolean countLineNumbers) {
            this.countLineNumbers = countLineNumbers;
            return this;
        }

        /**
         * Defines a default content type. When not null, indicates that the parsing
         * should be headless.
         * <p>
         * Default value: <code>null</code>
         *
         * @param contentType
         *            value of the default content type when parsing headless, null
         *            otherwise
         * @see org.apache.james.mime4j.parser.MimeStreamParser#parse(java.io.InputStream)
         */
        public Builder setHeadlessParsing(String contentType) {
            this.headlessParsing = contentType;
            return this;
        }

        public MimeConfig build() {
            return new MimeConfig(
                    strictParsing,
                    maxLineLen,
                    maxHeaderCount,
                    maxHeaderLen,
                    maxContentLen,
                    countLineNumbers,
                    headlessParsing,
                    malformedHeaderStartsBody);
        }

    }

}
