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

import java.io.UnsupportedEncodingException;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class ContentDisposition {
    private static final String CD_HEADER_PARAMS_EXPRESSION =
       "[\\w-]++( )?\\*?=( )?((\"[^\"]++\")|([^;]+))";
    private static final Pattern CD_HEADER_PARAMS_PATTERN =
            Pattern.compile(CD_HEADER_PARAMS_EXPRESSION);

    private static final String CD_HEADER_EXT_PARAMS_EXPRESSION =
            "(?i)(UTF-8|ISO-8859-1)''((?:%[0-9a-f]{2}|\\S)+)";
    private static final Pattern CD_HEADER_EXT_PARAMS_PATTERN =
            Pattern.compile(CD_HEADER_EXT_PARAMS_EXPRESSION);
    private static final Pattern CODEPOINT_ENCODED_VALUE_PATTERN = Pattern.compile("&#[0-9]{4};|\\S");
    
    private static final String FILE_NAME = "filename";

    private String value;
    private String type;
    private Map<String, String> params = new LinkedHashMap<>();

    public ContentDisposition(String value) {
        this.value = value;

        String tempValue = value;

        int index = tempValue.indexOf(';');
        if (index > 0 && tempValue.indexOf('=') >= index) {
            type = tempValue.substring(0, index).trim();
            tempValue = tempValue.substring(index + 1);
        }

        String extendedFilename = null;
        Matcher m = CD_HEADER_PARAMS_PATTERN.matcher(tempValue);
        while (m.find()) {
            final String paramName;
            String paramValue = "";

            String groupValue = m.group().trim();
            int eqIndex = groupValue.indexOf('=');
            if (eqIndex > 0) {
                paramName = groupValue.substring(0, eqIndex).trim();
                if (eqIndex + 1 != groupValue.length()) {
                    paramValue = groupValue.substring(eqIndex + 1).trim().replace("\"", "");
                }
            } else {
                paramName = groupValue;
            }
            // filename* looks like the only CD param that is human readable
            // and worthy of the extended encoding support. Other parameters
            // can be supported if needed, see the complete list below
            /*
                http://www.iana.org/assignments/cont-disp/cont-disp.xhtml#cont-disp-2

                filename            name to be used when creating file [RFC2183]
                creation-date       date when content was created [RFC2183]
                modification-date   date when content was last modified [RFC2183]
                read-date           date when content was last read [RFC2183]
                size                approximate size of content in octets [RFC2183]
                name                original field name in form [RFC2388]
                voice               type or use of audio content [RFC2421]
                handling            whether or not processing is required [RFC3204]
             */
            if ("filename*".equalsIgnoreCase(paramName)) {
                // try to decode the value if it matches the spec
                try {
                    Matcher matcher = CD_HEADER_EXT_PARAMS_PATTERN.matcher(paramValue);
                    if (matcher.matches()) {
                        String encodingScheme = matcher.group(1);
                        String encodedValue = matcher.group(2);
                        paramValue = Rfc5987Util.decode(encodedValue, encodingScheme);
                        extendedFilename = paramValue;
                    }
                } catch (UnsupportedEncodingException e) {
                    // would be odd not to support UTF-8 or 8859-1
                }
            } else if (FILE_NAME.equalsIgnoreCase(paramName) && paramValue.contains("&#")) {
                Matcher matcher = CODEPOINT_ENCODED_VALUE_PATTERN.matcher(paramValue);
                StringBuilder sb = new StringBuilder();
                while (matcher.find()) {
                    String matched = matcher.group();
                    if (matched.startsWith("&#")) {
                        int codePoint = Integer.parseInt(matched.substring(2, 6));
                        sb.append(Character.toChars(codePoint));
                    } else {
                        sb.append(matched.charAt(0));
                    }
                }
                if (sb.length() > 0) {
                    paramValue = sb.toString();
                }
            }
            params.put(paramName.toLowerCase(), paramValue);
        }
        if (extendedFilename != null) {
            params.put(FILE_NAME, extendedFilename);
        }
    }

    public String getType() {
        return type;
    }

    public String getFilename() {
        return params.get(FILE_NAME);
    }

    public String getParameter(String name) {
        return params.get(name);
    }

    public Map<String, String> getParameters() {
        return Collections.unmodifiableMap(params);
    }

    public String toString() {
        return value;
    }
}
