/* Copyright (c) 2009 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.appengine.demos.sticky.client;

import com.google.appengine.demos.sticky.client.model.Model;
import com.google.appengine.demos.sticky.client.model.Note;
import com.google.appengine.demos.sticky.client.model.Surface;
import com.google.gwt.dom.client.DivElement;
import com.google.gwt.dom.client.Document;
import com.google.gwt.dom.client.Element;
import com.google.gwt.event.dom.client.BlurEvent;
import com.google.gwt.event.dom.client.BlurHandler;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.dom.client.KeyCodes;
import com.google.gwt.event.dom.client.KeyPressEvent;
import com.google.gwt.event.dom.client.KeyPressHandler;
import com.google.gwt.user.client.ui.AbstractImagePrototype;
import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.ImageBundle;
import com.google.gwt.user.client.ui.SimplePanel;
import com.google.gwt.user.client.ui.TextBox;

/**
 * A widget for displaying the list of available surfaces to a user and for
 * adding new surfaces.
 * 
 * @author knorton@google.com (Kelly Norton)
 */
public class SurfaceListView extends FlowPanel implements Model.DataObserver {
  /**
   * Declaration of image bundle resources used in this widget.
   */
  public interface Images extends ImageBundle {
    @Resource("surface-list-add-hv.gif")
    AbstractImagePrototype surfaceListAddSurfaceButtonHv();

    @Resource("surface-list-add-up.gif")
    AbstractImagePrototype surfaceListAddSurfaceButtonUp();
  }

  /**
   * Provides a way to react to events generated by a {@link SurfaceListView}.
   */
  public interface Observer {

    /**
     * Invoked when the {@link SurfaceListView} is hidden with a call to
     * {@link SurfaceListView#hide()}.
     */
    void onHide();

    /**
     * Invoked when the {@link SurfaceListView} is shown with a call to
     * {@link SurfaceListView#show()}.
     */
    void onShow();
  }

  /**
   * A widget that provides inline editing when the user adds a new surface.
   */
  private class EditView extends FlowPanel implements BlurHandler,
      KeyPressHandler {
    private final TextBox titleTextBox;

    /**
     * 
     */
    public EditView() {
      final Document document = Document.get();
      final Element element = getElement();

      final Element rowElement = element.appendChild(document
          .createDivElement());
      rowElement.setClassName("surface-item-edit");

      titleTextBox = new TextBox();
      titleTextBox.setStyleName("surface-item-title");
      add(titleTextBox, rowElement.<com.google.gwt.user.client.Element> cast());
      titleTextBox.addBlurHandler(this);
      titleTextBox.addKeyPressHandler(this);

      final Element authorsElement = rowElement.appendChild(document
          .createDivElement());
      authorsElement.setClassName("surface-item-authors");
      authorsElement.setInnerText("/w Only You.");

      SurfaceListView.this.add(this, listElement
          .<com.google.gwt.user.client.Element> cast());

      titleTextBox.setFocus(true);
    }

    public void onBlur(BlurEvent event) {
      commit();
    }

    public void onKeyPress(KeyPressEvent event) {
      switch (event.getCharCode()) {
      case KeyCodes.KEY_ENTER:
        commit();
        break;
      case KeyCodes.KEY_ESCAPE:
        break;
      }
    }

    private void commit() {
      final String value = titleTextBox.getValue().trim();
      SurfaceListView.this.remove(this);
      if (value.length() > 0) {
        model.createSurface(value);
        hide();
      }
    }
  }

  /**
   * A widget to display information about a surface in a list.
   */
  private class ItemView extends SimplePanel implements ClickHandler {
    private final Element titleElement, authorsElement, noteCountElement;

    private final Surface surface;

    /**
     * @param surface
     *          the surface to be displayed
     */
    public ItemView(Surface surface) {
      this.surface = surface;
      final Document document = Document.get();
      final Element element = getElement();
      final Element rowElement = element.appendChild(document
          .createDivElement());
      titleElement = rowElement.appendChild(document.createSpanElement());
      noteCountElement = rowElement.appendChild(document.createSpanElement());
      authorsElement = rowElement.appendChild(document.createDivElement());

      rowElement.setClassName("surface-item" + getNextStyleNameSuffix());
      titleElement.setClassName("surface-item-title");
      noteCountElement.setClassName("surface-item-count");
      authorsElement.setClassName("surface-item-authors");

      titleElement.setInnerText(surface.getTitle());
      authorsElement.setInnerText(surface.getAuthorNamesAsString());
      noteCountElement.setInnerText("(" + surface.getNoteCount() + " notes)");

      addDomHandler(this, ClickEvent.getType());

      // Add to parent.
      SurfaceListView.this.add(this, listElement
          .<com.google.gwt.user.client.Element> cast());
    }

    public void onClick(ClickEvent event) {
      model.selectSurface(surface);
      hide();
    }
  }

  private final DivElement listElement;

  private int count;

  private final Model model;

  private final Observer observer;

  /**
   * @param model
   *          the model to which the Ui will bind itself
   * @param observer
   *          a single observer to receive callbacks from this object
   */
  public SurfaceListView(Images images, Model model, Observer observer) {
    this.model = model;
    this.observer = observer;

    setVisible(false);
    setStyleName("surface-list");

    final Element element = getElement();
    final Element titleElement = element.appendChild(Document.get()
        .createDivElement());
    titleElement.setClassName("surface-list-title");
    titleElement.setInnerText("Your Sticky Surfaces");

    final Element innerElement = element.appendChild(Document.get()
        .createDivElement());
    innerElement.setClassName("surface-list-list");

    listElement = innerElement.appendChild(Document.get().createDivElement());

    add(Buttons.createPushButtonWithImageStates(images
        .surfaceListAddSurfaceButtonUp().createImage(), images
        .surfaceListAddSurfaceButtonHv().createImage(), "surface-list-add",
        new ClickHandler() {
          public void onClick(ClickEvent event) {
            new EditView();
          }
        }), innerElement.<com.google.gwt.user.client.Element> cast());

    model.addDataObserver(this);
  }

  /**
   * Hides the Ui for this widget and notifies the {@link Observer} accordingly.
   */
  public void hide() {
    setVisible(false);
    observer.onHide();
  }

  public void onNoteCreated(Note note) {
  }

  public void onSurfaceCreated(Surface surface) {
    new ItemView(surface);
  }

  public void onSurfaceNotesReceived(Note[] notes) {
  }

  public void onSurfaceSelected(Surface nowSelected, Surface wasSelected) {
  }

  public void onSurfacesReceived(Surface[] surfaces) {
    for (int i = 0, n = surfaces.length; i < n; i++) {
      new ItemView(surfaces[i]);
    }
  }

  /**
   * Makes the Ui for this widget visible and notifies the {@link Observer}.
   */
  public void show() {
    setVisible(true);
  }

  private String getNextStyleNameSuffix() {
    return ((count++ & 1) == 0) ? "-odd" : "-even";
  }
}
