/**
 * Copyright (c) 2002-2007 IBM Corporation and others.
 * All rights reserved.   This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v2.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v20.html
 * 
 * Contributors: 
 *   IBM - Initial API and implementation
 */
package org.eclipse.emf.common.ui.celleditor;


import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;

import org.eclipse.emf.common.CommonPlugin;
import org.eclipse.jface.viewers.ComboBoxCellEditor;
import org.eclipse.jface.viewers.ILabelProvider;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.CCombo;
import org.eclipse.swt.events.FocusEvent;
import org.eclipse.swt.events.FocusListener;
import org.eclipse.swt.events.KeyEvent;
import org.eclipse.swt.events.KeyListener;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;


/**
 * This uses a list of objects and a label provider to build a combo box based on model objects rather than on strings.
 * If sort is true, the list will be modified to match the order of the sorted labels.
 */
public class ExtendedComboBoxCellEditor extends ComboBoxCellEditor
{
  private static final long serialVersionUID = 1L;

  private static class StringPositionPair implements Comparable<StringPositionPair>
  {
    Comparator<String> comparator = CommonPlugin.INSTANCE.getComparator();

    public String key;

    public int position;

    StringPositionPair(String key, int position)
    {
      this.key = key;
      this.position = position;
    }

    public int compareTo(StringPositionPair object)
    {
      if (object == this)
      {
        return 0;
      }
      else
      {
        StringPositionPair that = object;
        return comparator.compare(key, that.key);
      }
    }
  }

  public static boolean select(String filter, String labelValue)
  {
    if (filter != null && filter.length() > 0)
    {
      if (filter.length() > labelValue.length())
      {
        return false;
      }
      for (int i = 0; i < filter.length(); i++)
      {
        if (Character.toLowerCase(filter.charAt(i)) != Character.toLowerCase(labelValue.charAt(i)))
        {
          return false;
        }
      }
    }
    return true;
  }

  public static <T> String[] createItems(List<T> list, ILabelProvider labelProvider, boolean sorted)
  {
    return createItems(list, labelProvider, null, sorted);
  }

  public static <T> String[] createItems(List<T> list, ILabelProvider labelProvider, String filter, boolean sorted)
  {
    String[] result;

    if (filter != null && filter.length() > 0)
    {
      sorted = true;
    }

    // If there are objects to populate...
    //
    if (list != null && list.size() > 0)
    {
      if (sorted)
      {
        List<T> unsortedList = new ArrayList<T>(list.size());
        if (filter != null && filter.length() > 0)
        {
          for (int i = 0; i < list.size(); i++)
          {
            if (select(filter, labelProvider.getText(list.get(i))))
            {
              unsortedList.add(list.get(i));
            }
          }
        }
        else
        {
          unsortedList.addAll(list);
        }
        list.clear();

        StringPositionPair[] pairs = new StringPositionPair [unsortedList.size()];

        for (int i = 0, size = unsortedList.size(); i < size; ++i)
        {
          Object object = unsortedList.get(i);
          pairs[i] = new StringPositionPair(labelProvider.getText(object), i);
        }

        Arrays.sort(pairs);

        // Create a new array.
        //
        result = new String [unsortedList.size()];
        // Fill in the result array with labels and re-populate the original list in order.
        //
        for (int i = 0, size = unsortedList.size(); i < size; ++i)
        {
          result[i] = pairs[i].key;
          list.add(unsortedList.get(pairs[i].position));
        }
      }
      else
      {
        // Create a new array.
        //
        result = new String [list.size()];
        // Fill in the array with labels.
        //
        for (int i = 0, size = list.size(); i < size; ++i)
        {
          Object object = list.get(i);
          result[i] = labelProvider.getText(object);
        }
      }
    }
    else
    {
      result = new String [] { "" };
    }

    return result;
  }

  /**
   * This keeps track of the list of model objects.
   */
  protected List<?> originalList;

  protected List<?> list;

  protected ILabelProvider labelProvider;

  protected boolean sorted;

  public ExtendedComboBoxCellEditor(Composite composite, List<?> list, ILabelProvider labelProvider)
  {
    this(composite, list, labelProvider, false, SWT.READ_ONLY);
  }

  public ExtendedComboBoxCellEditor(Composite composite, List<?> list, ILabelProvider labelProvider, boolean sorted)
  {
    this(composite, list, labelProvider, sorted, SWT.READ_ONLY);
  }

  public ExtendedComboBoxCellEditor(Composite composite, List<?> list, ILabelProvider labelProvider, int style)
  {
    this(composite, list, labelProvider, false, style);
  }

  public ExtendedComboBoxCellEditor(Composite composite, List<?> list, ILabelProvider labelProvider, boolean sorted, int style)
  {
    super(composite, createItems(sorted ? list = new ArrayList<Object>(list) : list, labelProvider, null, sorted), style);
    this.originalList = list;
    this.list = list;
    this.labelProvider = labelProvider;
    this.sorted = sorted;
    if ((style & SWT.READ_ONLY) != 0)
    {
      new FilteringAdapter(getControl());
    }
  }

  protected void refreshItems(String filter)
  {
    CCombo combo = (CCombo)getControl();
    if (combo != null && (!combo.isDisposed()))
    {
      String[] items = createItems(list = new ArrayList<Object>(originalList), labelProvider, filter, sorted);
      combo.setItems(items);
      if (items.length > 0)
      {
        combo.select(0);
      }
    }
  }

  @Override
  public Object doGetValue()
  {
    // Get the index into the list via this call to super.
    //
    int index = (Integer)super.doGetValue();
    return index < list.size() && index >= 0 ? list.get((Integer)super.doGetValue()) : null;
  }

  @Override
  public void doSetValue(Object value)
  {
    // Set the index of the object value in the list via this call to super.
    //
    int index = list.indexOf(value);
    if (index != -1)
    {
      super.doSetValue(list.indexOf(value));
    }
  }

  public class FilteringAdapter implements KeyListener, FocusListener
  {

    /**
     * 
     */
    private static final long serialVersionUID = 1L;

    public FilteringAdapter(Control control)
    {
      control.addKeyListener(this);
      control.addFocusListener(this);
    }

    private StringBuffer filter = new StringBuffer();

    private void refreshItems()
    {
      ExtendedComboBoxCellEditor.this.refreshItems(filter.toString());
    }

    public void keyPressed(KeyEvent e)
    {
      e.doit = false;

      if (e.keyCode == SWT.DEL || e.keyCode == SWT.BS)
      {
        if (filter.length() > 0)
        {
          filter = new StringBuffer(filter.substring(0, filter.length() - 1));
        }
      }
      else if (e.keyCode == SWT.ARROW_UP || e.keyCode == SWT.ARROW_DOWN || e.keyCode == SWT.CR || e.keyCode == SWT.KEYPAD_CR || e.keyCode == SWT.LF)
      {
        e.doit = true;
      }
      else if (e.keyCode == SWT.ESC)
      {
        filter = new StringBuffer();
      }
      else if (e.character != '\0')
      {
        filter.append(e.character);
      }
      if (!e.doit)
      {
        refreshItems();
      }
    }

    public void keyReleased(KeyEvent e)
    {
      // Do nothing
    }

    public void focusGained(FocusEvent e)
    {
      filter = new StringBuffer();
    }

    public void focusLost(FocusEvent e)
    {
      filter = new StringBuffer();
      refreshItems();
    }
  }
}
