/*
 * Copyright 2007 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.dynatable.client;

import com.google.gwt.sample.dynatable.client.DynaTableDataProvider.RowDataAcceptor;
import com.google.gwt.user.client.rpc.InvocationException;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.ClickListener;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.DialogBox;
import com.google.gwt.user.client.ui.DockPanel;
import com.google.gwt.user.client.ui.Grid;
import com.google.gwt.user.client.ui.HTML;
import com.google.gwt.user.client.ui.HasAlignment;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwt.user.client.ui.Widget;

/**
 * A composite Widget that implements the main interface for the dynamic table,
 * including the data table, status indicators, and paging buttons.
 */
public class DynaTableWidget extends Composite {

  /**
   * A dialog box for displaying an error.
   */
  private static class ErrorDialog extends DialogBox implements ClickListener {
    private HTML body = new HTML("");

    public ErrorDialog() {
      setStylePrimaryName("DynaTable-ErrorDialog");
      Button closeButton = new Button("Close", this);
      VerticalPanel panel = new VerticalPanel();
      panel.setSpacing(4);
      panel.add(body);
      panel.add(closeButton);
      panel.setCellHorizontalAlignment(closeButton, VerticalPanel.ALIGN_RIGHT);
      setWidget(panel);
    }

    public String getBody() {
      return body.getHTML();
    }

    public void onClick(Widget sender) {
      hide();
    }

    public void setBody(String html) {
      body.setHTML(html);
    }
  }

  private class NavBar extends Composite implements ClickListener {

    public final DockPanel bar = new DockPanel();
    public final Button gotoFirst = new Button("&lt;&lt;", this);
    public final Button gotoNext = new Button("&gt;", this);
    public final Button gotoPrev = new Button("&lt;", this);
    public final HTML status = new HTML();

    public NavBar() {
      initWidget(bar);
      bar.setStyleName("navbar");
      status.setStyleName("status");

      HorizontalPanel buttons = new HorizontalPanel();
      buttons.add(gotoFirst);
      buttons.add(gotoPrev);
      buttons.add(gotoNext);
      bar.add(buttons, DockPanel.EAST);
      bar.setCellHorizontalAlignment(buttons, DockPanel.ALIGN_RIGHT);
      bar.add(status, DockPanel.CENTER);
      bar.setVerticalAlignment(DockPanel.ALIGN_MIDDLE);
      bar.setCellHorizontalAlignment(status, HasAlignment.ALIGN_RIGHT);
      bar.setCellVerticalAlignment(status, HasAlignment.ALIGN_MIDDLE);
      bar.setCellWidth(status, "100%");

      // Initialize prev & first button to disabled.
      //
      gotoPrev.setEnabled(false);
      gotoFirst.setEnabled(false);
    }

    public void onClick(Widget sender) {
      if (sender == gotoNext) {
        startRow += getDataRowCount();
        refresh();
      } else if (sender == gotoPrev) {
        startRow -= getDataRowCount();
        if (startRow < 0) {
          startRow = 0;
        }
        refresh();
      } else if (sender == gotoFirst) {
        startRow = 0;
        refresh();
      }
    }
  }

  private class RowDataAcceptorImpl implements RowDataAcceptor {
    public void accept(int startRow, String[][] data) {

      int destRowCount = getDataRowCount();
      int destColCount = grid.getCellCount(0);
      assert (data.length <= destRowCount) : "Too many rows";

      int srcRowIndex = 0;
      int srcRowCount = data.length;
      int destRowIndex = 1; // skip navbar row
      for (; srcRowIndex < srcRowCount; ++srcRowIndex, ++destRowIndex) {
        String[] srcRowData = data[srcRowIndex];
        assert (srcRowData.length == destColCount) : " Column count mismatch";
        for (int srcColIndex = 0; srcColIndex < destColCount; ++srcColIndex) {
          String cellHTML = srcRowData[srcColIndex];
          grid.setText(destRowIndex, srcColIndex, cellHTML);
        }
      }

      // Clear remaining table rows.
      //
      boolean isLastPage = false;
      for (; destRowIndex < destRowCount + 1; ++destRowIndex) {
        isLastPage = true;
        for (int destColIndex = 0; destColIndex < destColCount; ++destColIndex) {
          grid.clearCell(destRowIndex, destColIndex);
        }
      }

      // Synchronize the nav buttons.
      navbar.gotoNext.setEnabled(!isLastPage);
      navbar.gotoFirst.setEnabled(startRow > 0);
      navbar.gotoPrev.setEnabled(startRow > 0);

      // Update the status message.
      //
      setStatusText((startRow + 1) + " - " + (startRow + srcRowCount));
    }

