    /*
     * Copyright 2010 Google Inc.
     *
     * Licensed 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 com.google.gwt.sample.showcase.client.content.cell;

    import com.google.gwt.cell.client.AbstractCell;
    import com.google.gwt.cell.client.Cell;
    import com.google.gwt.cell.client.CheckboxCell;
    import com.google.gwt.cell.client.CompositeCell;
    import com.google.gwt.cell.client.FieldUpdater;
    import com.google.gwt.cell.client.HasCell;
    import com.google.gwt.core.client.GWT;
    import com.google.gwt.dom.client.Element;
    import com.google.gwt.resources.client.ClientBundle;
    import com.google.gwt.resources.client.ImageResource;
    import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
    import com.google.gwt.sample.showcase.client.content.cell.ContactDatabase.Category;
    import com.google.gwt.sample.showcase.client.content.cell.ContactDatabase.ContactInfo;
    import com.google.gwt.sample.showcase.client.content.cell.CwCellList.ContactCell;
    import com.google.gwt.user.client.ui.AbstractImagePrototype;
    import com.google.gwt.view.client.DefaultSelectionEventManager;
    import com.google.gwt.view.client.ListDataProvider;
    import com.google.gwt.view.client.SelectionModel;
    import com.google.gwt.view.client.TreeViewModel;

    import java.util.ArrayList;
    import java.util.List;
    import java.util.Map;
    import java.util.TreeMap;

    /**
     * The {@link TreeViewModel} used to organize contacts into a hierarchy.
     */
    public class ContactTreeViewModel implements TreeViewModel {

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

        ImageResource contactsGroup();
      }

      /**
       * The cell used to render categories.
       */
      private static class CategoryCell extends AbstractCell<Category> {

        /**
         * The html of the image used for contacts.
         */
        private final String imageHtml;

        public CategoryCell(ImageResource image) {
          this.imageHtml = AbstractImagePrototype.create(image).getHTML();
        }

        @Override
        public void render(Context context, Category value, SafeHtmlBuilder sb) {
          if (value != null) {
            sb.appendHtmlConstant(imageHtml).appendEscaped(" ");
            sb.appendEscaped(value.getDisplayName());
          }
        }
      }

      /**
       * Tracks the number of contacts in a category that begin with the same
       * letter.
       */
      private static class LetterCount implements Comparable<LetterCount> {
        private final Category category;
        private final char firstLetter;
        private int count;

        /**
         * Construct a new {@link LetterCount} for one contact.
         *
         * @param category the category
         * @param firstLetter the first letter of the contacts name
         */
        public LetterCount(Category category, char firstLetter) {
          this.category = category;
          this.firstLetter = firstLetter;
          this.count = 1;
        }

        public int compareTo(LetterCount o) {
          return (o == null) ? -1 : (firstLetter - o.firstLetter);
        }

        @Override
        public boolean equals(Object o) {
          return compareTo((LetterCount) o) == 0;
        }

        @Override
        public int hashCode() {
          return firstLetter;
        }

        /**
         * Increment the count.
         */
        public void increment() {
          count++;
        }
      }

      /**
       * A Cell used to render the LetterCount.
       */
      private static class LetterCountCell extends AbstractCell<LetterCount> {

        @Override
        public void render(Context context, LetterCount value, SafeHtmlBuilder sb) {
          if (value != null) {
            sb.appendEscaped(value.firstLetter + " (" + value.count + ")");
          }
        }
      }

      /**
       * The static images used in this model.
       */
      private static Images images;

      private final ListDataProvider<Category> categoryDataProvider;
      private final Cell<ContactInfo> contactCell;
      private final DefaultSelectionEventManager<ContactInfo> selectionManager =
          DefaultSelectionEventManager.createCheckboxManager();
      private final SelectionModel<ContactInfo> selectionModel;

      public ContactTreeViewModel(final SelectionModel<ContactInfo> selectionModel) {
        this.selectionModel = selectionModel;
        if (images == null) {
          images = GWT.create(Images.class);
        }

        // Create a data provider that provides categories.
        categoryDataProvider = new ListDataProvider<Category>();
        List<Category> categoryList = categoryDataProvider.getList();
        for (Category category : ContactDatabase.get().queryCategories()) {
          categoryList.add(category);
        }

        // Construct a composite cell for contacts that includes a checkbox.
        List<HasCell<ContactInfo, ?>> hasCells = new ArrayList<HasCell<ContactInfo, ?>>();
        hasCells.add(new HasCell<ContactInfo, Boolean>() {

          private CheckboxCell cell = new CheckboxCell(true, false);

          public Cell<Boolean> getCell() {
            return cell;
          }

          public FieldUpdater<ContactInfo, Boolean> getFieldUpdater() {
            return null;
          }

          public Boolean getValue(ContactInfo object) {
            return selectionModel.isSelected(object);
          }
        });
        hasCells.add(new HasCell<ContactInfo, ContactInfo>() {

          private ContactCell cell = new ContactCell(images.contact());

          public Cell<ContactInfo> getCell() {
            return cell;
          }

          public FieldUpdater<ContactInfo, ContactInfo> getFieldUpdater() {
            return null;
          }

          public ContactInfo getValue(ContactInfo object) {
            return object;
          }
        });
        contactCell = new CompositeCell<ContactInfo>(hasCells) {
          @Override
          public void render(Context context, ContactInfo value, SafeHtmlBuilder sb) {
            sb.appendHtmlConstant("<table><tbody><tr>");
            super.render(context, value, sb);
            sb.appendHtmlConstant("</tr></tbody></table>");
          }

          @Override
          protected Element getContainerElement(Element parent) {
            // Return the first TR element in the table.
            return parent.getFirstChildElement().getFirstChildElement().getFirstChildElement();
          }

          @Override
          protected <X> void render(Context context, ContactInfo value,
              SafeHtmlBuilder sb, HasCell<ContactInfo, X> hasCell) {
            Cell<X> cell = hasCell.getCell();
            sb.appendHtmlConstant("<td>");
            cell.render(context, hasCell.getValue(value), sb);
            sb.appendHtmlConstant("</td>");
          }
        };
      }

      public <T> NodeInfo<?> getNodeInfo(T value) {
        if (value == null) {
          // Return top level categories.
          return new DefaultNodeInfo<Category>(categoryDataProvider,
              new CategoryCell(images.contactsGroup()));
        } else if (value instanceof Category) {
          // Return the first letters of each first name.
          Category category = (Category) value;
          List<ContactInfo> contacts = ContactDatabase.get().queryContactsByCategory(
              category);
          Map<Character, LetterCount> counts = new TreeMap<Character, LetterCount>();
          for (ContactInfo contact : contacts) {
            Character first = contact.getFirstName().charAt(0);
            LetterCount count = counts.get(first);
            if (count == null) {
              count = new LetterCount(category, first);
              counts.put(first, count);
            } else {
              count.increment();
            }
          }
          List<LetterCount> orderedCounts = new ArrayList<LetterCount>(
              counts.values());
          return new DefaultNodeInfo<LetterCount>(
              new ListDataProvider<LetterCount>(orderedCounts),
              new LetterCountCell());
        } else if (value instanceof LetterCount) {
          // Return the contacts with the specified character and first name.
          LetterCount count = (LetterCount) value;
          List<ContactInfo> contacts = ContactDatabase.get().queryContactsByCategoryAndFirstName(
              count.category, count.firstLetter + "");
          ListDataProvider<ContactInfo> dataProvider = new ListDataProvider<ContactInfo>(
              contacts, ContactInfo.KEY_PROVIDER);
          return new DefaultNodeInfo<ContactInfo>(
              dataProvider, contactCell, selectionModel, selectionManager, null);
        }

        // Unhandled type.
        String type = value.getClass().getName();
        throw new IllegalArgumentException("Unsupported object type: " + type);
      }

      public boolean isLeaf(Object value) {
        return value instanceof ContactInfo;
      }
    }