/*
 * 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.commons.vfs2.provider.mime;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;

import javax.mail.Address;
import javax.mail.Header;
import javax.mail.Message.RecipientType;
import javax.mail.MessagingException;
import javax.mail.Part;
import javax.mail.internet.MimeMessage;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import lombok.Generated;

/**
 * A map which tries to allow access to the various aspects of the mail.
 */
@Generated // to exclude from code coverage
public class MimeAttributesMap implements Map<String, Object> {
    private static final String OBJECT_PREFIX = "obj.";

    private final Log log = LogFactory.getLog(MimeAttributesMap.class);
    private final Part part;
    private final Map<String, Method> mimeMessageGetters = new TreeMap<>();

    private Map<String, Object> backingMap;

    public MimeAttributesMap(final Part part) {
        this.part = part;
        addMimeMessageMethod(part.getClass().getMethods());
        addMimeMessageMethod(part.getClass().getDeclaredMethods());
    }

    private void addMimeMessageMethod(final Method[] methods) {
        for (final Method method : methods) {
            if (!Modifier.isPublic(method.getModifiers())) {
                continue;
            }
            if (method.getParameterTypes().length > 0) {
                continue;
            }

            if (method.getName().startsWith("get")) {
                mimeMessageGetters.put(method.getName().substring(3), method);
            } else if (method.getName().startsWith("is")) {
                mimeMessageGetters.put(method.getName().substring(2), method);
            }
        }
    }

    private Map<String, Object> getMap() {
        if (backingMap == null) {
            backingMap = createMap();
        }

        return backingMap;
    }

    private Map<String, Object> createMap() {
        // Object is either a String, or a List of Strings
        final Map<String, Object> ret = new TreeMap<>();

        Enumeration<Header> headers;
        try {
            final Enumeration<Header> allHeaders = part.getAllHeaders();
            headers = allHeaders;
        } catch (final MessagingException e) {
            throw new RuntimeException(e);
        }

        // add all headers
        while (headers.hasMoreElements()) {
            final Header header = headers.nextElement();
            final String headerName = header.getName();

            final Object values = ret.get(headerName);

            if (values == null) {
                ret.put(headerName, header.getValue());
            } else if (values instanceof String) {
                final ArrayList<String> newValues = new ArrayList<>();
                newValues.add((String) values);
                newValues.add(header.getValue());
                ret.put(headerName, newValues);
            } else if (values instanceof List) {
                @SuppressWarnings("unchecked") // we only add Strings to the Lists
                final List<String> list = (List<String>) values;
                list.add(header.getValue());
            }
        }

        // add all simple get/is results (with obj. prefix)
        final Iterator<Entry<String, Method>> iterEntries = mimeMessageGetters.entrySet().iterator();
        while (iterEntries.hasNext()) {
            final Map.Entry<String, Method> entry = iterEntries.next();
            final String name = entry.getKey();
            final Method method = entry.getValue();

            try {
                final Object value = method.invoke(part);
                ret.put(OBJECT_PREFIX + name, value);
            } catch (final IllegalAccessException | InvocationTargetException e) {
                log.debug(e.getLocalizedMessage(), e);
            }
        }

        // add extended fields (with obj. prefix too)
        if (part instanceof MimeMessage) {
            final MimeMessage message = (MimeMessage) part;
            try {
                final Address[] address = message.getRecipients(RecipientType.BCC);
                ret.put(OBJECT_PREFIX + "Recipients.BCC", address);
            } catch (final MessagingException e) {
                log.debug(e.getLocalizedMessage(), e);
            }
            try {
                final Address[] address = message.getRecipients(RecipientType.CC);
                ret.put(OBJECT_PREFIX + "Recipients.CC", address);
            } catch (final MessagingException e) {
                log.debug(e.getLocalizedMessage(), e);
            }
            try {
                final Address[] address = message.getRecipients(RecipientType.TO);
                ret.put(OBJECT_PREFIX + "Recipients.TO", address);
            } catch (final MessagingException e) {
                log.debug(e.getLocalizedMessage(), e);
            }
            try {
                final Address[] address = message.getRecipients(MimeMessage.RecipientType.NEWSGROUPS);
                ret.put(OBJECT_PREFIX + "Recipients.NEWSGROUPS", address);
            } catch (final MessagingException e) {
                log.debug(e.getLocalizedMessage(), e);
            }
        }

        return ret;
    }

    @Override
    public int size() {
        return getMap().size();
    }

    @Override
    public boolean isEmpty() {
        return getMap().size() < 1;
    }

    @Override
    public boolean containsKey(final Object key) {
        return getMap().containsKey(key);
    }

    @Override
    public boolean containsValue(final Object value) {
        return getMap().containsValue(value);
    }

    @Override
    public Object get(final Object key) {
        return getMap().get(key);
    }

    @Override
    public Object put(final String key, final Object value) {
        throw new UnsupportedOperationException();
    }

    @Override
    public Object remove(final Object key) {
        throw new UnsupportedOperationException();
    }

    @Override
    public void putAll(final Map<? extends String, ? extends Object> t) {
        throw new UnsupportedOperationException();
    }

    @Override
    public void clear() {
        throw new UnsupportedOperationException();
    }

    @Override
    public Set<String> keySet() {
        return Collections.unmodifiableSet(getMap().keySet());
    }

    @Override
    public Collection<Object> values() {
        return Collections.unmodifiableCollection(getMap().values());
    }

    @Override
    public Set<Entry<String, Object>> entrySet() {
        return Collections.unmodifiableSet(getMap().entrySet());
    }
}