    public void failed(Throwable caught) {
      setStatusText("Error");
      if (errorDialog == null) {
        errorDialog = new ErrorDialog();
      }
      if (caught instanceof InvocationException) {
        errorDialog.setText("An RPC server could not be reached");
        errorDialog.setBody(NO_CONNECTION_MESSAGE);
      } else {
        errorDialog.setText("Unexcepted Error processing remote call");
        errorDialog.setBody(caught.getMessage());
      }
      errorDialog.center();
    }
  }

  private static final String NO_CONNECTION_MESSAGE = "<p>The DynaTable example uses a <a href=\"http://code.google.com/"
      + "webtoolkit/documentation/com.google.gwt.doc.DeveloperGuide."
      + "RemoteProcedureCalls.html\" target=\"_blank\">Remote Procedure Call</a> "
      + "(RPC) to request data from the server.  In order for the RPC to "
      + "successfully return data, the server component must be available.</p>"
      + "<p>If you are running this demo from compiled code, the server "
      + "component may not be available to respond to the RPC requests from "
      + "DynaTable.  Try running DynaTable in hosted mode to see the demo "
      + "in action.</p> "
      + "<p>Click on the Remote Procedure Call link above for more information "
      + "on GWT's RPC infrastructure.";

  private final RowDataAcceptor acceptor = new RowDataAcceptorImpl();

  private final Grid grid = new Grid();

  private final NavBar navbar = new NavBar();

  private ErrorDialog errorDialog = null;

  private final DockPanel outer = new DockPanel();

  private final DynaTableDataProvider provider;

  private int startRow = 0;

  public DynaTableWidget(DynaTableDataProvider provider, String[] columns,
      String[] columnStyles, int rowCount) {

    if (columns.length == 0) {
      throw new IllegalArgumentException(
          "expecting a positive number of columns");
    }

    if (columnStyles != null && columns.length != columnStyles.length) {
      throw new IllegalArgumentException("expecting as many styles as columns");
    }

    this.provider = provider;
    initWidget(outer);
    grid.setStyleName("table");
    outer.add(navbar, DockPanel.NORTH);
    outer.add(grid, DockPanel.CENTER);
    initTable(columns, columnStyles, rowCount);
    setStyleName("DynaTable-DynaTableWidget");
  }

  public void clearStatusText() {
    navbar.status.setHTML("&nbsp;");
  }

  public void refresh() {
    // Disable buttons temporarily to stop the user from running off the end.
    //
    navbar.gotoFirst.setEnabled(false);
    navbar.gotoPrev.setEnabled(false);
    navbar.gotoNext.setEnabled(false);

    setStatusText("Please wait...");
    provider.updateRowData(startRow, grid.getRowCount() - 1, acceptor);
  }

  public void setRowCount(int rows) {
    grid.resizeRows(rows);
  }

  public void setStatusText(String text) {
    navbar.status.setText(text);
  }

  private int getDataRowCount() {
    return grid.getRowCount() - 1;
  }

  private void initTable(String[] columns, String[] columnStyles, int rowCount) {
    // Set up the header row. It's one greater than the number of visible rows.
    //
    grid.resize(rowCount + 1, columns.length);
    for (int i = 0, n = columns.length; i < n; i++) {
      grid.setText(0, i, columns[i]);
      if (columnStyles != null) {
        grid.getCellFormatter().setStyleName(0, i, columnStyles[i] + " header");
      }
    }
  }
}
