package org.pm4j.core.pm.pageable;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;

import org.pm4j.core.exception.PmRuntimeException;
import org.pm4j.core.pm.PmDefaults;

/**
 * Implements a {@link PageableCollection} based on an {@link List} of items to
 * handle.
 *
 * @author olaf boede
 *
 * @param <T_ITEM>
 *          The type of items handled by this set.
 */
public class PageableListImpl<T_ITEM> implements PageableCollection<T_ITEM> {

  private Collection<T_ITEM> originalObjects;
  private List<T_ITEM>       objectsInOriginalSortOrder;
  private List<T_ITEM>       objects;
  private int                pageSize = 10;
  private int                currentPageIdx;
  private boolean            multiSelect;
  private Set<T_ITEM>        selectedItems = new LinkedHashSet<T_ITEM>();
  private Comparator<?>      currentSortComparator;
  private Filter<T_ITEM>     currentFilter;

  /**
   * @param objects
   *          The set of objects to iterate over.
   * @param pageSize
   *          The page size to use.
   */
  public PageableListImpl(Collection<T_ITEM> objects) {
    this.originalObjects = objects;
    this.currentPageIdx = 1;

    // TODO olaf: add another switch to enable such expensive checks:
    if (objects != null && PmDefaults.getInstance().debugHints) {
      Set<T_ITEM> checkSet = new HashSet<T_ITEM>(objects);
      if (checkSet.size() != objects.size()) {
        throw new PmRuntimeException("Some instances of the pageable list are not unique!");
      }
    }

    onUpdateCollection();
  }

  /**
   * Creates an empty collection.
   * @param pageSize The page size.
   * @param multiSelect The multi selection behavior definition.
   */
  public PageableListImpl(int pageSize, boolean multiSelect) {
    this(new ArrayList<T_ITEM>());
    this.pageSize = pageSize;
    this.multiSelect = multiSelect;
  }


  @Override
  public List<T_ITEM> getItemsOnPage() {
    if (objects.isEmpty()) {
      return Collections.emptyList();
    }

    int first = PageableCollectionUtil.getIdxOfFirstItemOnPage(this) - 1;
    int last = PageableCollectionUtil.getIdxOfLastItemOnPage(this);

    if (first < 0)
      throw new RuntimeException();

    return objects.subList(first, last);
  }

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

  @Override
  public int getPageSize() {
    return pageSize;
  }

  @Override
  public void setPageSize(int newSize) {
    pageSize = newSize;
  }

  @Override
  public int getCurrentPageIdx() {
    return currentPageIdx;
  }

  @Override
  public void setCurrentPageIdx(int pageIdx) {
    this.currentPageIdx = pageIdx;
  }


  @Override
  public void sortItems(Comparator<?> sortComparator) {
    currentSortComparator = sortComparator;
    _applyFilterAndSortOrder();
  }

  @Override
  public void sortBackingItems(Comparator<?> sortComparator) {
    sortItems(sortComparator);
  }

  @SuppressWarnings("unchecked")
  @Override
  public void setItemFilter(Filter<?> filter) {
    currentFilter = (Filter<T_ITEM>)filter;
    _applyFilterAndSortOrder();
  }

  @Override
  public void setBackingItemFilter(Filter<?> filter) {
    setItemFilter(filter);
  }

  @Override
  public Filter<?> getBackingItemFilter() {
    return currentFilter;
  }

  @Override
  public boolean isSelected(T_ITEM item) {
    return selectedItems.contains(item);
  }

  @Override
  public void select(T_ITEM item) {
    if (!multiSelect) {
      selectedItems.clear();
    }

    if (item != null) {
      selectedItems.add(item);
    }
  }

  @Override
  public void deSelect(T_ITEM item) {
    selectedItems.remove(item);
  }

  @Override
  public boolean isMultiSelect() {
    return multiSelect;
  }

  @Override
  public void setMultiSelect(boolean isMultiSelect) {
    this.multiSelect = isMultiSelect;
  }

  @Override
  public Collection<T_ITEM> getSelectedItems() {
    return selectedItems;
  }

  @Override
  public void onUpdateCollection() {
    _assignObjectsFromOri();
    _applyFilterAndSortOrder();
  }

  @SuppressWarnings("unchecked")
  private void _assignObjectsFromOri() {
    this.objectsInOriginalSortOrder = (originalObjects == null || originalObjects.isEmpty())
        ? java.util.Collections.EMPTY_LIST
        : new ArrayList<T_ITEM>(originalObjects);
    this.objects = objectsInOriginalSortOrder;
  }

  private void _applyFilterAndSortOrder() {
    List<T_ITEM> filteredList = _filter(objectsInOriginalSortOrder);
    _sortAndAssignToObjects(filteredList);

    // XXX olaf: just moves to the last possible page if necessary.
    // The user may want to stay on the page with his selected item.
    // We need to define strategies for application specific definitions.
    if (currentPageIdx * pageSize >= filteredList.size()) {
      currentPageIdx = PageableCollectionUtil.getNumOfPages(this);
    }

    // TODO olaf: if the filter gets changed to find items again, we need to do this to prevent
    //            negative page item index identification...
    // Check if it was really a good idea to have a 0-page index for empty collections.
    if (objects.size() > 0 && currentPageIdx == 0) {
      currentPageIdx = 1;
    }
  }

  @SuppressWarnings("unchecked")
  private void _sortAndAssignToObjects(List<T_ITEM> srcList) {
    if (currentSortComparator == null) {
      objects = srcList;
    } else {
      // prevent unintended sort operation on the source list.
      if (objects == srcList) {
        objects = new ArrayList<T_ITEM>(srcList);
      }
      Collections.sort(objects, (Comparator<T_ITEM>)currentSortComparator);
    }
  }

  private List<T_ITEM> _filter(List<T_ITEM> unfilteredList) {
    if (currentFilter == null) {
      return unfilteredList;
    }

    // remember selected item and clear selection if not multiSelect
    T_ITEM selectedItem = null;
    if(!multiSelect) {
      if(!selectedItems.isEmpty()) {
        selectedItem = selectedItems.iterator().next();
        selectedItems.clear();
      }
    }
    List<T_ITEM> filteredList = new ArrayList<T_ITEM>();
    int listSize = unfilteredList.size();
    for (int i=0; i<listSize; ++i) {
      T_ITEM item = unfilteredList.get(i);
      if (currentFilter.doesItemMatch(item)) {
        filteredList.add(item);
        // re-select item if not multiSelect and item is within filtered items
        if(!multiSelect) {
          if(item == selectedItem) {
            select(selectedItem);
          }
        }
      }
    }

    return filteredList;
  }

}

@@ -5767,43 +5767,8 @@
 t.%0D%0A
-      if (objects == srcList) %7B%0D%0A  
     
@@ -5808,33 +5808,24 @@
 (srcList);%0D%0A
-      %7D%0D%0A
       Collec

Allow to filter and sort in parallel.