/*
 * @(#)MimeHeaderAttr.java
 *
 * Copyright (c) 2001-2002 JScape
 * 1147 S. 53rd Pl., Mesa, Arizona, 85206, U.S.A.
 * All rights reserved.
 *
 * This software is the confidential and proprietary information of
 * JScape. ("Confidential Information").  You shall not disclose such
 * Confidential Information and shall use it only in accordance with
 * the terms of the license agreement you entered into with JScape.
 */
package com.jscape.inet.mime;

import com.jscape.util.ByteBuffer;

import java.io.Serializable;
import java.io.UnsupportedEncodingException;
import java.nio.charset.StandardCharsets;

import static com.jscape.inet.mime.MimeConstants.EQUALS;

/**
 * Implements methods for managing the attributes of a <code>MimeHeader</code>.
 */
public class MimeHeaderAttr
        implements Serializable {
    /**
     * Special characters to escape in MIME header attribute.
     */
    private static final char[] TSPECIALS = {
            '(', ')', '<', '>', '@', ',', ';', ':', '\\', '"', '/', '[', ']',
            '?', '=', ' ', '\t', '\r', '\n', '\f'
    };

    /**
     * Represents name of MIME header attribute.
     */
    protected String name;

    /**
     * Represents value of MIME header attribute.
     */
    protected String value;

    /**
     * Constructs a new <code>MimeHeaderAttr</code> instance.
     */
    public MimeHeaderAttr() {
        this.name = "";
        this.value = "";
    }

    /**
     * Constructs a new <code>MimeHeaderAttr</code> instance.
     *
     * @param attr the unparsed MIME header attribute data
     *
     * @throws MimeException if a MIME related parsing error occurs
     */
    public MimeHeaderAttr(String attr)
            throws MimeException {
        this();
        parse(attr.getBytes(StandardCharsets.US_ASCII));
    }

    /**
     * Constructs a new <code>MimeHeaderAttr</code> instance.
     *
     * @param attr the unparsed MIME header attribute data
     *
     * @throws MimeException if a MIME related parsing error occurs
     */
    public MimeHeaderAttr(byte[] attr)
            throws MimeException {
        this();
        parse(attr);
    }

    /**
     * Constructs a new <code>MimeHeaderAttr</code> instance.
     *
     * @param name  the name of attribute
     * @param value the value of attribute
     *
     * @throws MimeException if a MIME related parsing error occurs
     */
    public MimeHeaderAttr(String name, String value)
            throws MimeException {
        this(name, value, "us-ascii");
    }

    /**
     * Constructs a new <code>MimeHeaderAttr</code> instance.
     *
     * @param name              the name of attribute
     * @param value             the value of attribute
     * @param characterEncoding the character encoding to use for the attribute
     *                          value
     *
     * @throws MimeException if a MIME related parsing error occurs
     */
    public MimeHeaderAttr(String name, String value, String characterEncoding)
            throws MimeException {
        this.name = name;
        this.value = value;

        try {
            this.value = characterSetEncode(value, characterEncoding);

            // enclose in quotes if contains special characters
            if (!value.startsWith("\"")) {
                if (containsSpecials()) {
                    this.value = "\"" + this.value + "\"";
                }
            }
        } catch (UnsupportedEncodingException ue) {
            throw new MimeException("Unable to encode value using " + characterEncoding);
        }
    }

    /**
     * Encodes a value according to RFC 2047.
     *
     * @param value             the value to encode
     * @param characterEncoding the character set to encode data in
     *
     * @return the encoded value
     *
     * @throws UnsupportedEncodingException if the character encoding is
     *                                      invalid
     */
    private String characterSetEncode(String value, String characterEncoding)
            throws UnsupportedEncodingException {
        return RFC2047Encoder.encode(value, characterEncoding, 0);
    }

    /**
     * Parses the MIME header attribute.
     *
     * @param data the MIME header attribute to parse
     *
     * @throws MimeException if a MIME related parsing error occurs
     */
    protected void parse(byte[] data)
            throws MimeException {
        ByteBuffer buf = new ByteBuffer(data);
        parse(buf);
    }

    /**
     * Parses the MIME header attribute.
     *
     * @param buffer the MIME header attribute to parse
     *
     * @throws MimeException if a MIME related parsing error occurs
     */
    protected void parse(ByteBuffer buffer)
            throws MimeException {
        try {
            String tmp = buffer.toString(StandardCharsets.US_ASCII).trim();
            int start = tmp.indexOf(EQUALS);
            String namePart = null;
            String valuePart = null;

            if (start == -1) {
                namePart = tmp;
            } else {
                namePart = tmp.substring(0, start);
                valuePart = tmp.substring(start + 1);
            }

            this.name = namePart;
            this.value = valuePart;

            // trim name and value
            if (name != null) {
                this.name = this.name.trim();
            }

            if (value != null) {
                this.value = this.value.trim();
            }
        } catch (StringIndexOutOfBoundsException sto) {
            throw new MimeException("Unable to parse MIME header attr: " + buffer.toString(), sto);
        }
    }

    /**
     * Gets name of attribute.
     *
     * @return name
     */
    public String getName() {
        return name;
    }

    /**
     * Sets name of attribute.
     *
     * @param name the name of attribute
     */
    public void setName(String name) {
        this.name = name;
    }

    /**
     * Gets value of attribute. Removes quotes if found.
     *
     * @return value
     *
     * @throws MimeException if a MIME related parsing error occurs
     */
    public String getValue()
            throws MimeException {
        return MimeMessage.removeChar(getValueString(), '\"');
    }

    /**
     * Gets value of attribute. Leaves quotes if found.
     *
     * @return value
     *
     * @throws MimeException if a MIME related parsing error occurs
     */
    public String getValueString() {
        if (value.indexOf("=?") != -1) {
            return RFC2047Encoder.getDecodedValue(value);
        } else {
            return value;
        }
    }

    /**
     * Sets value of attribute.
     *
     * @param value the attribute value
     */
    public void setValue(String value) {
        this.value = value;
    }

    /**
     * Gets String representation of this <code>MimeHeaderAttr</code>.
     *
     * @return String
     */
    public String toString() {
        if (value != null) {
            return name + "=" + value;
        } else {
            return name;
        }
    }

    /**
     * Checks if special character are in attribute value.
     *
     * @return true if special characters are included in attribute, false
     *         otherwise
     */
    private boolean containsSpecials() {
        boolean containsSpecials = false;
        char[] data = value.toCharArray();

        for (int i = 0; i < data.length; ++i) {
            char c = data[i];

            for (int ts = 0; ts < TSPECIALS.length; ++ts) {
                if (c == TSPECIALS[ts]) {
                    containsSpecials = true;

                    break;
                }
            }
        }

        return containsSpecials;
    }
}
