/*
 * $Id$
 *
 * License Agreement.
 *
 * Rich Faces - Natural Ajax for Java Server Faces (JSF)
 *
 * Copyright (C) 2007 Exadel, Inc.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License version 2.1 as published by the Free Software Foundation.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 */
package org.richfaces.cdk.model;

import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.Set;

import com.google.common.base.Predicate;
import com.google.common.collect.Iterables;
import com.google.common.collect.Iterators;
import com.google.common.collect.Lists;

/**
 * <p class="changed_added_4_0">
 * Base class for all model collections. This collection use {@link Mergeable#same(Object)} method instead of equals to lookup
 * objects in the {@link #contains(Object)} and {@link #remove(Object)} methods. In addition, it merges same objects instead of
 * replace in the {@link #add(ModelElement)} method.
 * </p>
 *
 * @author asmirnov@exadel.com
 */
public class ModelSet<E extends ModelElement<? super E>> implements ModelCollection<E>, Set<E> {
    private final List<E> elements = Lists.newArrayList();
    private Comparator<E> comparator;

    public static <T extends ModelElement<? super T>> ModelSet<T> create() {
        return new ModelSet<T>();
    }

    public static <T extends ModelElement<? super T>> ModelSet<T> create(Comparator<T> comparator) {
        ModelSet<T> collection = new ModelSet<T>();
        collection.setComparator(comparator);
        return collection;
    }

    /**
     * <p class="changed_added_4_0">
     * </p>
     *
     * @return the comparator
     */
    public Comparator<E> getComparator() {
        return this.comparator;
    }

    /**
     * <p class="changed_added_4_0">
     * </p>
     *
     * @param comparator the comparator to set
     */
    public void setComparator(Comparator<E> comparator) {
        this.comparator = comparator;
    }

    @Override
    public E find(final Predicate<? super E> predicate) {
        try {
            return Iterables.find(elements, predicate);
        } catch (NoSuchElementException e) {
            return null;
        }
    }

    @Override
    public boolean add(E e) {
        if (null == e) {
            throw new NullPointerException();
        }
        for (E element : elements) {
            if (element.same(e)) {
                element.merge(e);
                return false;
            }
        }
        return elements.add(e);
    }

    @Override
    public boolean addAll(Collection<? extends E> c) {
        boolean changed = false;

        for (E e : c) {
            changed |= add(e);
        }

        return changed;
    }

    @Override
    public void clear() {
        elements.clear();
    }

    @SuppressWarnings("unchecked")
    @Override
    public boolean contains(Object o) {
        if (null != o) {
            Class<? extends Object> clazz = o.getClass();
            for (E element : elements) {
                if (clazz.isInstance(element) && element.same((E) o)) {
                    return true;
                }
            }
        }
        return false;
    }

    @Override
    public boolean containsAll(Collection<?> c) {
        return elements.containsAll(c);
    }

    @Override
    public boolean isEmpty() {
        return elements.isEmpty();
    }

    @Override
    public Iterator<E> iterator() {
        if (null == comparator) {
            return elements.iterator();
        } else {
            List<E> list = Lists.newArrayList(elements);
            Collections.sort(list, comparator);
            return Iterators.unmodifiableIterator(list.iterator());
        }
    }

    @Override
    public boolean remove(Object o) {
        if (null != o) {
            Class<? extends Object> clazz = o.getClass();
            for (E element : elements) {
                if (clazz.isInstance(element) && element.same((E) o)) {
                    elements.remove(element);
                    return true;
                }
            }
        }
        return false;
    }

    @Override
    public boolean removeAll(Collection<?> c) {
        boolean changed = false;

        for (Object e : c) {
            changed |= remove(e);
        }

        return changed;
    }

    @Override
    public boolean retainAll(Collection<?> c) {
        return elements.retainAll(c);
    }

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

    @Override
    public Object[] toArray() {
        Object[] array = elements.toArray();
        return array;
    }

    @Override
    public <T> T[] toArray(T[] a) {
        return elements.toArray(a);
    }

    @Override
    public <R, D> R accept(R result, Visitor<R, D> visitor, D data) {
        return null == result ? accept(visitor, data) : result;
    }

    public <R, D> R accept(Visitor<R, D> visitor, D data) {
        for (ModelElement element : elements) {
            R result = element.accept(visitor, data);
            if (null != result) {
                return result;
            }
        }
        return null;
    }
}
