/**
   * The UiBinder interface used by this example.
   */
  interface Binder extends UiBinder<Widget, CwCellSampler> {
  }

  /**
   * The constants used in this Content Widget.
   */
  public static interface CwConstants extends Constants {
    String cwCellSamplerDescription();

    String cwCellSamplerName();
  }

  /**
   * The images used for this example.
   */
  static interface Images extends ClientBundle {
    ImageResource contactsGroup();
  }

  /**
   * Get a cell value from a record.
   * 
   * @param <C> the cell type
   */
  private static interface GetValue<C> {
    C getValue(ContactInfo contact);
  }

  /**
   * A pending change to a {@link ContactInfo}. Changes aren't committed
   * immediately to illustrate that cells can remember their pending changes.
   * 
   * @param <T> the data type being changed
   */
  private abstract static class PendingChange<T> {
    private final ContactInfo contact;
    private final T value;

    public PendingChange(ContactInfo contact, T value) {
      this.contact = contact;
      this.value = value;
    }

    /**
     * Commit the change to the contact.
     */
    public void commit() {
      doCommit(contact, value);
    }

    /**
     * Update the appropriate field in the {@link ContactInfo}.
     * 
     * @param contact the contact to update
     * @param value the new value
     */
    protected abstract void doCommit(ContactInfo contact, T value);
  }

  /**
   * Updates the birthday.
   */
  private static class BirthdayChange extends PendingChange<Date> {

    public BirthdayChange(ContactInfo contact, Date value) {
      super(contact, value);
    }

    @Override
    protected void doCommit(ContactInfo contact, Date value) {
      contact.setBirthday(value);
    }
  }

  /**
   * Updates the category.
   */
  private static class CategoryChange extends PendingChange<Category> {

    public CategoryChange(ContactInfo contact, Category value) {
      super(contact, value);
    }

    @Override
    protected void doCommit(ContactInfo contact, Category value) {
      contact.setCategory(value);
    }
  }

  /**
   * Updates the first name.
   */
  private static class FirstNameChange extends PendingChange<String> {

    public FirstNameChange(ContactInfo contact, String value) {
      super(contact, value);
    }

    @Override
    protected void doCommit(ContactInfo contact, String value) {
      contact.setFirstName(value);
    }
  }

  /**
   * Updates the last name.
   */
  private static class LastNameChange extends PendingChange<String> {

    public LastNameChange(ContactInfo contact, String value) {
      super(contact, value);
    }

    @Override
    protected void doCommit(ContactInfo contact, String value) {
      contact.setLastName(value);
    }
  }

  /**
   * The main CellTable.
   */
  @UiField(provided = true)
  DataGrid<ContactInfo> contactList;

  /**
   * The commit button.
   */
  @UiField
  Button commitButton;

  /**
   * The redraw button.
   */
  @UiField
  Button redrawButton;

  /**
   * The list of cells that are editable.
   */
  private List<AbstractEditableCell<?, ?>> editableCells;

  /**
   * The list of pending changes.
   */
  private List<PendingChange<?>> pendingChanges = new ArrayList<PendingChange<?>>();

  /**
   * Initialize this example.
   */
  @Override
  public Widget onInitialize() {
    Images images = GWT.create(Images.class);

    // Create the table.
    editableCells = new ArrayList<AbstractEditableCell<?, ?>>();
    contactList = new DataGrid<ContactInfo>(25, ContactInfo.KEY_PROVIDER);
    contactList.setMinimumTableWidth(140, Unit.EM);
    ContactDatabase.get().addDataDisplay(contactList);

    // CheckboxCell.
    final Category[] categories = ContactDatabase.get().queryCategories();
    addColumn(new CheckboxCell(), "Checkbox", new GetValue<Boolean>() {
      @Override
      public Boolean getValue(ContactInfo contact) {
        // Checkbox indicates that the contact is a relative.
        // Index 0 = Family.
        return contact.getCategory() == categories[0];
      }
    }, new FieldUpdater<ContactInfo, Boolean>() {
      @Override
      public void update(int index, ContactInfo object, Boolean value) {
        if (value) {
          // If a relative, use the Family Category.
          pendingChanges.add(new CategoryChange(object, categories[0]));
        } else {
          // If not a relative, use the Contacts Category.
          pendingChanges.add(new CategoryChange(object, categories[categories.length - 1]));
        }
      }
    });

    // TextCell.
    addColumn(new TextCell(), "Text", new GetValue<String>() {
      @Override
      public String getValue(ContactInfo contact) {
        return contact.getFullName();
      }
    }, null);

    // EditTextCell.
    Column<ContactInfo, String> editTextColumn =
        addColumn(new EditTextCell(), "EditText", new GetValue<String>() {
          @Override
          public String getValue(ContactInfo contact) {
            return contact.getFirstName();
          }
        }, new FieldUpdater<ContactInfo, String>() {
          @Override
          public void update(int index, ContactInfo object, String value) {
            pendingChanges.add(new FirstNameChange(object, value));
          }
        });
    contactList.setColumnWidth(editTextColumn, 16.0, Unit.EM);

    // TextInputCell.
    Column<ContactInfo, String> textInputColumn =
        addColumn(new TextInputCell(), "TextInput", new GetValue<String>() {
          @Override
          public String getValue(ContactInfo contact) {
            return contact.getLastName();
          }
        }, new FieldUpdater<ContactInfo, String>() {
          @Override
          public void update(int index, ContactInfo object, String value) {
            pendingChanges.add(new LastNameChange(object, value));
          }
        });
    contactList.setColumnWidth(textInputColumn, 16.0, Unit.EM);

    // ClickableTextCell.
    addColumn(new ClickableTextCell(), "ClickableText", new GetValue<String>() {
      @Override
      public String getValue(ContactInfo contact) {
        return "Click " + contact.getFirstName();
      }
    }, new FieldUpdater<ContactInfo, String>() {
      @Override
      public void update(int index, ContactInfo object, String value) {
        Window.alert("You clicked " + object.getFullName());
      }
    });

    // ActionCell.
    addColumn(new ActionCell<ContactInfo>("Click Me", new ActionCell.Delegate<ContactInfo>() {
      @Override
      public void execute(ContactInfo contact) {
        Window.alert("You clicked " + contact.getFullName());
      }
    }), "Action", new GetValue<ContactInfo>() {
      @Override
      public ContactInfo getValue(ContactInfo contact) {
        return contact;
      }
    }, null);

    // ButtonCell.
    addColumn(new ButtonCell(), "Button", new GetValue<String>() {
      @Override
      public String getValue(ContactInfo contact) {
        return "Click " + contact.getFirstName();
      }
    }, new FieldUpdater<ContactInfo, String>() {
      @Override
      public void update(int index, ContactInfo object, String value) {
        Window.alert("You clicked " + object.getFullName());
      }
    });

    // DateCell.
    DateTimeFormat dateFormat = DateTimeFormat.getFormat(PredefinedFormat.DATE_MEDIUM);
    addColumn(new DateCell(dateFormat), "Date", new GetValue<Date>() {
      @Override
      public Date getValue(ContactInfo contact) {
        return contact.getBirthday();
      }
    }, null);

    // DatePickerCell.
    addColumn(new DatePickerCell(dateFormat), "DatePicker", new GetValue<Date>() {
      @Override
      public Date getValue(ContactInfo contact) {
        return contact.getBirthday();
      }
    }, new FieldUpdater<ContactInfo, Date>() {
      @Override
      public void update(int index, ContactInfo object, Date value) {
        pendingChanges.add(new BirthdayChange(object, value));
      }
    });

    // NumberCell.
    Column<ContactInfo, Number> numberColumn =
        addColumn(new NumberCell(), "Number", new GetValue<Number>() {
          @Override
          public Number getValue(ContactInfo contact) {
            return contact.getAge();
          }
        }, null);
    numberColumn.setHorizontalAlignment(HasHorizontalAlignment.ALIGN_LOCALE_END);

    // IconCellDecorator.
    addColumn(new IconCellDecorator<String>(images.contactsGroup(), new TextCell()), "Icon",
        new GetValue<String>() {
          @Override
          public String getValue(ContactInfo contact) {
            return contact.getCategory().getDisplayName();
          }
        }, null);

    // ImageCell.
    addColumn(new ImageCell(), "Image", new GetValue<String>() {
      @Override
      public String getValue(ContactInfo contact) {
        return "contact.jpg";
      }
    }, null);

    // SelectionCell.
    List<String> options = new ArrayList<String>();
    for (Category category : categories) {
      options.add(category.getDisplayName());
    }
    addColumn(new SelectionCell(options), "Selection", new GetValue<String>() {
      @Override
      public String getValue(ContactInfo contact) {
        return contact.getCategory().getDisplayName();
      }
    }, new FieldUpdater<ContactInfo, String>() {
      @Override
      public void update(int index, ContactInfo object, String value) {
        for (Category category : categories) {
          if (category.getDisplayName().equals(value)) {
            pendingChanges.add(new CategoryChange(object, category));
            break;
          }
        }
      }
    });

    // Create the UiBinder.
    Binder uiBinder = GWT.create(Binder.class);
    Widget widget = uiBinder.createAndBindUi(this);

    // Add handlers to redraw or refresh the table.
    redrawButton.addClickHandler(new ClickHandler() {
      @Override
      public void onClick(ClickEvent event) {
        contactList.redraw();
      }
    });
    commitButton.addClickHandler(new ClickHandler() {
      @Override
      public void onClick(ClickEvent event) {
        // Commit the changes.
        for (PendingChange<?> pendingChange : pendingChanges) {
          pendingChange.commit();
        }
        pendingChanges.clear();

        // Push the changes to the views.
        ContactDatabase.get().refreshDisplays();
      }
    });

    return widget;
  }

  /**
   * Add a column with a header.
   * 
   * @param <C> the cell type
   * @param cell the cell used to render the column
   * @param headerText the header string
   * @param getter the value getter for the cell
   */
  private <C> Column<ContactInfo, C> addColumn(Cell<C> cell, String headerText,
      final GetValue<C> getter, FieldUpdater<ContactInfo, C> fieldUpdater) {
    Column<ContactInfo, C> column = new Column<ContactInfo, C>(cell) {
      @Override
      public C getValue(ContactInfo object) {
        return getter.getValue(object);
      }
    };
    column.setFieldUpdater(fieldUpdater);
    if (cell instanceof AbstractEditableCell<?, ?>) {
      editableCells.add((AbstractEditableCell<?, ?>) cell);
    }
    contactList.addColumn(column, headerText);
    return column;
  }