/**
 * 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.jaxws.context;

import java.lang.reflect.Array;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

import javax.activation.DataHandler;

import org.apache.cxf.attachment.AttachmentImpl;
import org.apache.cxf.message.Attachment;

/**
 * This is a package local attachments wrapper class to treat the jaxws attachments
 * as CXF's attachments.
 */
class WrappedAttachments implements Set<Attachment> {
    private Map<String, DataHandler> attachments;
    private Map<String, Attachment> cache;
        
    WrappedAttachments(Map<String, DataHandler> attachments) {
        this.attachments = attachments;
        this.cache = new HashMap<String, Attachment>();
    }
        
    public int size() {
        return attachments.size();
    }

    public boolean isEmpty() {
        return attachments.isEmpty();
    }

    public boolean contains(Object o) {
        if (o instanceof Attachment) {
            return attachments.containsKey(((Attachment) o).getId());
        }
        return false;
    }

    public Iterator<Attachment> iterator() {
        return new WrappedAttachmentsIterator(attachments.entrySet().iterator());
    }

    public Object[] toArray() {
        return toArray(new Object[attachments.size()]);
    }

    @SuppressWarnings("unchecked")
    public <T> T[] toArray(T[] a) {
        T[] copy = a.length == attachments.size() 
            ? a : (T[])Array.newInstance(a.getClass(), attachments.size());
        int i = 0;
        for (Map.Entry<String, DataHandler> entry : attachments.entrySet()) {
            Attachment o = cache.get(entry.getKey());
            if (o == null) {
                o = new AttachmentImpl(entry.getKey(), entry.getValue());
                cache.put(entry.getKey(), o);
            }
            copy[i++] = (T)o;
        }
        return copy;        
    }

    public boolean add(Attachment e) {
        if (!attachments.containsKey(e.getId())) {
            attachments.put(e.getId(), e.getDataHandler());
            cache.put(e.getId(), e);
            return true;
        }
        return false;
    }

    public boolean remove(Object o) {
        if (o instanceof Attachment) {
            cache.remove(((Attachment) o).getId());
            return attachments.remove(((Attachment) o).getId()) != null; 
        }
        return false;
    }

    public boolean containsAll(Collection<?> c) {
        boolean b = true;
        for (Iterator<?> it = c.iterator(); it.hasNext();) {
            Object o = it.next();
            if (!(o instanceof Attachment) && attachments.containsKey(((Attachment) o).getId())) {
                b = false;
                break;
            }
        }
        return b;
    }

    public boolean addAll(Collection<? extends Attachment> c) {
        boolean b = false;
        for (Iterator<? extends Attachment> it = c.iterator(); it.hasNext();) {
            Attachment o = it.next();
            if (!attachments.containsKey(o.getId())) {
                b = true;
                attachments.put(o.getId(), o.getDataHandler());
                cache.put(o.getId(), o);
            }
        }
        return b;
    }

    public boolean retainAll(Collection<?> c) {
        boolean b = false;
        Set<String> ids = new HashSet<String>();
        for (Iterator<?> it = c.iterator(); it.hasNext();) {
            Object o = it.next();
            if (o instanceof Attachment) {
                ids.add(((Attachment)o).getId());
            }
        }
        
        for (Iterator<String> it = attachments.keySet().iterator(); it.hasNext();) {
            String k = it.next();
            if (!ids.contains(k)) {
                b = true;
                it.remove();
                cache.remove(k);
            }
        }
        return b;
    }

    public boolean removeAll(Collection<?> c) {
        boolean b = false;
        for (Iterator<?> it = c.iterator(); it.hasNext();) {
            Object o = it.next();
            if (o instanceof Attachment && attachments.containsKey(((Attachment) o).getId())) {
                b = true;
                attachments.remove(((Attachment) o).getId());
                cache.remove(((Attachment) o).getId());
            }
        }
        return b;
    }

    public void clear() {
        attachments.clear();
        cache.clear();
    }

    Map<String, DataHandler> getAttachments() {
        return attachments;
    }
    
    class WrappedAttachmentsIterator implements Iterator<Attachment> {
        private Iterator<Map.Entry<String, DataHandler>> iterator;
        private String key;
        
        WrappedAttachmentsIterator(Iterator<Map.Entry<String, DataHandler>> iterator) { 
            this.iterator = iterator;
        }
            
        public boolean hasNext() {
            return iterator.hasNext();
        }

        public Attachment next() {
            Map.Entry<String, DataHandler> e = iterator.next();
            key = e.getKey();
            Attachment o = cache.get(key);
            if (o == null) {
                o = new AttachmentImpl(key, e.getValue());
                cache.put(key, o);
            }
            return o;
        }

        public void remove() {
            iterator.remove();
            cache.remove(key);
        }
    }
}
