/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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 org.apache.openjpa.trader.client.ui;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.FlexTable;
import com.google.gwt.user.client.ui.Grid;
import com.google.gwt.user.client.ui.HTML;
import com.google.gwt.user.client.ui.HasHorizontalAlignment;
import com.google.gwt.user.client.ui.ScrollPanel;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwt.user.client.ui.Widget;

/**
 * A composite widget combines a data table, a table header, a scrollbar,
 * and a caption and a {@link HelpLink help anchor}.
 * <br>
 * Each row of the data table displays an instance of type T. How the instance
 * is displayed in controlled by a set of {@link GridCellRenderer renderers}
 * attached to each column.
 * <br>
 * The styles used
 * <LI>table-caption : for the caption
 * <LI>column-header : for the column headers
 * <LI>row-odd       : for odd numbered rows
 * <LI>row-even      : for even numbered rows
 *
 *
 * @author Pinaki Poddar
 *
 * @param <T> the type of data being displayed.
 */
public class ScrollableTable<T> extends Composite  {
    private FlexTable _main;
    private Grid _caption;
    private FlexTable _header;
    private ScrollPanel _scroll;
    private List<GridCellRenderer<T>> _renderers;
    private List<T> _rows;
    private boolean _stripeRows;

    private static final String STYLE_CAPTION = "table-caption";
    private static final String STYLE_HEADER  = "column-header";
    private static final String STYLE_MAIN    = "table";
    private static final String ROW_EVEN      = "row-even";
    private static final String ROW_ODD       = "row-odd";

    /**
     * Create a scrollable table with the given caption and given pixel dimension.
     * The table will not be backed by a data  storage model.
     *
     * @param caption of the table
     * @param w width in pixel
     * @param h height in pixel
     */
    public ScrollableTable(String caption, int w, int h) {
        this(caption, w+"px", h+"px", false);
    }

    /**
     * Create a scrollable table with the given caption and given pixel dimension.
     * The table will not be backed by a data  storage model.
     *
     * @param caption of the table
     * @param w width in pixel
     * @param h height in pixel
     * @param updatable whether the table data will be backed by a storage such
     * that row can be updated rather than always inserted
     */
    public ScrollableTable(String caption, int w, int h, boolean updatable) {
        this(caption, w+"px", h+"px", updatable);
    }

    /**
     * Create a scrollable table with the given caption and given dimension.
     *
     * @param caption of the table
     * @param w width in given unit
     * @param h height in given unit
     * @param updatable whether the table data will be backed by a storage such
     * that row can be updated rather than always inserted
     */
    public ScrollableTable(String caption, String w, String h, boolean updatable) {
        super();
        _renderers = new ArrayList<GridCellRenderer<T>>();

        VerticalPanel vert = new VerticalPanel();
        vert.setSpacing(0);

        _caption  = new Grid(1,2);
        _caption.setWidth("100%");
        setCaption(caption);
        vert.add(_caption);


        _header = new FlexTable();
        _header.addStyleName(STYLE_HEADER);
        _header.setWidth("100%");

        _main = new FlexTable();
        _main.addStyleName(STYLE_MAIN);

        _main.setWidth("100%");
        _main.setBorderWidth(0);
        _scroll = new ScrollPanel();
        _scroll.setSize(w, h);
        _scroll.setAlwaysShowScrollBars(true);
        _scroll.add(_main);


        vert.add(_header);
        vert.add(_scroll);
        if (updatable) {
            _rows = new ArrayList<T>();
        }

        initWidget(vert);
    }

    @SuppressWarnings("unchecked")
    public List<T> getModel() {
        return _rows == null ? Collections.EMPTY_LIST : Collections.unmodifiableList(_rows);
    }

    public void setCaption(String str) {
        HTML caption = new HTML(str);
        caption.addStyleName(STYLE_CAPTION);
        _caption.setWidget(0, 0, caption);
    }

    public void setSize(int w, int h) {
        int dh = 0;
        if (_caption != null) {
           dh += _caption.getOffsetHeight();
        }
        dh += _header.getOffsetHeight();
        _scroll.setPixelSize(w, h-dh);
    }


    public void setRenderer(int column, GridCellRenderer<T> r) {
        if (column > _renderers.size()) {
            for (int i = _renderers.size(); i <= column; i++) {
                _renderers.add(null);
            }
            _renderers.add(r);
        } else if (column == _renderers.size()) {
            _renderers.add(r);
        } else {
            _renderers.set(column, r);
        }
    }

    public void setColumnHeader(int column, String txt, String width) {
        HTML header = new HTML(txt);
        header.addStyleName(STYLE_HEADER);
        _header.setWidget(0, column, header);
        _main.getColumnFormatter().setWidth(column, width);
    }

    public void setStripeRows(boolean stripe) {
        _stripeRows = stripe;
    }

    public boolean isStripeRows() {
        return _stripeRows;
    }

    public void insert(T data) {
        setRow(_main.getRowCount(), data, null);
    }

    public void remove(T data) {
        int i = findRow(data);
        if (i != -1) {
            _main.removeRow(i);
            _rows.remove(i);
        }
    }

    /**
     * Update entire row.
     */
    public void update(T data, Integer[] columns) {
        int i = findRow(data);
        if (i != -1) {
            setRow(i, data, columns);
        } else {
            insert(data);
        }
    }

    public void updateCell(int row, int column, Widget widget, boolean animate) {
        if (animate) {
            FadeEffect fadeOut = new FadeEffect(_main.getWidget(row, column), false);
            fadeOut.scheduleRepeating(10);
            DOM.setElementAttribute(widget.getElement(), "opacity", "0.0");
            _main.setWidget(row, column, widget);
            FadeEffect fadeIn = new FadeEffect(_main.getWidget(row, column), true);
            fadeIn.scheduleRepeating(10);
        } else {
            _main.setWidget(row, column, widget);
        }
    }

    public int getRowCount() {
        return _main.getRowCount();
    }

    /**
     * Sets the cells of an existing row.
     * Calls each renderer.
     * @param row
     * @param data
     */
    private void setRow(int row, T data, Integer[] columns) {
        if (_rows != null) {
            if (row < 0 || row >= _rows.size())
                _rows.add(data);
            else
                _rows.set(row, data);
        }
        for (int i = 0; i < _renderers.size(); i++) {
            GridCellRenderer<T> r = _renderers.get(i);
            if (r == null)
                continue;
            if (containsColumn(columns, i)) {
                Widget widget = r.render(data);
                if (widget != null)
                    _main.setWidget(row, i, widget);
            }
        }
        if (isStripeRows()) {
            _main.getRowFormatter().setStylePrimaryName(row, row%2 == 0? ROW_EVEN : ROW_ODD);
        }
        _scroll.scrollToBottom();
    }

    public int findRow(T data) {

        if (_rows == null || data == null)
            return -1;
        return _rows.indexOf(data);
    }

    private boolean containsColumn(Integer[] columns, int i) {
        if (columns == null)
            return true;
        for (int j = 0; j < columns.length; j++) {
            if (columns[j] == i)
                return true;
        }
        return false;
    }

    public T get(int i) {
        if (_rows == null || i < 0 || i >= _rows.size())
            return null;
        return _rows.get(i);
    }

    public void scrollToBottom() {
        _scroll.scrollToBottom();
    }

    public void addHelp(final String url) {
        if (_caption == null) {
            return;
        }
        _caption.getColumnFormatter().setWidth(0, "95%");
        HelpLink help = new HelpLink(url);
        _caption.setWidget(0, 1, help);
        _caption.getCellFormatter().setHorizontalAlignment(0, 1, HasHorizontalAlignment.ALIGN_RIGHT);
    }


}
